文章目录
Lecture 3 数组与函数
1. 数组
如何记录很多数?→ 数组
如何定义一个数组:<类型> 变量名称[元素数量];
int grades[100];//grades[0]——grades[99]
double weight[20];
注意:数组下标(0—n-1)与数组元素个数(n)不一致
数组越界:segment fault
1.1 数组的集成初始化
定义数组时给数组赋值称为数组的集成初始化;
1.2 数组的大小
- sizeof给出整个数组所占据的内容的大小,单位是字节;
- 数组中元素个数 = sizeof(a) / sizeof(a[0])
1.3 数组的遍历
数组变量本身不能被赋值,要把一个数组的所有元素交给另一个数组,必须采用遍历:
错误赋值方式:
int a[] = {1, 2, 3, 4, 5, 6, }; int b[] = a;
正确赋值方式:
for ( i=0; i<length; i++)
{
b[i]=a[i];
}
遍历数组常见错误:
- 循环结束条件是<=数组长度;
- 离开循环后,继续用i的值来做数组元素的下标。
1.4 数组作为函数参数
- 数组作为函数参数时,往往必须再用另一个参数来传入数组的大小;
- 数组作为函数参数时,不能在[]中给出数组的大小(没有意义);
- 也不能在参数列表中或函数内部利用sizeof来计算数组的元素个数(因为数组是以指针的形式传进函数)
- 详见指针的应用场景——“求最大最小值”部分
1.5 实例
1.5.1 统计输入量0-9中各个数字出现的个数
//统计输入量0-9中各个数字出现的个数,输入-1表示结束。
#include <stdio.h>
int main()
{
const int number = 10; //数组的大小
int x;
int count[number]; //定义数组 (C99:数组大小可以为一个变量)
int i;
/*****初始化数组*****/
for ( i=0; i<number; i++)
{
count[i]=0;
}
scanf("%d", &x);
while ( x!=-1)
{
if ( x>=0 && x<=9)
{
count[x]++; //数组参与运算
}
scanf("%d", &x);
}
/*****遍历数组输出*****/
for ( i=0; i<number; i++)
{
printf("%d: %d\n", i, count[i]);
}
return 0;
}
1.5.2 选择排序
先找出最大的,放到最后面(即与数组中最后一个数交换);固定最后一个,在剩下的数中再找出最大的,放到最后面;重复进行此步骤即可。
详见:选择排序&二分搜索
2.函数
函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值。
函数定义:
2.1 void函数
没有返回值的函数用void函数名(参数表),不能使用带值的return(可以没有return),调用的时候不能做返回值的赋值;如果函数有返回值,则必须使用带值的return。
2.2 函数声明
- 要想把函数写在main函数后面,需要先写声明:
函数头;
- 函数头加;构成函数原型;函数原型可以不写参数名,只写参数类型
2.3 函数调用
调用函数:函数名(参数值);
()起到了表示函数调用的重要作用,即使没有参数也需要() 如果有参数,则需要给出正确的数量和顺序,这些值会被按照顺序依次用来初始化函数中的参数。
从函数中返回值:return停止函数的执行,并送回一个值
- return;
- return 表达式;
调用函数时给的值与参数的类型不匹配是C语言传统上的最大漏洞,编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的,后续的语言,C++/Java在这方面很严格。
C语言在调用函数时,永远只能传值给函数(而不是变量)。每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系。
2.4 本地变量
函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量;定义在函数内部的变量就是本地变量,参数也是本地变量。
本地变量的规则:
- 本地变量是定义在块(即大括号{})内的;程序运行进入这个块之前,其中的变量不存在;离开这个块,其中的变量就消失了;
- 块外面定义的变量在块里面仍然有效;块里面定义了和外面同名的变量则掩盖了外面的;
- 不能在同一个块定义同名的变量;
- 本地变量不会被默认初始化;参数在进入函数的时候被初始化了。
2.5 其他细节
- 如果确定函数没有参数,函数定义写为
void f(void);
void f();
表示f函数的参数表未知,并不表示没有参数; - 调用函数时的圆括号里的逗号是标点符号,不是运算符
f(a, b)和f((a, b))区别在于前者传递了两个参数,后者传递了一个参数。 - C语言不允许函数嵌套定义
3. 二维数组
定义:int a[3][5];
通常理解为a是一个3行5列的矩阵:
3.1 二维数组的遍历
for ( i=0; i<3; i++)
{
for ( j=0; j<5; j++)
{
a[i][j] = i*j;
//a[i][j]是一个int,表示第i行第j列上的单元
}
}
3.2 二维数组的初始化
int a[][5]=
{
{0,1,2,3,4},
{1,2,3,4,5},
{2,3,4,5,6},
};
- 列数是必须给出的,行数可以由编译器来数
- 每行一个{},逗号分隔
- 最后的逗号可以存在,有古老的传统
- 如果省略,表示补零
- 也可以用定位(C99 ONLY)
注意:只有定义时可以这样初始化,定义完成后应通过遍历方法用for循环初始化;原因在于定义完成后a[][]被视作数组中的一个元素,而不是整个数组
使用变量定义数组时,定义时不能直接初始化,需要之后利用遍历来初始化数组。例如:
const int cnt = 3;
int a[cnt][cnt];
//此处不能直接对数组进行集成初始化,需要之后利用遍历来初始化数组
3.3 实例分析
tic-tac-toe游戏,详见:二维数组应用之tic-tac-toe游戏胜负判断
4.几点注意
- “代码复制”是程序质量不良的表现;
- 算法不一定和人的思考方式相同;单一出口原则(一个return);一专多能是不好的代码;
- 小技巧:在程序中平白无故加个大括号,多数时候是为了调试程序,(观察程序输出过程)利用本地变量的规则,方便调试。
- 收获:程序运行结果错误时,利用编译器的调试功能,一步一步调代码,观察代码运行过程,一般都可以发现错误所在;