《C专家编程》总结
开始读《C专家编程》之前,有一个很担心的问题:94年出的讲语言的书,在现在(2012)还有多少是适用的。因此,一边读,一边用VS2010做实验。最后发现大部分内容都还在用。读完后,觉得最精彩的部分有二:一是讲解如何理解声明,二是深入地讲解数组名与指针。下文是将看书过程中所做的笔记进行的整理。
p.s: 以下代码均在VS2010测试过
1. 使用无符号数时要特别注意(不推荐使用无符号数)
当无符号数与有符号数在同一条表达式中出现时,有符号数会被转换为无符号数。e.g:
int feng = -1;
unsigned int yan = 5;
bool result = (feng < yan) ? true : false; //最后的结果会是false
原因是C语言在计算含有不同类型的表达式时,会将类型向上提升。在本例中,int被提升了unsigned int,从而使-1的补码被解析为很大的整数
2. 相邻的字符串常量会被自动合并成一个字符串
e.g:
char *str[] = {"feng" "yan", "zero"}; //"feng"和"yan"被合并成一个了:"fengyan"
3. 易出错的优先级
.高于* e.g: *p.f 正确的理解:*(p.f)
[]高于* e.g: int *ap[] 正确的理解:int *(ap[]) ap是个数组,其元素为int*
函数()高于* e.g: int *fp() 正确的理解:int* fp() fp是返回int*的函数
==和!=高于位操作符 e.g: val & mask != 0 正确的理解:val & (mask != 0)
==和!=高于赋值符 e.g: c = getchar() != EOF 正确的理解:c = (getchar() != EOF)
算术运算高于移位运算 e.g: msb<<4 + lsb 正确的理解:msb << (4 + lsb)
逗号运算符优先级最低 e.g: i = 1, 2 正确的理解:(i = 1), 2
4. 理解声明,定义,typedef语句的步骤
a. 找标识符
b. 找被括号括起来的部分
c. 找后缀操作符,如果是(),则表示是函数;如果是[],则表示是数组
d. 找前缀操作符,如果是*,则表示“指向XX的指针”
e. 找const和volatile,如果const,volatile后面紧跟类型(如int,long),那么它作用于类型,其它情况下,作用于它左边紧邻的项
e.g:
int const * zero; //zero是一个指针,指向一个常量整形
char (*zero)[20]; //zero是一个指针,指向一个有20个char元素的数组
typedef void (*ptr_to_func)(int); //ptr_to_func是新类型名,这种类型是一个函数指针,指向接收一个int参数,并返回void的函数
char* const * (*zero)(); //zero是一个函数指针,该函数无参数,并返回一个指针,返回的指针指向一个常量指针
char* (*zero[10])(int **p); //zero是一个数组,元素是函数指针,其指向的函数授受一个二维指针,并返回一个指向char的指针
void (*signal(int sig, void (*func)(int)))(int); //signal是一个函数,该函数接收一个int,一个函数指针,并返回一个函数指针
5. 左值与右值
左值通常表示存储结果的地方(地址),其值在编译时可知
右值通常表示地址的内容,其值通常要到运行时才知道
6. 指针与数组名不等同的情况(定义为数组,却声明为指针,或者反过来)
前提知识(假设有定义:int array[10], *ptr;):
a. 使用数组名下标访问(如:array[1]),会直接将数组名的地址加上偏移值作为变量的地址(即array[1]的地址)
b. 使用指针下标访问(如:ptr[1]),会先取指针指向的内容,然后将这个内容加上偏移值作为变量的地址(即ptr[1]的地址)
不等同的原因:
当定义为数组,却声明为指针时,相当于用指针下标访问的方法来解析一个数组名下标,即先取数组第0号元素的内容,然后将这个内容加上偏移值作为变量的地址,从而访问了不该访问的东西。反之亦然。
7. 指针与数组等同的情况
a. 编译器将表达式中的数组名当作指向该数组第0号元素的指针,下标当作指针的偏移量,即array[i]会被当作*(array + i)
b. 编译器将函数参数声明中的数组名当作指向该数组第0号元素的指针,即在函数内部得到的是指针,而不是数组名
b. 编译器将函数参数声明中的数组名当作指向该数组第0号元素的指针,即在函数内部得到的是指针,而不是数组名
基于a情况,可知这条谣言是假的(至少在一维数组中一定不成立):
用指针迭代数组比用下标迭代数组更快
基于b情况,可解释为什么在传递数组后,不能用以下方法计算数组长度
int ArrayLength(int arr[]) {
return sizeof(arr) / sizeof(arr[0]); //返回值必定是1,因为此时的arr是一个指针,而不是数组名
}
注意b情况的将数组改写为指针并不是递归定义的,e.g:
实参 char zero[10][10] 被改写为 char (*zero)[10],这里将数组的数组改写为数组的指针
实参 char *zero[10] 被改写为 char **zero,这里将指针数组改写为指针的指针
实参 cahr (*zero)[10] 不改变,因为此时的zero是指针,而不是数组
8. interposition
interposition指用户定义的函数取代库中声明完全相同的函数,注意这不是指重载,而是指下面这种:
void zero(); //user defined function
void zero(); //library function
出现interposition时,要特别注意以下情况:
void zero(); //user defined function
int main() {
zero(); //调用用户定义的函数zero,而不是库函数zero
FengYan(); //假设这是另一个库函数,并且函数内调用库函数zero,此时由于interposition,变成调用用户定义的zero
return 0;
}
备注:
出现interposition时,在VS2010会出现warning: inconsistent dll linkage
9. 堆栈段作用
a. 存储局部变量
b. 函数调用时,存储有关的维护信息
c. 用作暂时存储区。e.g: 计算一个很长的表达式时,会把部分结果先压到堆栈中