第三章 语义陷阱
3.1指针与数组
C语言的值得注意的两点
1.C语言只有一堆数组,而且数组的大小必须在编译期就作为一个常数确定下来。因为数组中的元素可以是任何类型的对象,因此也可以是另一个数组。所以多维数组就是这样“仿真”来的。
2.对于一个数组,我们只能够做两件事:确定数组的大小,以及获得指向该数组下标为0的元素的指针。其它有关数组的操作都是通过指针进行的。
*a是数组a中下标为0的元素的引用。*(a+1)即是数组a中下标为1的元素的引用。*(a+i)即数组a中下标为i的元素的引用。而a[i]是上面写法的简记。因为a+i和i+a的含义一样,所以a[i]和i[a]是同一种含义!
对于二维数组 int a[10][13],a则是“数组的数组”。声明指向a的指针的话需要这种形式:int (*p)[13]; 但对于二维数组最好还是使用下标的形式!
3.2非数组的指针
在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符(‘\ 0’)的内存区域。因为C语言要求字符串常量以空字符作为结束标志。
strlen函数返回的字符个数并未包括空字符,因此在用malloc和strlen结合分配内存时需要加上一个空字符。类似这种状况:
malloc(strlen(s)+1);
3.3作为参数的数组声明
将数组作为函数参数毫无意义,C语言会自动地将作为参数的数组声明转换为相应的指针声明。
int strlen(char s[]){ }与 int strlen(char *s){ }写法相同!
int main(int argc,char **argv){}与int main(int argc,*argv[])等价,但前者强调argv是一个指向某数组的起始元素的指针,该数组的元素为字符指针类型。
3.4避免“举隅法”
举隅法(synecdoche)以含义更宽泛的词语来代替含义相对较窄的词语,或者相反。
名词就解释完了,这里只要记住一条:复制指针并不同时复制指针所指向的数据!
3.5空指针并非空字符串
//这节好像没什么东西好记!
#define NULL 0
3.6边界计算与不对称边界
关于边界问题也是我经常犯糊涂的问题,今天看了有些收获!
用第一个入界点和第一个出界点来表示一个数值范围。下界是入界点,包括在取值范围之中;上界是出界点,不包括在取值范围之中。这叫不对称边界:
1.取值范围的大小就是上界与下界之差。
2.如果取值范围为空,那么上界等于下界。这是第一条的直接推论。
3.取值范围为空,上界也永远不可能小于下界。
对于问题:整数x满足边界条件x大于等于16且x小于等于37,那么此范围内x的可能取值的个数是多少?//此博客中好像不能出现小于号,因此用文字代替。
下界是16,包含中!上界是38,38是不包含在其中的。因此用上界减去下界38-16=22 因此可得出答案是22。方便快捷!
对于数组buffer[N],&buffer[N]是一个地址,C语言中引用这个无效元素的地址是允许的,可利用于本例中的不对称边界原则,但引用该元素则是非法的! //这个不对称边界原则在STL中也有应用!
3.7求值顺序
C语言中只有四个运算符(&& || ?: ,)存在规定的求值顺序。而其它运算符对其操作数求值的顺序是未定义的,特别地,赋值运算符并不保证任何求值顺序。因此,在编程中不要对求值顺序作假设!
3.8运算符&&,|| 和 !
3.9整数溢出
注意有符号数的计算!
3.10为main函数提供返回值
这个不用说了,gcc已经明确要求main要返回值!