@(zhangxiaoya89的笔记本)
这篇笔记就起这么一个俗气的名字吧,《C陷阱和缺陷》这本书是去年买的,但是去年没有完全看完,只是看了前半部分,昨天才想起来,找出来,翻了翻发现没剩下多少了于是就打算昨天看完,但是昨天看另外一片文章入迷了,就没时间再继续完成这个了,刚刚吃过饭没事就把剩余的部分看完了,感觉没有去年那种神秘了。
刚刚看完,给我的感觉就是这本书在用一种大师的口气来教训我在学习和使用C语言编写程序过程中应该或者必须要注意的问题。也就是像书的封面写的“聆听大师的教诲,掌握编程精髓”。
从这本书中能学到什么?
虽然是刚刚看完书的后半部分,但是大半的内容已经忘记了,因为有些东西我并不打算现在就很认真的关系,至少是现在,当下我最主要的任务就是尽可能高效的掌握C语言和C++的比较基本,而且是实际工程项目广泛应用的知识,另外还有应对面试官那些我忽略的问题,比如虚函数实现机制、宏是个什么鬼。。。此类问题。对于平台移植相关,以及早期机器(16位机器、甚至是8位机器)我暂时不想化太多的时间仔细研究。
那就列举我在这本书中学到的东西把。
这本书的是想说什么?
有意思的是,这本书是从第0章开始,比较符合C语言的风格,哈哈。。。至少我是这样理解的。这一章节介绍了这本书要写的是什么东西。
文中的一句话我感觉定位还是比较准确的:
程序设计错误实际上是反映的程序与成员对该程序的”心智模式“两者的相异之处。
这本书要介绍的内容是我们在使用C语言时常犯的错误,引起这些错误的原因来自两部分:程序语言本身的缺陷和程序语言中不容易注意的错误。
这类的错误区别与通常由程序员逻辑思维造成的错误,换句话说,本书中讲到的错误通常不是程序员故意要犯的错误。就像上面的那句话,这类错误是程序语言对程序的理解和程序员对程序的理解不一样造成的。我们可以把这类的错误当做是程序语言的缺陷,或者是故意留下的陷阱。
程序设计语言是分为若干个层次的:
- 从词法分析的角度,也就是从每个符号
token
的角度分析错误; - 从语法分析的角度,也就是从每个语句的角度分析;
- 从语义分析的角度,也就是对于每个语句完成的任务的具体的意义角度分析;
- 通过了编译的阶段,下一个阶段就要分析在链接阶段会有哪些容易被忽略的错误;
- 程序设计语言都提供了很方便的调用库,在使用不同的库函数的时候又会发生哪些错误。
- 预处理这个过程也不能忽略,著名的宏就是在这个阶段起作用的,这个阶段有什么陷阱。
- 习惯了使用高级开发语言,语言本身提供了可移植属性,那么对于C语言是不能忽略程序移植遇到的问题。
以上阶段也就是包含了在程序设计过程中所有出现错误的不同层次,从这些层次依次逐个分析,就把这些陷阱或者缺陷都揪出来了。
这也就是这本书的意义,不是分析程序员的逻辑错误,而是程序员容易忽略的问题,或者由于程序语言设计的缺陷会带来的潜在问题。若是这个目的,那么就可以很顺利的读完这本书,并且收货还是不少。
“词法”到底会有什么trap
单词或者是符号token
对于编译器来说已经是原子了,这里能有什么错误,除非是把符号都写错了,还是真有一些,前辈还是列举了几个:
- 符号,比如说判断相等的符号“==”,容易少些一个;
- 符号&和符号&&是不同的;
- 数字我们默认是十进制,但是当一个整数是
0
开始的,那它就是八进制了; - 字符串与字符的区别,一个是单引号,一个是双引号,能区分吗?
也就这点东西了,如果陷阱再多一点,我就怀疑语言本身的问题了。
”语法“能有什么问题呢?
这一章是我最认真阅读的,也是我发现我也有好多问题没有想过,这些错误确实是我的弱点。
- 函数指针是个什么鬼?就是一个指针,指向某个类型的函数的首地址。像其他指针一样,可以进行强制转化,地址为0的函数能用吗?我怕把自己的系统挂了。。。
- 运算符的优先级,我其实最讨厌这个,经常记混了,赋值运算符
assignments
居然是从右向左结合,这个一定要记住了;还有sizeof
不是一个函数,他是运算符,后面可以不用写括号的。 - 分号表示已经结束了,一定不要忘了,尤其是跟
return
在一起的时候。 switch
语句经常忘记break
,这个通常编译器是允许的,但是运行结果可能大相径庭。- 函数调用在函数名后面要由括号包含实参列表的,即使没有实参,也要有括号;
- 悬挂
else
,通常else
要跟同意括号内最近的未匹配的if
结合的,这里最好是按照正确的格式写代码,就算它什么也不做,那就加上一个分号而已。
在“语法”上常常忽视的问题
- 数组与指针,一位数组问题不是特别大,注意指针是指针,数组虽然是指向首元素的指针,但是它也只是数组名而已;最应该要注意的是二维数组与二级指针,定义一个指向一个数组的指针怎么定义?
int (*p)[31];
这里的*p
实际上是一个有31个元素的数组;当然,也可以从第二维定义,先定义一个指针数组,int ** pp = int *[12];
,然后给每个数组分配空间。 - 非数组的指针,使用前一定要分配空间哦。
- 作为参数的数组声明,数组名会退化成指针的。
- 避免“举隅法”(
synecdoche
)这个词的意思是用宽泛的意思代替较窄的词语,需要注意的是复制指针,病不能复制指针所指向的内容。 - 空指针并不等于空字符串,直接比较两个指针是可以的,但是不要用
strcmp
函数判断空指针,这个函数是要判断指针指向的内容的。 - 边界计算与不对称边界,这里讲的内容最多,其实就是讲的边界不对称的情况下方式数组越界,这个也是面试的时候经常会考到的问题。
- 求值顺序,这里其实就要注意自增或者自减的问题,还有在语句中,表达式的计算顺序。
- 运算符
&&
、||
和!
,这个问题比较常见。 - 整数溢出,也是面试的时候常常考虑到的,如何判断表达式是否越界,借助无符号数是个不错的方式,比如
if(a > INT_MAX - b) xxx;
或者if((unsigned)a +(unsigned)b > INT_MAX) xxx;
. - 给
main
函数提供一个返回值,这个我通常是返回0
.
链接器也会出问题
给我的感觉就是这章的内容给意义并不是特别大,主要介绍的是链接器在链接过程中如何处理外部变对象的命名冲突,所谓的外部对象是在函数体外声明或者定义的变量,这里的内容在《程序员的自我修养—编译、链接与库》介绍的比较详细了。
正确使用库函数
库函数确实很方便,但是使用不好也容易出错,这章的内容我感觉意义不大,至少对我现在的任务来说,重点不在这里。
预处理
这个比较重要,因为预处理主要是处理宏,这个东西比较诡异,因为是在预处理阶段,是不能检测到任何语言级别的错误的,语言级别的错误只有编译器和连接器才能检测到,预处理只是在文本上进行处理,比如去掉所有的注释,将所有的宏展开,这里的展开是指在任何使用宏的地方将程序文本替换,并不检查什么 语法错误。用宏定义常量还是没问题的,但是:
- 不能忽略宏中的空格
- 宏不是函数,不要没事把函数定义成宏
- 宏也不是语句
- 宏也不是类型
最后,最好是少用宏,用常量吧!
移植问题
我暂时不想考虑!
最后,前辈给出建议
就是不要说服自己皇帝的新装;在程序中明确的表明自己的意图。
总结程序一句话:写程序别装X!