不积硅步无以至千里
分类: C/C++
2012-01-02 09:27:52
第三章 语义陷阱
在单词拼写正确和语法正确的情况下,存在着一些容易引起歧义的程序书写方式。
3.1 指针与数组
C语言中关于数组值得注意的两点:
(1) C语言中只有一维数组,而且数组的大小在编译期就作为一个常数确定下来。C语言中的数组元素可以是任何类型的对象。
(2) 对于一个数组我们可以做两件事情:确定数组的大小,获得指向该数组下标为0的元素的指针。对数组元素的操作,其实都可以看作是对指针的操作。
任何指针都是指向某种类型的变量。
给一个指针加上一个整数与给该指针的二进制表示加上整数有着截然不同的概念。
如果a是一个含有三个整型变量的数组,p是一个指向整型变量的指针,则如下语句是非法的,p=&a。原因是,&a是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。
除了被用作sizeof函数的参数这一情况外,a始终代表着指向a数组中下标为0的元素的指针。
如有二维整型数组calendar[12][31],指向整型数据的指针p,则p=calendar语句是非法的。原因如下:calendar是数组的数组,使用calendar表示一个指向数组的指针,而p却是一个指向整型变量的指针,二者类型不一致。
以下是一种声明指向数组的指针的方法:int (*ap)[31],这个语句的实际效果是声明一个含有31个元素的数组*ap,因此ap就是指向这个数组的指针。
3.2 非数组的指针
C语言中,字符串常量代表了一块包含了所有字符和一个空字符(‘\0’)的内存区域。假定我们有两个字符串s和t,我们希望将这两个字符串连接成一个字符串,则应使用以下代码:
从上述代码中可以得知以下三点:
(1) 因为字符串需要一个结束字符’\0’,因此使用malloc时需要分配strlen(s)+strlen(t)+1个内存单元;
(2) malloc有可能不能够提供足够的内存单元,此时该函数将会返回NULL;
(3) 因为使用malloc显式地分配了内存空间,所以需要在使用完成之后显式地释放内存空间。
3.3 作为参数的数组声明
C语言中无法将数组作为函数参数进行传递。如果我们使用数组名作为参数,那么数组名会立即转化为指向该数组第一个元素的指针。所以如有char hello[]; 则
与
完全等价。
3.4 避免“举隅法”
指针与指针指向的数据有着本质的不同。复制指针并不等同于复制指针所指向的数据。
3.5 空指针并非空字符串
当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向内存单元中的内容。
3.6 边界计算和不对称边界
C语言中数组下标从0开始。如有int a[10],有以下循环赋值语句
以上循环将会成为一个死循环。原因是数组中不存在元素a[10],在i=10的时候,a[10]=0,表示着a之后的第一个字的值为零。如果编译器按照内存地址递减的方式来分配变量,则a[i]=0即意味着i的值为0,也就是程序将陷入死循环。
取值范围的大小就是上界与下界之差。
如果取值范围为0,那么上界与下界相等。
即使取值范围为空,上界也不可能小于下界。
重复执行表达式—n>=0只是进行n次迭代的一种方法。
buffer[N]并不存在,数组buffer的下标从0到N-1,我们用下面的写法
代替
原因在于我们坚持“不对称边界”的原则,我们要比较指针bufptr与缓冲区后第一个字符的地址,而&buffer[N]正好是这一地址。我们并没有引用一个并不存在的元素,而只是引用这个“元素”的地址。这在C语言中是被允许的。
3.7 求值顺序
考虑下面的表达式:
如果a确实小于b,此时必须进一步对c
C语言中只有四种运算符(&&,||,?:和,)存在规定的求值顺序。
3.8 运算符&&、||和!
3.9 整数溢出
C语言中存在两类整数算术运算,有符号运算和无符号运算。在无符号运算中不存在“溢出”一说。但是,当两个整数都是有符号数时,“溢出”就有可能发生,而且“溢出”的结果是未定义的。当一个运算的结果发生“溢出”时,做出任何假设都是不安全的。
假设,a和b是两个非负整型变量,我们需要检查a+b是否会溢出,一种想当然的方式是:
这并不能正常运行,当a+b发生溢出时,所有关于结果的假设都是不可靠的。一种正确的做法是将a和b都强制转换为无符号整型数据。
此处的INT_MAX代表最大的整数值。ANSI C在
3.10 为函数main提供返回值
一个返回值类型为整数的函数如果返回失败,实际上隐含地返回了一个“垃圾”整数。
大多数C语言的实现都通过main的返回值来告知操作系统函数的执行是否成功。典型的处理方案是,执行成功则返回0,返回非0值则代表执行失败。
最为经典的“hello world”程序应该是这样: