1.char *b = const char *a 会报警告,而const char* a = char *b不会,要使这种赋值形式合法,必须满足下列条件之一:
1)两个操作数都是指向有限定符或无限定符的相容类型的指针
2)左边指针指向的类型必须具有右边指针所指向类型的全部限定符
警告内容:initialization discards ‘const’ qualifier from pointer target type [enabled by default]
(C专家编程:1.9节)
2.对有符号数转换为无符号数的例子和解决办法
来看一段代码:
int main()
{
int d = -1;
unsigned int a = 3;
if(d < a)
{
printf("d is low
");
}
}
这段代码在运行后毫无反应,说明程序没有进入if判断语句,即if判断的返回值总是0
原因显而易见,因为d被转换为无符号整数,会变得很大,这样永远都大于a,
解决的办法是
if(d < (int a))
(C编程专家:1.10节)
3.switch-case语句中break的用法
switch(flag) { case 0: { if() { ... break; }
...
} }
上面代码本意是如果进入if语句,执行一段代码后跳出if语句,执行case 0后面的语句,但是这里的break直接跳出了switch,它匹配的是 case 0。
(C专家编程:2.2.1节)
4.多行信息
ANSIC有一个特性是相邻的字符串常量将被自动合并成一个字符串,省掉了在书写多行信息时必须在行末加“”的做法
旧格式:
printf("hello world");
新格式:
printf("hello" "world");
除了最后一个字符串外,其余每个字符串末尾的' '字符都会被自动删除
但是要注意的是,如果在初始化一个字符串数组的时候,每个字符串末尾都要加逗号区分,不然一不小心就会将两个字符串连接
(C专家编程:2.2.2节)
5.运算符优先级的结合使用
当按照常规理解运用运算符时,他们之间的优先级可能会导致代码执行错误,例如:
1) == 和 != 高于位操作符
a&b != 0
其本意可能是
(a&b) != 0
但实际意义是
a& (b != 0)
2)算术运算高于移位运算符
a << 4 +b
的实际意义是
a << (4 + b)
3)逗号运算符在所有运算符中优先级最低
i = 1, 2
这种情况很容易认为i = 2
但实际i = 1,而逗号后执行常量为2的运算,计算结果丢弃
而如果是
i = 1, i = 2;
则结果是i = 2
(C专家编程:2.3.2节)
个人认为:避免这些情况发生的最好措施是加括号,同时书写“美观”的代码
6.操作符的结合性
我们知道优先级不同的操作符,优先执行优先级比较高的那个,但是如果相同优先级的操作符,需要考虑它们的结合性,例如:
int a, b = 1, c = 2; a = b = c;
上述代码的结果是a = 2,因为所有的赋值符都具有右结合性,也就是说表达式中最右边的操作最先执行,然后从右到左依次执行
类似的,具有左结合性的操作符(如位操作符 & 和 |)则是从左至右依次执行
(C专家编程 2.3.2节)
7.最大一口策略
z = y +++ x;
ANSI C规定了“maxxmal munch strategy”(最大一口策略),这种策略表示如果下一个标记有超过一种的解决方案,编译器将选取能组成最长字符序列的方案。所以上述代码的结果是:
z = y++ +x;
(C专家编程 2.4.2节)
8.函数局部变量被释放
char *fun() { char a[10]; ... return a; }
上述代码返回一个局部数组a,其实在函数fun执行完之后a已经被释放,所以得到的可能是不符合预期的结果。解决这种类型问题的办法是:
1) 返回一个指向字符串常量的指针
char *func(){return "hello world";}
这种方法很简单,但是比较难计算字符串的内容,此外如果字符串常量存储于只读内存区,也无法改写它
2) 使用全局声明的数组
将数组a声明为全局变量,缺点在于任何人都有可能在任何时刻修改这个数组,而该函数的下次调用也会覆盖该数组的内容
3) 使用静态数组
这样做的缺点是函数的下一次调用将覆盖这个数组的内容
4) 显式分配一个内存:char *a = malloc(10);
缺点是需要程序员做好内存的申请和释放,如果不释放的话,频繁的调用函数将会造成“内存泄漏”
(C专家编程 2.4.4节)
9.typedef和define的区别
1) 可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做
#define peach int unsigned peach i; /*没问题*/
typedef int banana; unsigned banana i;/*错误,非法*/
2) 在连续几个变量的声明中,用yupedef定义的类型能够保证声明中所有 的变量均为同一类型,而用宏定义无法保证
#define int_ptr int* int_ptr a, b;
在预处理后,第二行变为
int *a, b;
这样a和b为不同类型,a为指向int类型的指针,b为int类型的变量。而
tyoedef int* int_prt int_prt a, b;
用typedef声明后,a和b的类型相同,都是指向int类型的指针。
(C专家编程 3.6节)
10.指针与数组
1) 定义为指针,但是以数组方式引用
char *p="Hello workd!";
char p[10]="Hello world!";
当对以上两种声明分别取p[6]时,两种情况下都能取得字符w,但是其执行路径其实完全不一样。其执行步骤为:
a.取得符号表中p的地址,提取存储于此处的指针
b.把下标所表示的偏移量与指针的值相加,产生一个地址
c.访问上面这个地址,取得字符
注:如果是其他的类型int,double等,则步长会不一样
2) 数组与指针的区别
指针 数组
保存数据的地址 保存数据
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。
如果指针有一个下表[I],就把指针的内容加上I作为地址,从中提取数据。 直接访问数据,a[I]就是简单的以a+I为地址取得数据。
通常用于动态的数据结构 通常用于存储固定数目且数据类型相同的元素;
相关的函数:malloc(),free() 隐式分配和删除
通常指向匿名数据 自身即为数据名
3) 字符串常量初始化
char *p="Hello workd!"; char p[10]="Hello world!";
以上两种方式都可以初始化一个字符串,但是一般编译器认为前者创建的存储在只读区,只允许读取,而后者创建的则可以被修改
(C专家编程 4.3~4.5节)
11.动态库的好处
1) 使软件不必因新版本函数库或操作系统的发布而重新链接
2) 提高系统的总体性能
a.动态链接可执行文件比功能相同的静态链接可执行文件的体积小
b.所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝,而不是静态链接那样每个链接都独自产生一个拷贝至内存中
(C专家编程 5.2节)
12.函数库链接的5个秘密
1) 动态库扩展名是.so,静态库是.a
2) 例如,如果通过-lthread选项,告诉编译器链接到libthread.so
3) 编译器期望在确定的目录找到库(可以使用-Lpathname告诉编译器其他目录,也有环境变量LD_LIBRARY_PATH和LD_RUN_PATH来告诉编译器这类信息,处于安全考虑,一般不在环境变量中直接设置,而是在链接时使用-Lpathname和-Rpathname)
4) 观察头文件,确认所使用的函数库
5) 与提取动态库的符号相比,静态库的符号提取方法限制更严
注:nm命令可以查看用到的符号;可以在ld程序中使用-m命令,让连接器生成一个报告,包括被Interpositioning的符号的说明;使用ldd命令,可以查询可执行文件所依赖的动态链接库
(C专家编程 5.3节)
13.虚拟内存
1)让程序受安装在机器上的物理内存数量的限制是非常不便的。虚拟内存的基本思路就是用廉价但缓慢的磁盘来扩充快速却昂贵的内存。在任一给定时刻,程序实际需要使用的虚拟内存区段的内容就被载入物理内存中。
当物理内存中的数据有一段时间未被使用,它们就可能被转移到硬盘中,节省下来的物理内存空间用于载入需要使用的其他数据。
2)内存管理硬件负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的真正内存中。应用程序程序员只看到虚拟地址,并不知道自己的进程在磁盘和内存之间来回切换,除非他们观察运行时间或者查看诸如“ps”之类的系统命令。
如果该进程可能不会马上运行(优先级低或者睡眠),操作系统可以暂时取回所有分配给它的物理内存资源,将该进程的所有相关信息都备份到磁盘上。
(C专家编程 7.3节)
14.内存泄漏
1)堆常见问题
a.内存损坏,释放或改写仍在使用的内存
b.内存泄漏,未释放不再使用的内存
内存泄漏:进程一边分配越来越多的内存,一边却并不释放不再使用的那部分内存
2)检测内存泄漏
a.使用swap命令观察还有多少可用的交换空间,如果发现可用的交换区越来越少,则可能发生了内存泄漏
b.使用“ps -lu 用户名”命令来显示所有进程的大小,如果某个进程(SZ)不断增长而从不缩小,则可能发生的内存泄漏
(C专家编程 7.6节)
15.总线错误
总线错误几乎都是因为未对齐的读或写造成的。
对齐的意思就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。例如:
访问一个8字节的double数据时,地址只允许是8的整数倍。
下面这段代码:
union { char a[10]; int i; } u; int *p = (int*)&(u.a[1]); *p = 17;
因为int类型是按4字节对其的,而char数组a+1的地址没有按int对齐,所以会报总线错误
(C专家编程 7.7节)
16.指针数组初始化
指针数组不能由非字符串的类型直接初始化:
int *a[] = {/*无法编译成功*/ {1, 2, 3, 4, 5}, {2, 3, 4} }
如果想用这种方式进行初始化,可以创建几个单独的数组,然后用这些数组名来初始化原先的数组。
int a1[] = {1, 2, 3, 4, 5}; int a2[] = {2, 3, 4}; int *a = { a1, a2 }
(C专家编程 9.6)
17.向函数传递数组
1)增加一个额外的参数,表示元素的数目
2)赋予数组最后一个元素一个特殊的值,提示它时数组的尾部(字符串结尾的‘