第一章 C:穿越时空的迷雾
1.1 C语言的史前阶段
编译器设计者的金科玉律:效率(几乎)就是一切。编译器的效率包括两个方面:运行效率(代码的运行速度)和编译效率(产生可执行代码的速度)
1.2 C语言的早期体验
根据编译器设计者地思路而发展形成的语言特性有:
数组小标从0而不是从1开始。
C语言的基本数据类型直接与底层硬件相对应。
auto关键字显然是摆设。
表达式中的数组名可以看作是指针。
float被自动扩展为double。
不允许嵌套函数(函数内部包含另一个函数的定义)。
register关键字。
1.3 保准I/O库和C预处理器
C预处理器所实现的3个主要功能是:
字符串替换
头文件包含
通用代码模板的扩展
1.4 K&R C
1.5 今日之ANSI C
1.6 它很棒,但它符合标准吗
不可移植的代码
由编译器定义的——由编译器设计者决定采取何种行动(不同的编译器所采取的行动可能并不相同,但它们都是正确的)。
未定义的——在某些正确情况下的做法,标准并未明确规定应该怎么做。
坏代码
未定义的——在某些不正确情况下的做法,标准并未规定应该怎么做。
约束条件——这是一个必须遵守的限制或要求。
可抑制的代码
严格遵循标准的——一个严格遵循标准的程序应该是:
只使用已确定的特性。
不突破任何由编译器实现的限制。
不产生任何依赖由编译器定义的或未定义的或未确定的特性的输出。
1.7 编译限制
每一个ANSI C编译器必须能够支持:
在函数定义中形参数量的上限至少可以达到31个。
在函数调用时实参数量的上限至少可以达到31个。
在一条源代码行里至少可以有509个字符。
在表达式中至少可以支持32层嵌套的括号。
long型整数不得低于32位。等等。
1.8 ANSI C标准的结构
1.9 阅读ANSI C标准,寻找乐趣和裨益
foo (const char**p){}
main(int argc,char **argv)
{
foo(argv); //会产生警告:参数与类型不服
}
每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)。
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
char *cp;
const char*ccp;
ccp = cp;
赋值合法,反过来就不能进行赋值。
1.10 “安静的改变”究竟有多少安静
尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值而用它来表示数量。
尽量使用像int那样的有符号数,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(入-1被翻译为非常大的整数)。
只有在使用位段和二进制掩码时,才可以用无符号数。应该在表达式中使用强制类型转换,使操作数均为有符号数或者无符号数,这样就不必由编译器来选择结果的类型。
1.11 轻松一下——由编译器定义的Pragmas效果