1.在使用全局变量时,一般将定义放在.c文件中,而将声明放在.h文件中,定义变量的.c文件也应该包含该头文件, 以便编译器检查定义和声明的一致性。
2. 永远不要把外部函数的原型放到.c 文件中: 通常它与定义的一致性不能得到检查,而矛盾的原型比不用还糟糕。
3. 在函数外声明的变量和有静态存储类型的变量可以确保初始值为零, 就像程序员键入了“=0”一样。因此, 这些变量如果是指针会被初始化为正确的空指针, 如果是浮点数会被初始化为0.0。这个规则也适用于数组和结构(称为“聚合体”); 对于初始化来说, 数组和结构都被认为是“变量”。 用malloc() 和realloc() 动态分配的内存也可能包含垃圾数据,因此必须由调用者正确地初始化。用calloc() 获得的内存为全零, 但这对指针和浮点值不一定有用。
4. 这样的初始化有什么问题?char *p = malloc(10); 编译器提示“非法初始式”云云。
这个声明是静态或非局部变量吗?函数调用只能出现在自动变量(即局部非静态变量) 的初始式中。
5. 我总算弄清除函数指针的声明方法了, 但怎样才能初始化呢?
用下面这样的代码
extern int func();
int (*fp)() = func;
当一个函数名出现在这样的表达式中时, 它就会“蜕变” 成一个指针(即, 隐式地取出了它的地址),这有点类似数组名的行为。
6. 是否有自动比较结构的方法?
没有。编译器没有简单的好办法实现结构比较(即, 支持结构的== 操作符),这也符合C的低层特性。简单的按字节比较会由于结构中没有用到的“空洞” 中的随机数据(参见问题2.10) 而失败;而按域比较在处理大结构时需要难以接受的大量重复代码。如果你需要比较两个结构, 你必须自己写函数按域比较。
7.结构体的初始化方法。
有几种初始化方式:
struct a a1 = {
};
或者
struct a a1 = {
}
或者
struct a a1 = { 1, 2};
内核喜欢用第一种,使用第一种和第二种时,成员初始化顺序可变。
8.怎样从/向数据文件读/写结构?
用fwrite() 写一个结构相对简单:
fwrite(&somestruct, sizeof somestruct, 1,fp);
对应的fread()调用可以再把它读回来。但是这样写出的文件却不能移植。同时注意如果结构包含任何指针, 则只有指针值会被写入文件,当它们再次读回来的时候, 很可能已经失效。最后, 为了广泛的移植, 你必须用“b” 标志打开文件。
移植性更好的方案是写一对函数, 用可移植(可能甚至是人可读) 的方式按域读写结构,尽管开始可能工作量稍大。
9. 如何确定域在结构中的字节偏移?
ANSI C 在<</I>stddef.h>中定义了offsetof() 宏, 用offsetof(struct s, f) 可以计算出域f 在结构s中的偏移量。如果出于某种原因, 你需要自己实现这个功能, 可以使用下边这样的代码:
#define offsetof(type, f) ((size_t) \
((char *)&((type *)0)->f - (char *)(type*)0))
这种实现不是100% 的可移植; 某些编译器可能会合法地拒绝接受。
10. 这是个巧妙的表达式: a ˆ= b ˆ= a ˆ= b 它不需要临时变量就可以交换a 和b的值。这不具有可移植性。它试图在序列点之间两次修改变量a, 而这是无定义的。
11. 我可否用括号来强制执行我所需要的计算顺序?
一般来讲, 不行。运算符优先级和括弧只能赋予表达是计算部分的顺序. 在如下的代码中
f() + g() * h()
尽管我们知道乘法运算在加法之前, 但这并不能说明这三个函数哪个会被首先调用。
12. 可是&& 和|| 运算符呢?我看到过类似while((c =getchar()) != EOF && c != ’\n’) 的代码
这些运算符在此处有一个特殊的“短路” 例外: 如果左边的子表达式决定最终结果(即,真对于||和假对于&& ) 则右边的子表达式不会计算。因此, 从左至右的计算可以确保, 对逗号表达式也是如此。而且,所有这些运算符(包括? : ) 都会引入一个额外的内部序列点。
13. 对于a[i] = i++; 我们不知道a[] 的哪一个分量会被改写,但i的确会增加1,对吗?
不一定!如果一个表达式和程序变得未定义, 则它的所有方面都会变成未定义。
14. 为什么如下的代码int a = 100, b = 100; long int c = a *b; 不能工作?
根据C 的内部类型转换规则, 乘法是用int 进行的, 而其结果可能在转换为long 型并赋给左边的c之前溢出或被截短。可以使用明确的类型转换, 强迫乘法以long 型进行:
long int c = (long int)a * b; 注意, (long int)(a * b)不能达到需要的效果。
15. 我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。可以用下边这样的代码吗?
不能。? : 操作符, 跟多数操作符一样, 生成一个值, 而不能被赋值。换言之, ? :不能生成一个“左值”。如果你真的需要, 你可以试试下面这样的代码:
*((condition) ? &a : &b) =complicated_expression;
尽管这毫无优雅可言。
16. 我有一个char * 型指针正巧指向一些int 型变量,我想跳过它们。为什么如下的代码((int *)p)++; 不行?
在C 语言中, 类型转换意味着“把这些二进制位看作另一种类型, 并作相应的对待”;这是一个转换操作符, 根据定义它只能生成一个右值(rvalue)。而右值既不能赋值, 也不能用++ 自增。(如果编译器支持这样的扩展,那要么是一个错误, 要么是有意作出的非标准扩展。) 要达到你的目的可以用:
p = (char *)((int *)p + 1);
或者,因为p 是char * 型, 直接用
p += sizeof(int);
17. 我能否用void** 指针作为参数, 使函数按引用接受一般指针?(待定)
不可移植。C 中没有一般的指针的指针类型。void*可以用作一般指针只是因为当它和其它类型相互赋值的时候, 如果需要, 它可以自动转换成其它类型; 但是,如果试图这样转换所指类型为void* 之外的类型的void** 指针时, 这个转换不能完成。
18. 我有一个函数extern int f(int *); 它接受指向int型的指针。我怎样用引用方式传入一个常数?下面这样的调用f(&5); 似乎不行。
在C99 中, 你可以使用“复合常量”:
f((int[]){5});
19. 传入函数的参数不一定被当作指针环境, 因而编译器可能不能识别未加修饰的0 “表示”指针。在函数调用的上下文中生成空指针需要明确的类型转换,强制把0 看作指针。例如, Unix 系统调用execl接受变长的以空指针结束的字符指针参数。它应该如下正确调用:
execl("/bin/sh", "sh", "-c", "date", (char*)0);
特别是, 在函数调用的参数里, NULL之前(正如在0 之前) 的类型转换还是需要。
20. 可以使用未加修饰的0:
初始化
赋值
比较
固定参数的函数调用且在作用域
内有原型
需要显示的类型转换:
函数调用, 作用域内无原型
变参函数调用中的可变参数
21. 如果NULL 定义成#define NULL ((char *)0)难道不就可以向函数传入不加转换的NULL 了吗?
一般情况下, 不行。复杂之处在于, 有的机器不同类型数据的指针有不同的内部表达。这样的NULL定义对于接受字符指针的的函数没有问题, 但对于其它类型的指针参数仍然有问题(在缺少原型的情况下), 而合法的构造如
FILE *fp = NULL;
则会失败。
不过, ANSI C 允许NULL 的可选定义
#define NULL ((void *)0)
22. 那么为什么作为函数形参的数组和指针申明可以互换呢?
这是一种便利。
由于数组会马上蜕变为指针,数组事实上从来没有传入过函数。允许指针参数声明为数组只不过是为让它看起来好像传入了数组, 因为该参数可能在函数内当作数组使用。
23. 我如何声明大小和传入的数组一样的局部数组?
直到最近以前, 你都不能; C 语言的数组维度一直都是编译时常数。但是,C99引入了变长数组(VLA), 解决了这个问题; 局部数组的大小可以用变量或其它表达式设置, 可能也包括函数参数。( gcc提供参数化数组作为扩展已经有些时候了。)如果你不能使用C99 或gcc, 你必须使用malloc(),并在函数返回之前调用free()。
24.二维数组可以省略第一个坐标还是第二个坐标?
可以省略第一个坐标,一维数组在初始化的时候可以省略坐标,因为系统可以根据后面的元素的个数来确定坐标的值,二维数组在初始化的时候有形如:{{1,2,3,},{4,5,6},…}这种初始化方式,而其中的{1,2,3}就可以看做一维数组的一个元素,所以能够省略行坐标。
25. 当我向一个接受指针的指针的函数传入二维数组的时候, 编译器报
错了。
数组蜕化为指针的规则不能递归应用。数组的数组(即C 语言中的二维数组) 蜕化为数组的指针,而不是指针的指针。数组指针常常令人困惑, 需要小心对待。