数组
数据可以存放在变量里,每一个变量有一个名字,有一个类型,还有它的生存空间。如果我们需要保存一些相同类型、相似含义、相同生存空间的数据,我们可以用数组来保存这些数据,而不是用很多个独立的变量。数组是长度固定的数据结构,用来存放指定的类型的数据。一个数组里可以有很多个数据,所有的数据的类型都是相同的。
初试数组
- 如何写一个程序计算用户输入的数字的平均数?
- 不需要记录,也就不需要数组
如何写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数?
int number [100];
scanf("%d", &number[i]);
int main()
{
int current;
double sum = 0.0;
int count = 0;
int number[100];
scanf("%d", ¤t);
while (current != -1)
{
number[count] = current;
sum += current;
count++;
scanf("%d", ¤t);
}
if (count > 0)
{
printf("%f\n", sum / count);
for (int i = 0; i < count; i++)
{
if (number[i] > sum / count)
{
printf("%d\n", number[i]);
}
}
}
return 0;
}
注意:这段程序存在安全隐患,我们预先定义好了这个数组长度为
100
,而我们输入的时候,并没有限制要输入多少个,所以输入的数字数量可能超过100
。定义数组
- <类型> 变量名称[元素数量];
int grades[100]
double weight[20]
- 元素数量必须是整数
C99
之前:元素数量必须是编译时刻确定的字面量- 数组是一个容器,这是现代程序语言设计都应该有的一种特性,特点是:
- 其中所有的元素具有相同的数据类型
- 一旦创建,不能改变大小
- 数组中的元素在内存中是连续依次排列的
初始化数组
int a[4] = { 1, 2 };
int a[] = { 1, 2, 0, 0 };
int a[] = { 1, 2, 0, 0, };
int a[4] = { 1, 2, 0, 0, 5 };
int a[10]
- 一个
int
类型的数组 10
个单元:a[0], a[1], ......, a[9]
- 每个单元就是一个
int
类型的变量 - 可以出现在赋值的左边或右边
a[2] = a[1] + 6;
- 在赋值左边的叫做左值
数组的单元
- 数组的每个单元就是数组类型的一个变量
- 使用数组时放在
[]
中的数字叫做下标或索引,下标从0
开始计数:grades[0]
grades[99]
average[5]
有效的下标范围
- 编译器和运行环境都不会检查数组下标是否越界,无论对数组单元做读还是写
- 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
segmentation fault
- 但是可能运气好,没造成严重的后果
- 所以这是程序员的责任来保证程序只使用有效的下标值:
[0, 数组的大小-1]
长度为0
的数组?
int main()
{
int current;
int record[10];
for (int i = 0; i < 10; i++)
{
record[i] = 0;
}
printf("请输入数字:");
scanf("%d", ¤t);
while (current != -1 && current >= 0 && current <= 9)
{
record[current]++;
printf("请输入数字:");
scanf("%d", ¤t);
}
for (int i = 0; i < 10; i++)
{
printf("record[%d]=%d ", i, record[i]);
}
return 0;
}
函数的定义和使用
初见函数
int fnGetLength(int num)
{
int length = 0;
while (num != 0)
{
num /= 10;
length++;
}
return length;
}
求和
求出
1
到10
、20
到30
和35
到45
的三个和
可以发现这三个程序大同小异,都是求和,我们可以将它们封装起来,提高代码质量,提升开发效率
int fnGetSum(int num1, int num2)
{
int sum = 0;
for (int i = num1; i <= num2; i++)
{
sum += i;
}
return sum;
}
什么是函数
- 函数是一块代码,接受零个或多个参数,做一件事情,并返回零个或一个值
- 可以先现象成数学中的函数:
y = f(x)
函数定义
调用函数
- 函数名(参数值);
()
起到了表示函数调用的重要作用
- 即使没有参数也需要
()
- 如果有参数,则需要给出正确的数量和顺序
- 这些值会被按照顺序依次用来初始化函数中的参数
从函数中返回
从函数中返回值
return
停止函数的执行,并返回一个值return;
return 表达式;
- 可以赋值给变量
- 可以再传递给函数
没有返回值的函数
void
函数名(参数表)- 不能使用带值的
return
- 可以没有
return
- 调用的时候不能做返回值的赋值
函数的参数和变量
函数原型
函数先后关系
- 像这样把
sum()
写在上面,是因为:C
的编译器自上而下顺序分析你的代码
如果用户自定义函数很多,全部放在main()
函数前面,会降低用户的可读性,因此我们要说明函数原型
函数原型
- 函数头,以分号
;
结尾,就构成了函数的原型- 函数原型的目的就是告诉编译器这个函数长什么样
- 名称
- 参数(数量及类型)
- 返回类型
- 旧标准习惯把函数原型写在调用它的函数里面
- 现在一般写在调用它的函数前面
- 原型里可以不写参数的名字,但是一般仍然写上
int fnGetLength(int num);
int fnGetSum(int num1, int num2);
函数传递
调用函数
- 如果函数有参数,调用函数时必须传递给它数量、类型正确的值
- 可以传递给函数的值时表达式的结果,这包括:
- 字面量
- 变量
- 函数的返回值
- 计算的结果
类型不匹配
- 调用函数时给的值与参数的类型不匹配时C语言传统上最大的漏洞
- 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
- 后续的语言,
C++/Java
在这方面很严格
传过去的是什么?
void fnSwapValue(int num1, int num2)
{
int temp = num1;
num1 = num2;
num2 = temp;
}
这样的代码能交换a
和b
的值吗?
C语言在调用函数的时候,永远只能传值给函数
传值
- 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
- 过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值,叫做“实际参数”
- 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量而不是把值传进去了,所以我们不建议用这种古老的方式来称呼它们。
FORTRAN
语言有把变量传进去的方法,而C
语言只能传值- 我们认为,它们时参数和值的关系
本地变量
- 函数的每一次运行,就产生了一个独立的变量空间,在这个空间中的变量,时函数的这次运行所独有的,称作本地变量
- 定义在函数内部的变量就是本地变量
- 参数也是本地变量
变量的生存期和作用域
- 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
- 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
- 对于本地变量,这两个问题的答案是统一的:大括号内——块
本地变量的规则
- 本地变量是定义在块内的
- 它可以是定义在函数的块内
- 也可以定义在语句的块内
- 甚至可以随便拉一对大括号来定义变量
- 程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
- 块外面定义的变量在里面仍然有效
- 块里面定义了和外面同名的变量则掩盖了外面的
函数轶事
没有参数时
- 编写函数原型时一定要严谨,要明确说明有没有参数,否则可能会出错
void swap(void);
- 编写函数原型时一定要严谨,要明确说明有没有参数,否则可能会出错
逗号运算符
- 调用函数时的逗号和逗号运算符怎么区分?
- 调用函数时的圆括号里的逗号时标点符号,不是运算符
f(a, b)
f((a, b))
函数里的函数?
C语言不允许函数嵌套定义
what?
int i, j, sum(int a, b);
- 定义了
int
型变量i
和j
,也声明了函数sum
,需要两个int
型参数a
和b
return (i);
- 没有任何意义,是一个表达式,并没有什么意义,但会让人误以为是一个函数
关于main()
int main()
也是一个函数- 要不要写成
int main(void)
return
的0
有人看吗?
Windows: if errorlevel 1...
(批处理,可根据不同返回结果,做相应的操作)Unix Bash: echo $?
Csh: echo $status
一些题
- 对于不返回值而且只有一个int类型的参数的函数,以下哪些函数原型是正确的?
void f(int x);
void f();
void f(x);
void f(int);
- 以下程序的输出是什么? ```c void swap(int a, int b); int main() { int a = 5; int b = 6; swap(a,b); printf(“%d-%d\n”, a, b); return 0; } void swap(int a, int b) { int t = a; a = b; b = t; }
// swap()函数并没有改变变量的值 // 所以出书的输出结果是:5-6
<a name="qsHm1"></a>
# 二维数组
> `int a[3][5];`
> 通常理解为`a`是一个`3`行`5`列的矩阵
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1557552/1641384070843-ac296537-de2a-408e-8e2a-504fd3f94ee8.png#clientId=u4f0d4fe9-4b73-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=116&id=u1afe46f4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=232&originWidth=902&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12538&status=done&style=none&taskId=u3cda2d86-e6ce-4727-85e2-9953bd96eee&title=&width=451)
<a name="CF89u"></a>
### 二维数组的遍历
```c
int main()
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
printf("%d ", a[i][j]);
}
}
return 0;
}
a[i][j]
是一个int
- 表示第
i
行第j
列上的单元 a[i, j]
是什么?a[j]
,逗号运算符的值是右边的值,也就是j
二维数组的初始化
int a[][5] =
{
{0, 1, 2, 3, 4},
{2, 3, 4, 5, 6},
};
- 列数是必须给出的,行数可以由编译器来数
- 每行一个
{}
,逗号分隔 - 最后的逗号可以存在,有古老的传统;
- 如果省略表示补零
- 也可以用定位(
* C99 ONLY
) -
井字棋
// 比较X和O数量,来确定谁赢了 int main() { int countOfX = 0; int countOfO = 0; char chess[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (chess[i][j] == "x") { countOfX++; } if (chess[i][j] == "o") { countOfO++; } } } if (countOfX == countOfO) { printf("平局!"); } else if (countOfX < countOf) { printf("X获胜!"); } else { printf ("O获胜!"); } }