继续分享读书笔记,精简笔记可做提醒,此部分读完5min。
C语言进阶
三、语义陷阱
1、 指针与数组
1)C语言只有一维数组,但是数组内可以放任何类型的对象,当然也可以放进去一个数组。
2)对于数组我们只能做两件事:确定数组的大小,以及获得指向该数组下标为0的元素的指针,其他的有关数组的操作,实际上都是通过指针进行的。
理解数组声明:
int a[3];
int *p;
struct {
int p[4];
double x;
}b[17];
int cal[12][31]; cal[4]含义是什么? p=cal[4]; p的含义是什么?
int *p ; p = cal; 非法
int (*p)[31]; p = cal; 合法
int *p ; int i; p=&i; 合法
int *p ; int i[3] p= i; 合法
int *p ; int i[3] p=& i; 在ANSI C中不合法
2、 非数组的指针
C语言以空字符(’\0‘)作为字符串常量的结束标志,将字符串s和r连接成一个字符串,标准操作为char *r ;
r =malloc(strlen(s)+strlen(t)+1);
if(!r)
{
exit(1);
}
strcpy(r,s);
strcat(r,t);
free(r);
3、 作为参数的数组声明
char hello[]=”hello“;
传参 hello 等价于 &hello[0]
将数组作为参数传参时
函数strlen(char s[ ])等价于strlen(char *s)
但作为定义声明char s[ ] 与 char *s 截然不同。
4、 避免”举隅法“
char *p,*q;
p=”xyz”;
q=p;
q[1] = ? q[1] = ‘y’;
不要混淆指针与指针指向的数据,复制指针时数据并不复制。
5、 空指针并非空字符串
将0赋值给指针变量时,绝对不能使用该指针指向的内存中存储的内容。
if(p==(char ) 0) 合法
if(strcmp(p,(char)0)==0) 非法
6、 边界计算与不对称性
注意数组下标与数组大小。
7、 求值顺序
|| 首先对左侧操作数求值,若为1,则不对右侧求值,若为0,则对右侧求值。
&& 对左求值,若为0,则不对右侧执行,若为1,则对右侧求值。
a ? b:c 三目运算符,若a为真,则执行b,若为假,则执行C。
8、 运算符&&、|| 和 !
按位运算符&、|、~位运算
逻辑运算符&&、||、!逻辑运算
9、 整数溢出
两个无符号整数运算不存在溢出,一个有符号整数,一个无符号整数运算,将有符号整数转换为无符号整数,也不会发生溢出,只有两个有符号整数运算时才有可能溢出,且溢出的结果是未知的。
例如去检查溢出:if(a+b <0)
complain( );
如果a+b发生溢出,那么if的检查就会失败,因为加法运算将设置一个内部寄存器的四个状态为:正、负、零、溢出。
正确的方式是将a与b都要转换为无符号整数,INT_MAX代表可能的最大整数值
if((unsigned) a+(unsigned)b >INT_MAX)
complain( );
10、 为函数main提供返回值
main 函数执行完后需要增加返回值。
四、 连接
1、 什么是连接器
C语言中的重要思想是分别编译,若干个源程序可以在不同的时候单独进行编译,连接器将若干个C源程序合并为一个整体。该整体能够被操作系统直接执行。如果C语言实现中提供了lint程序,一定要使用,可以检测出很多错误。
2、声明与定义
int a; 如果未初始化,编辑器应该默认初始化为0,(有些编辑器不能保证)
extern int a;外部引用别的地方的定义的int a;
如果在不同的源文件中定义了同一个变量,并各指定一个初始值,例如一个文件中指定int a=7,另一个文件中定义int a =9,这个时候外部引用时,大多数系统会拒绝接受该程序,如果有多个定义但未初始化,一些系统会接受。最好的解决办法是,每个外部变量只定义一次。
3、命名冲突与static修饰符
1)避免与库函数命名冲突
2)static可以将变量的作用域限制在一个源文件中,以减少命名冲突。,同样 static可以修饰函数,效果一样。
4、形参、实参与返回值
函数的形参有无都行,如果函数的形参列表为空,那么被调用时实参列表也为空。
任何C函数都有返回值,任何函数在调用它的每个文件中,都在第一次被调用之前进行声明或定义,就不会有任何与返回类型相关的麻烦。
要注意形参与实参数据类型的一致。
5、 检查外部类型
如果一个源文件中声明 extern int n; 另一个源文件中却是 long n;这样会导致什么问题?
1)如果编译器足够强大,可以检测出来这种错误。
2)如果是32位计算机,可能能正常工作,但是这种写法绝对是错误的
3)虽然两个实例的存储空间大小不同,但是却共享存储空间的方式能够满足,赋给其中一个的值,对另一个也有效,错误的程序因为某个巧合可以工作。
4)两个变量n共享存储空间的方式,对其中一个赋值,等同于给另一个赋了完全不同的值,程序不能正常工作。
6、 头文件
为了避免上面的错误,我们遵循一个简单的规则:每个外部对象只在一个地方声明。这个地方就是在一个头文件中,定义该外部对象的模块也应该包括这个头文件。例如:
在file.h中包含声明 extern char filename[ ];
如果其他外部c源文件中需要用到filename时,只需要在C文件中添加
#include “file.h”
C陷阱与缺陷(读书笔记一)
C陷阱与缺陷(读书笔记三)
C陷阱与缺陷(读书笔记四)
C陷阱与缺陷(读书笔记总)