第八章 为什么程序员无法分清万圣节和圣诞节
(1)printf("%d", sizeof 'A'); 该语句打印的结果为4(机器上int的长度),字符常量的类型是int,根据提升规则,它由char转换为int。
“在表达式中,每个char都被转换为int...注意所有位于表达式中的float都被转换为double...由于函数参数也是一个表达式,所以当参数传递给函数时也会发生类型转换。具体地说,char和short转换为int,而float转换为double。”
——The C Programing language,第一版
整型提升就是char、short int和位段类型(无论signed或unsigned)以及枚举类型将被提升为int,前提是int能够完整地容纳原先的数据,否则将被转换为unsigned int。
真正值得注意之处——参数也会被提升!
(2)建立原型就是为了消除一种普通(但很难发现)的错误,就是形参和实参之间类型不匹配。如果使用了函数原型,缺省参数提升就不会发生。
第九章 再论数组
(1)数组声明可以分为三种情况:
1. 外部数组(external array)的声明;
2. 数组的定义(记住,定义是声明的一种特殊情况,它分配内存空间,并可能提供一个初始值);
3. 函数参数的声明。
(2)数组与指针的等同
所有作为函数参数的数组名总是可以通过编译器转换为指针。
对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。
通用规则:当一个数组名出现在一个表达式中时,就会被转换为一个指向该数组第一个元素的指针。
什么时候数组和指针是相同的:
规则1. 表达式中的数组名(与声明不同)被编译器当做一个指向该数组第一个元素的指针;
规则2. 下标总是与指针的偏移量相同;
规则3. 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。
在表达式中,指针和数组时可以互换的,因为它们在编译器里的最终形式都是指针,并且都可以进行取下标操作。
编译器自动把下标值的步长调整到数组元素的大小。对起始地址执行家法操作之前,编译器会负责计算每次增加的步长。
不论是指针还是数组,在连续的内存地址上移动时,编译器都必须计算每次前进的步长。计算的方法是偏移量乘以每个数组元素占用的字节数,计算结果就是偏移数组起始地址的实际字节数。
标准规定的“类型的数组”的形参的声明应该调整为“类型的指针”。在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。
(3)C语言把数组形参当做指针
在C语言中,所有非数组形式的数据实参均以传值形式(对实参作一份拷贝并传递给调用的函数,函数不能修改作为实参的实际变量值,而只能修改传递给它的那份拷贝)调用。
函数的返回值绝对不能是一个函数或数组,而只能是指向数组或函数的指针。
数据也可以使用传址调用,只要在它前面加上取地址操作符&,这样传递给函数的是实参的地址而不是实参的拷贝。事实上,取地址操作符的主要用途就是实现传址调用。
在函数内部使用指针,所能进行的对数组的操作几乎跟传递原原本本的数组没有差别,只不过,如果想用sizeof(实参)来获得数组的长度,所得的的结果不正确而已。
数组名是不可修改的左值,它的值是不能改变的。
可以通过向函数传递一个指向数组第一个元素的指针来访问整个数组,但也可以让指针指向任何一个元素,这样传递给函数的就是从该元素之后的数值片段。
(4)数组和指针可交换性的总结
1. 用a[i]这样的形式对数组进行访问总是被编译器“改写”或解释为像*(a+i)这样的指针访问。
2. 指针始终就是指针,它绝不可以改写成数组。可以用下标形式访问指针,一般都是指针作为函数参数时,而且知道实际传递给函数的是一个数组。
3. 在特定的上下文中,也就是作为函数的参数(也只有这种情况),一个数组的声明可以看作室一个指针。作为函数参数的数组(就是在一个函数调用中)始终会被编译器修改成为指向数组第一个元素的指针。
4. 当把一个数组定义为函数的参数时,可以选择把它定义为数组,也可以定义指针,不管选择哪种方法,在函数内部事实上获得的都是一个指针。
5. 在其他所有的情况中,定义和声明必须匹配。如果定义了一个数组,在其他文件对它进行声明时也必须把它声明为数组,指针也是如此。
(5)像[i,j,k]这样的下标形式(由逗号分隔)是C语言的合法的表达形式,只是它并非同时引用这几个下标(实际引用的下标是k,也就是逗号表达式的值)。
(6)只有字符串常量可以初始化指针数组,指针数组不能由非字符串的类型直接初始化:
char *vegetables[] = { "carrot",
"celery",
"corn"
}; /*没问题*/
int *wights[] = {
{1, 2, 3, 4, 5},
{6, 7},
{8, 9, 10}
}; /*无法成功编译*/
如果想用这种方法对数组进行初始化,可以创建几个单独的数组,然后用这些数组名来初始化原先的数组。
int row_1[] = {1, 2, 3, 4, 5, -1}; /*-1是行结束标志*/
int row_2[] = {6, 7, -1};
int row_3[] = {8, 9, 10, -1};
int *weight[] = {
row_1,
row_2,
row_3
};/*OK*/