《C陷阱和缺陷》读书笔记-其二:语义陷阱

第三章 语义陷阱

1、指针和数组

(1)对于数组:

C语言中只有一维数组,且数组的大小必须在编译器间就作为一个常数确定下来,数组的元素可以是任何类型的对象;

对数组的操作只有两个,确定数组的大小,以及获得指向该数组下标为0的元素的指针。

(2)如果一个指针指向数组中的一个元素,对指针加一就可以获取指向数组中下一个元素的指针,对指针减一可以获取指向该数组中前一个元素的指针,加减n类似。针对指向数组元素的指针的加减,实际上是移动对应元素的指向。

(3)数组名通常可以认为是数组中下标为0的元素的指针,例外情况是在sizeof(a)中,其结果是整个数组的大小,而不是单个元素的大小,因此,常用以下表达式求取数组长度sizeof(a) / sizeof(a[0])

(4)数组名a可以表示数组下标为0的元素的指针,则数组下标为0的元素的值可以用*a来表示;又因为数组指针可以加减,则a + 1表示下标为1的元素的指针,a + n表示下标为n的元素的指针(n小于数组长度);进一步,数组下标为1的元素的值为*(a + 1),数组下标为n的元素的值为*(a + n),通常简写为a[n]。

(5)对于二维数组,如int calendar[12][31];,calendar是一个有着12个数组类型元素的数组,每个数组类型元素又是一个有着31个整型元素的数组;可以理解为,每31个整数构成的一个数组,合并成一个大元素,12个这样的元素组合成一个新的数组,即二维数组:

calendar[0]: 30个整数组成一个大元素
calendar[1]: 30个整数组成一个大元素
calendar[2]: 30个整数组成一个大元素
calendar[3]: 30个整数组成一个大元素
calendar[4]: 30个整数组成一个大元素
calendar[5]: 30个整数组成一个大元素
calendar[6]: 30个整数组成一个大元素
calendar[7]: 30个整数组成一个大元素
calendar[8]: 30个整数组成一个大元素
calendar[9]: 30个整数组成一个大元素
calendar[10]: 30个整数组成一个大元素
calendar[11]: 30个整数组成一个大元素

个人对二维数组的理解,可以扩展到多维:
calendar[i]里面存放的是一个地址,该地址指向第i个大元素的首地址;
这个12个地址组成一个一维数组,而calendar是这个一维数组的首地址,因此,calendar表示指向第0个大元素的地址,由于指针的可加性,*(calendar + i)表示指向第i个大元素的地址,简写为calendar[i];
有了大元素的首地址,如果需要获取大元素中某个元素的值,可以用
(*(calendar + i) + j)表示,其中i表示第i个大元素,j表示对应大元素中的第几个元素,简写为calendar[i][j]。

(6)数组指针的应用:
如上,calendar表示存放第一个大元素地址的地址,即指向大小为31的int数组的指针;
定义如下变量:int (*monthp)[31];
monthp是一个指针,该指针指向的是一个大小为31的int数组,与calendar意义相同,因此可以这样写:
monthp = calendar; 或者 monthp = calendar[i];

(7) 字符串

字符串常量代表了一块包括字符串中所有字符以及一个空字符('\0')的内存区域的地址,所以字符串常量本质上是一个地址。

字符串以空字符'\0'作为结束标志,但是库函数strlen返回参数中字符串所包括的字符数目时并不计算这个空字符串;因此需要分配空间时,需要特别注意为末尾空字符分配一个字符空间。

(8)数组作为入参
将数组名作为参数传递时,C语言会自动将作为参数的数组声明转化为相应的指针声明,即指向该数组第一个元素的指针;
int strlen(char s[])会被退化成int strlen(char *s)

2、不对称边界

(1)数组元素下标从0开始;

(2)不对称边界通用原则:
其一:首先考虑最简单情况下的特例,然后将得到的结果往外推;
其二:仔细计算边界,绝对不能掉以轻心。

(3)数组中实际不存在的“溢界”元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较,但是不能引用。

3、整数溢出

如果算术运算符的一个操作数时有符号整数,另一个是无符号整数,那么有符号整数会被转换成无符号整数,不会发生“溢出”;如果两个操作数都是有符号整数,“溢出”就有可能发生;

当发生整数溢出时,做出任何假设都是不安全的。

4、其他

(1)指针与指针所执行的数据有区别:指针是指向一个区域的起始地址,而对应的数据是该区域的内容,其内容长短指针无法确定,但是其内容的数据类型必须与指针的类型相匹配;且通过指针的加减访问的范围也仅局限于该区域,否则会发生不可预期的错误,即指针越界。

(2)将常数0强制转换成指针,不等于任何有效的指针,即我们常用的空指针(#define NULL 0);
此外,将其他整数转换为一个指针,得到的结果依赖于编译器的实现。

(3)求值顺序:&& 和 || 首先对左侧操作数求值,只有在需要时才对右侧进行求值;三目运算符?: 根据判断条件,再确定求取哪一个表达式的值;逗号表达式,先对左侧操作数求值,然后该值丢弃,继续对右侧求值;

赋值运算符不保证任何求值顺序。

(4)建议给main函数添加返回值,0代表程序执行成功,非0表示程序执行失败。

posted @ 2021-06-22 00:53  Pangolin2  阅读(76)  评论(0编辑  收藏  举报