读C陷阱与缺陷笔记

1.字符与字符串

(1)用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。

eg:对于采用ASCII字符集的编译器而言,'a'的含义与0141(八进制)或者97(十进制)严格一致。

(2)用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化。

eg:printf("Hello world\n");与char hello[] = {'H','e','l','l','o',' ','w','o','r','l','d','\n',0}; printf(hello);相一致。

2.函数声明

(1)任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符从表面上看与表达式有些类似,对他求值应该返回一个声明中给定类型的结果。

eg: float f;  float *pf;  float *g(); float (*h)();

(2)类型转换符:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。

eg:  声明:float (*h)();

           类型转换符:(float (*)())

(3)函数指针: float (*fp)();                         调用:  (*fp)();

(4)将常数0转型为“指向返回值为void的函数的指针”类型: (void (*)())0

(5)获取变量的类型:只需要在变量声明中将变量名去掉即可

3.优先级

(1)任何一个逻辑运算符的优先级低于任何一个关系运算符

(2)移位运算符的优先级比算术运算符要低,但是比关系运算符要高

4.函数调用

(1)C语言要求:在函数调用时即使函数不带参数,也应该包括参数列表。

eg:f();

5.指针与数组

(1)C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,C语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。

(2)对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。

(3)声明一个数组:int a[3];

(4)任何指针都是指向某种类型的变量。

eg:int *ip;   表明ip是一个指向整型变量的指针

eg:int calendar[12][31];

       int (*monthp)[31];

       monthp = calendar;

       int *dayp;

      dayp = *monthp;

(5)在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符(‘\0’)的内存区域的地址。

(6)在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。因此,将数组作为函数参数毫无意义。所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。

(7)复制指针并不同时复制指针所指向的数据。

(8)出于代码文档化的考虑,常数0这个值经常用一个符号来代替:#define NULL 0

(9)当常数0被转换为指针使用时,这个指针绝对不能被解除引用。换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。

(10)“栏杆错误(差一错误)”的解决技巧:用第一个入界点和第一个出界点来表示一个数值范围。

入界点:包括在取值范围之中

出界点:不包括在取值范围之中

eg:x>=16  且  x<38   16为入界点,38为出界点  长度即为:38-16=22

常用例子:

建议的做法: int a[10],i;

                       for (i = 0; i < 10; i++)

                            a[i] = 0;

不建议的做法:int a[10], i;

                        for (i = 0; i <= 9; i++)

                            a[i] = 0;

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

6.声明与定义 

(1) int a; 如果其位置出现在所有的函数体之外,那么它就被称为外部对象a的定义。

(2)extern int a; 这个语句说明a是一个外部整型变量,但是它包括了extern关键字,这就显示地说明了a的存储空间是在程序的其他地方分配的。

(3)建议:每个外部变量只定义一次

7.static int a; a的作用域限制在一个源文件中,对于其他源文件,a是不可见的。

8.为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,我们就应该声明该函数为static。

9.保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。

eg:(1) char filename[] = "/etc/passwd";

             extern char filename[];

       (2) char* filename = "/etc/passwd";

             extern char* filename;

10.getchar函数在一般情况下返回的是标准输入文件中的下一个字符,当没有输入时返回EOF(一个在头文件stdio.h中被定义的值,不同于任何一个字符)。

11.程序输出有两种方式:一种是即时处理方式,另一种是先暂存起来,然后在大块写入的方式,前者往往造成较高的系统负担。因此C语言一般是通过库函数setbuf来控制写操作之前的输出数据量。

12.在调用库函数时,我们应该首先检测作为错误指示的返回值,确定程序执行已经失败,然后,在检查errno,来搞清楚出错原因。

13.建议:在宏定义中最好把每个参数都用括号括起来,同样,整个结果表达式也应该用括号括起来。

14.建议:相比起宏定义,建议使用typedef来定义

15.null指针并不指向任何对象。因此,除非是用于赋值或比较运算,出于其他任何目的使用null指针都是非法的。

16.调用malloc(n)将返回一个指针,指向一块新分配的可以容纳n个字符的内存,编程者可以使用这块内存。把malloc函数返回的指针作为参数传入给free函数,就释放了这块内存,这样就可以重新利用了。调用realloc函数时,需要把指向一块已分配内存的区域指针以及这块内存新的大小作为参数传入,就可以调整(扩大或缩小)这块内存区域为新的大小,这个过程中有可能涉及到内存的拷贝。

猜你喜欢

转载自blog.csdn.net/swif_N_F/article/details/78956406