自己找工作过程中复习过的书包括《C traps and Pitfalls》,《编程珠玑》,《编程之美》,《算法导论》,《Effective c++》,《c++ 沉思录》,另外的就是Linux我平时经常看的一些书了。所以这些书都是在我的书桌上,从来没有丢过。当然还有一本你永远都不可能撤下去的书叫做《深入理解计算机系统》,这个自然是不用提的。先从大概上面说一下自己对于这几本书的看法吧。因为在找工作的面试过程中,这些书可能没有像《程序员面试宝典》一样给你非常直接的帮助,但是其中所涉及的内涵,绝对是你在面试过程中能够自信满满的保证。
《C traps and Pitfalls》这本书是久负盛名的很薄的一本书,在阅读过程中,你从来都不想过要停下来,你总是迫不及待的想看看下一章到底写得什么自己还没有这样透彻的了结果。该书的作者是Andrew Koenig,也是《c++ 沉思录》的作者,出版了14年,总共印刷了18次,足以证明该书对于程序员的重要性。这中间有非常多的c语言编程中的一些重要的经验教训,这些都是那些容易导致人犯错的特性。大多数程序员在成长为C编程高手的道路上,范过得错误都是惊人的相似的,而且在一代一代的程序员中一再出现。
第一章:词法“陷阱”
首先是"=="和“=”的区别,我相信所有的c程序员都知道这个吧。然后是"&"和"&&"的区别,一个是位操作符,另一个是逻辑运算符。另一个鲜为人知的特性是c编译器中的特性,就是词法分析中的“贪心法“。比如“==”这样的,那么编译器会尽量读入更多的字符。比如a---b,表述的就应该是a -- - b;比如y=x/*b,那么/*就会被识别成注释。所以我们在学习c的第一堂课中,就反复强调编程风格的重要性,一个原因就在于这里。写过编译器的人都知道,词法分析是一个自动机的过程,所以不要将这些带有二意性的词语通过糟糕的编程风格表现出来。还有一个比较诡异的是我最近才身有体会的,就是十进制和八进制的写法区别:八进制前面多一个0。比如0755和755是不一样的,嘿嘿。
第二章:语法“陷阱”
函数声明问题,比如你要调用地址为0的系统启动代码,该如何做。那么可以为:(*( void ( * ) ()) 0 )()。首先C变量的声明由两部分组成:类型以及一组类似表达式的声明符。函数的声明类似,比如float f();,那么f()返回的就是float,那么f就是一个返回浮点数的函数。比如float *pf;,那么*pf是一个浮点数,pf就是一个指向浮点数的指针。还有一个需要理解的是函数指针问题,书上面给了一个非常给力的方法来做,也非常的方便。其实久了你就能够知道函数指针是怎么回事了比如float (*) ( float , float) 等等,这些都是一个骨架的。Linux下面IPC中有一个函数signal函数,用来注册各个信号的动作,声明就非常的复杂:void (*signal ( int , void (*)( int ) ) ) ( int ),一个返回函数指针的函数,参数是int和一个函数指针。当然定义宏就非常的方便了比如:typedef void (* HANDLER) (int);,那么HANDLER signal( int , HANDLER)。还有一个语法“陷阱”当然就是优先级了,这个我直接浏览了一下,因为我从来不去记这个东西。还有的就是switch中的break,和悬挂的else问题了。else会与最接近的一个if进行匹配。
第三章:语义“陷阱”
一个主要的问题就在于指针与数组的问题,其实明白三点就明白了。1,C语言中只有一维数组,而且大小必须在编译器确定。2,对于数组,我们只知道两个,数组大小以及指向0的地址。书上还举例说了很多的例子,都是非常典型的,但是我觉得只要能够理解这两条,指针与数组这个C中的倚天剑和屠龙刀能够很容易理解了。一个可能新手非常难于理解的事情是二维数组,也就是用一维数组的模拟。比如说int array[12][31],int *p,p = array[1]。对于这样的一个声明和赋值,其实理解很容易,首先array[1]表示的是31个元素的数组中的一个,那么array[1]表示的就是其中的着一个数组的首地址,然后赋值给p,p就指向了这个31个数组的第一个int元素。以此类推,int **p = array也是成立的,指向的是第一个元素。一个需要很注意的问题可能还在于对于C语言来说,数组作为参数的意义:那就是没有意义。所以C会自动的将作为参数的数组声明转换为相应的指针声明。这里的一个问题就是一个数组传递进去,变成了指针,那么sizeof就只能是指针了。还有一个可能需要很久的编程实践才能理解的一个问题就是C中数组的边界,从0开始,而且是[)形式的。这和C++中的迭代器非常的相似,这样带来的好处很多,比如计算数目什么的。而且在自己写程序比如二分中,只要坚持这个特性,就不会出现对下标不清晰的理解。还有一个在编程中非常纠结的问题就是短路问题,比如&&和三目运算符。这些都好理解,不好理解的是这些运算符所带来的++和--运算符的计算顺序问题。比如y[i++]=y[i]或者y[i]=y[i++],结果都是未定义的,根据不同的编译器。还有的比如将计算放在函数的参数位置,其结果也是未定义的。所以像这种二意性的语法,完全应该避免。在有符号的计算过程中,就存在”溢出“的问题。溢出是非常普遍的,比如在二分程序中,mid = ( a + b ) / 2,这个地方,a和b都是有符号整数则可能溢出,所以通常写成mid = b + ( a - b ) / 2。
第四章:链接
这个部分不论在哪本书中,都是我最喜欢的。其实这部分在《程序员的自我修养中》专本一大本书来进行了介绍,非常的深刻。这本书不是什么所谓的专家写得,而是几个真正的一线程序员写得,写得非常的到位,中国人还是能够写出好书的,前提是不要停在理论的高度。在《深入理解计算结系统中》,对于Linux的连接器的介绍也是非常的深刻,尤其是在编译器的预处理、编译成汇编文件、汇编成可重定位的目标文件。然后就轮到连接器了,可能在Linux下用Gcc你都很难能够体会代这个过程,写过Makefile的人就应该有所体会了。Gcc也会帮助你将这个过程自动化。可重定位的就是你在Gcc中加入-c命令编译出来的.o文件,但是这个虽然是二进制码,但是是不能执行的。要成为一个可执行的文件,还需要一步就是将这些.o文件连接起来。链接的一个很重要的工作就是符号解析,其实最主要的还是外部符号的解析。这个部分在《深入理解计算机系统》中有非常详细非常到位的解释。一个不可避免的问题就是所谓的静态链接和动态链接。静态链接就是将你用到的库中的某些需要的代码在链接过程中,拷贝到最终的目标文件中。这样生成的可执行文件在装载的时候直接运行,而不用了解到底用了什么库,但是不好的地方就是可执行程序会变得非常的大,而且不便于代码的共享。所以有了动态链接库,包括链接时动态和运行是动态链接。详细的介绍可以参考《深入理解计算机系统》。
第五、六、七章:库函数、预处理、可移植性。
这几个部分可能显得没有那么的重要,但是在预处理中的宏的部分讲解的非常的生动,举例也是非常的到位的,非常值得一看。我也是没事的时候总是找出这一章来细细的反复的读。
总结
总的来说,这本书没有辜负程序员的期望,从他的印刷版本就知道了。一本非常好的经验集合,每个程序员如果选择一本关于C的需要经常翻阅的书,那么这本书当之无愧了。