相识
- 标点符号可以有几种不同的含义。
- 程序中的所有标识符都必须声明。
- 标识符可以有多个一致的声明。
- 声明被绑定到它们所出现的作用域。
- 声明指定标识符,而定义指定对象。
- 对象在初始化的同时被定义。
- 初始化设定中缺少的元素的默认值为0。
- 对于一个有n个元素的数组,第一个元素的索引为0,最后一个元素的索引为n-1。
- 每个对象或函数必须只有一个定义。
按值调用。其他编程语言也有按引用调用,它是一种被调用的函数可以改变变量值的机制。 C没有实现按引用传递,但是它有另一种机制来将变量的控制传递给另一个函数:通过获取地址和发送指针。
C语言中最危险的结构是所谓的强制类型转换
指针 将指针伪装成函数的参数
将类型修饰符和限定符绑定到左边。我们希望从视觉上将标识符与它们的类型分开。我们通常会写成如下,其中char*是类型,name是标识符
char* name;
我们还将左绑定规则应用于限定符,写成
char const* const path_name;
这里,第一个const将char限定至其左边,*使其成为一个指针,第二个const再次将所指内容限定至其左边。 不使用连续声明。它们混淆了类型声明符的绑定。例如:
unsigned const*const a, b;
这里,b的类型是unsigned const
。也就是说,第一个const指向类型,第二个const只指向a的声明。这样的规则非常混乱,你还有更重要的东西要学习。
使用数组符号来表示指针参数。只要这些假设指针不能为空,我们就可以这样做。例如:
// 这里强调参数不能为空
size_t strlen(char const string[static 1]);
int main(int argc, char* argv[argc+1]);
// 相同函数的兼容声明
size_t strlen(const char *string);
int main(int argc, char **argv);
第一个声明强调strlen必须接收一个合法的(非空)指针,并且至少要访问string的一个元素。第二个声明总结了main接收一个指向char的指针数组的事实,数组包含程序名、argc-1个程序参数和一个终止数组的空指针。 注意,前面的代码是合法的。第二组声明只是为编译器已知的特性添加了额外的等价声明。
使用函数符号来表示函数指针参数。同样,只要我们知道函数指针不能为空,就可以这样做:
int atexit(void handler(void));
int atexit(void (*handler)(void));
这里,atexit的第一个声明强调,从语义上讲,它接收一个名为handler的函数作为参数,并且不允许使用空函数指针。从技术上讲,函数参数处理程序handler被“重写”为函数指针,就像数组参数被重写为对象指针一样,但是对于功能的描述来说,这并不重要。再次注意,前面的代码是合法的,第二个声明只是为atexit添加了一个等价的声明。
对变量的定义要尽可能靠近首次使用它们的位置。缺少变量初始化,特别是指针的初始化,是新手C程序员遇到的主要陷阱之一。这就是为什么我们应该尽可能地将变量的声明与变量的第一次赋值结合起来,C为此提供的工具是定义,即声明和初始化。它给一个值命名,并在第一次使用该值的地方引入该名称。这对于for循环特别方便。一个循环的循环变量与另一个循环中的循环变量在语义上是不同的对象,因此我们在for中声明该变量,以确保它保持在循环的范围内。
对代码块使用前缀表示法。为了能够读取一个代码块,很重要的一点是要容易地捕获它的两个方面:用途和范围。因此:所有{
都作为前缀与引入它们的语句或声明位于同一行。里面的代码缩进一级。终止}
在与引入块的语句的同一级上启用一个新行。如果}
之后有延续的块语句,则它们在同一行上延续。例如:
int main(int argc, char* argv[argc + 1]) {
puts("hello world");
if (argc > 1) {
while(true) {
puts("some programs never stop");
}
} else {
do {
puts("but this one does");
} while (false);
}
return EXIT_SUCCESS;
}