C和指针:第七,八章
第7章 函数
1. 函数的定义与声明
函数的定义是函数体的实现,而声明是向编译器提供该函数的信息,用于确保函数能被正确调用。
提供函数原型的两种方式:一是,在当前文件中已定义函数;二是,通过#include 指令导入函数原型。
函数原型中的参数名称是可选的,但在函数原型中加入描述性的参数可提供更有用的信息。如:
char *strcpy( char *, char *) ;
char *strcpy( char *destination, char *source);
函数原型的参数必须与函数定义参数匹配,这里的匹配,包括参数类型和顺序。
2. 函数的缺省值:当程序调用一个无法见到原型的函数时,编译器默认该函数返回一个整型值。
3. 函数的参数
C函数的所有参数默认都是“值传递”,这意味着函数将获得参数的一份拷贝。但,如果被传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用程序中的数组元素。这时的数组元素不会被复制,这种被称为“地址传递”。原因是,数组名实际上是一个指针,传递给函数的是一个指针的拷贝。
可以将函数参数设为指针或是引用,实现以地址传递。然而,这也是我们大多情况下所选择的。
4. 函数的递归与迭代
递归函数就是调用自身,而迭代是将函数功能拆分成若干步骤,再重复执行这些步骤。递归的方便之处就是代码简单,缺点是它会带来不必要的系统开销,而且在特定条件下(比如在计算Fibonacci 数列时),这样的开销是巨大的。而使用迭代可以提高程序的效率。如果一个递归函数内部所执行的最后一条语句就是调用自身时,就被称为尾部递归。尾部递归可以很容易地改写为循环的形式,这样的效率也通常更高一些。
5. 可变参数列表
可变参数列表通过宏来实现,这些宏定义在stdarg.h头文件中,它是标准的一部分。可变参数必须从头到尾按顺序逐个访问。如果一开始就想访问参数列表中间的参数,是不行的。
第8章 数组
1. 数组名是指针常量,也是数组第一个元素的地址。
2. 下标引用,假如有:
int array[10];
int *ap = array + 2;
1). *ap,解除引用,也就是array[2]的值,你也可以写成:*(array + 2)
2). ap[0],与它对待的表达式是:*(ap+(0)),去掉0和括号,结果与上面的*ap表达式相等。因此也是array[2]的值。
3). ap+6,ap指向的是array[2],再往这个基础上加6,就是指向array[8],表达式相当于:array+8或 &array[8]
4). *ap+6,这个结果是先解除引用,再与6相加,表达式相当于:array[2] + 6
5). *(ap+6),先算括号里的,下标指向数组中的第9个元素,再解除引用,表达式相当于:array[8]
6). ap[6],与2). 类似,相当于:*(array+8)
7). &ap,这个表达式合法,但不知道ap会放在相对于array的什么位置
8). ap[-1],与2).,6).类似,相当于 array[1]
9). ap[9],表达式看上去正常,但实际上已经越界,它相当于array[11]
10). 2[array] 与array[2]相等,但不应该这样书写,这样会影响程序的可读性
3. 指针与下标
使用下标绝不会比指针更有效率,使用指针有时会比下标更有效率
4. 只要有可能函数的指针形参,都应用const 来修饰
5. 如果要向函数传递一个数组名参数,函数形参可以是指针或数组,以下两条语句等效:
int strlen (char *string);
int strlen (char string[]);
但使用指针更为准确。因为函数的形参实际上是个指针,而不是数组。它指向的是已经分配好的内存空间,这也解释了为什么数组形参可以与任何长度的数组匹配(它实际上传递的只是指向数组第一个元素的指针)。
6. 数组的初始化,和变量一样。静态内存中的数组,默认初始化值为0,动态内存的数组默认不初始化。
static int c[10]; // 默认数组所有元素为值0
如果初始化时,初始化值的个数大于数组元素个数,是非法的(但在不同编译器下结果却不一样,在VC 6.0下是非法的,在Code::Blocks 10.05下是合法的);如果初始化值的个数小于数组元素个数,未被指定初始化的元素值为0;但如果初始化时逗号前没有初始化值(初始化时,不允许省略中间值,只允许省略最后的几个值)是非法的,如:
int c[5] = {1, 2, 3, 4, 5, 6}; // VC 6.0下非法,Code::Blocks 10.05下合法
int c[5] = {1, 2, 3, 4}; // 合法, c[4] = 0
int c[5] = {1, 2, , 4, 5 }; // 非法
以下初始化将数组长度设置为刚好容纳所有初始化值的长度:
int c[] = {1, 2, 3, 4, 5, 6, 7};
字符数组的初始化:
char message[] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, 0};
或
char message[] = “hello”;
7. 多维数组,如果有:
int matrix [3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
*(matrix + 1) 指向matrix[0][1]
*(matrix + 1) + 5 指向matrix[1][1]
8. 指向数组的指针,假如有:
int matrix[3][10];
int *mp = matrix; // 非法,因为matrix是一个指向整型数组的指针,而mp是指向整型变量的指针
应当声明为:
int (*mp)[10] = matrix; // mp 指向matrix的第一行, mp是一个指向拥有10个整型元素的数组的指针
mp+1 指向matrix的第二行
如果要在元素之间移动,应该声明为:
int *pi = &matrix[0][0]; 或 int *pi = matrix[0];
9. 函数参数的多维数组
int matrix[3][10];
fun(matrix);
如果函数fun的参数matrix是指向一个包含10个整型元素的数组的指针,则fun的原型应是:
void fun(int (*mp)[10]); 或 void fun(int mp[][10]);
以上函数原型中必须声明第二维的长度,这样编译器才能对各下标进行运算
10. 两种字符串存储方案:
1).
char const keyword[][9] =
{
“do”,
“for”,
“if”,
“register”,
“switch”,
“while”
};
2.)
char const *keyword[] =
{
“do”,
“for”,
“if”,
“register”,
“switch”,
“while”,
NULL
};
方案一,使用二维数组来存储这些字符,方案二,使用指针来存储。从效率上来看,方案一使用数组来存储要低些,因为它每行长度都被固定为刚好能容纳最长的关键字。但是,它不需要任何指针。另一方面,指针数组本身也要占用空间,因为每个字符串常量所占的内存空间是它本身。
方案的选择,取决于你所存储字符串的特点。如果你要存储的字符串长度都差不多,那么使用数组形式会更紧凑一些,也无需使用指针。但如果每个字符串的长度都千差万别,或更糟的是,大多数字符串都很短,只有少数的几个却很长时,使用指针形式就会更紧凑些。而实际上,除非非常巨大的表,否则这些差别非常小,所以通常根本不重要。