分享

《C专家编程》总结

 unix89 2013-12-11

 开始读《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号元素的指针,即在函数内部得到的是指针,而不是数组名
基于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: 计算一个很长的表达式时,会把部分结果先压到堆栈中

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多