一、字符和字符串
1. Q:为什么strcat(string, '!')不行?
A:strcat()用于拼接字符串,所以应该写成strcat(string, "!")。"!"实际上包含两个字符:'!'和'\0'。
2. Q:为什么不能这样检查一个字符串是否跟某个值匹配?
char *string;
...
if (string == "value") {
/* string matches "value"*/
}
A:C语言中的字符串用字符的数组表示,C语言不会把数组作为一个整体来操作(赋值、比较等)。上面代码段的==操作符实际上比较的是两个指针是否相等,要比较两个字符串,一般使用库函数strcmp()。
3. Q:我正开始考虑多语言字符集的问题,是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢?
A:就算char型被定义为16位,sizeof(char)依然是1(sizeof是以char的大小作为一个单位的),而<limits.h>中的CHAR_BIT会被定义为16,届时将不能声明(或用malloc分配)一个8位的对象。
二、布尔表达式和变量
1. Q:C语言中布尔值该用什么类型?为什么它不是一个标准类型?
A:C语言中没有提供标准的布尔类型,部分原因在于选择一个这样的类型设计最好有程序员来决定的空间/时间折中。(使用int型可能更快,使用char型可能更节省数据空间。然而,如果需要和int型反复转换,那么更小的类型也可能生成更大或更慢的代码。)可以使用#define或枚举常量定义真假,无伤大雅。
#define TRUE 1
#define FALSE 0 enum bool {false, true};
2. Q:我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE, FALSE, Min(), Max()等,但是它们的定义相互冲突,怎么办?
A:这是个典型的命名空间问题。理想状态下,第三方库的厂商在定义符号(预处理库、全局变量和函数名称)的时候应该尽责地确保不会发生命名空间冲突,最好的解决方案是让厂商修改他们的头文件。作为一种迂回措施,你也可以在发生冲突的#include指令之间解除或重新定义冲突的宏。
三、C预处理器
1. 如果一个参数在宏扩展中出现了多次,而实参是带副作用的表达式,则宏可能不能正确运行。
#define square(x) ((x) * (x))
square(i++); //会被扩展为((i++)*(i++)),而这是未定义的
2. Q:如何书写多语句宏?
A:通常的目标时能够像一个包含函数调用的表达式语句一样调用宏:MACRO(arg1, arg2); 这意味着“调用者”需要提供最终的分号,而宏体不需要。因此宏体不能为简单的括号包围的复合语句,因为这个宏可能会用于带else的if/else语句的if分支。传统的解决方法是使用do-while语句。
#define MACRO(arg1, arg2) {stmt1; stmt2;}
if (cond)
MACRO(arg1, arg2);
else /* some other code*/ /*宏展开后,用户提供的最终的分号就会成为语法错误*/
if (cond)
{stmt1; stmt2;};
else /* some other code */ /* 传统的解决方案 */
#define MACRO(arg1, arg2) do { \
stmt1;
stmt2;
...
} while()
如果宏体内的语句都是简单语句(没有声明或循环),那么还有一种技术,就是写一个使用一个或多个逗号操作符的表达式,放在括号中:
#define FUNC(arg1, arg2) (expr1, expr2. expr3) // FUNC返回expr3
有些编译器(如gcc)可以使用非标准的“inline”关键字或其他扩展自动地或根据程序员的请求使用内联扩展小函数。
3. Q:应该把哪些内容放在.h文件?哪些内容放在.c文件?
A:作为一般规则,应该把下面所列的内容放入头文件:(a)宏定义;(b)结构、联合和枚举声明;(c)typedef声明;(d)外部函数声明;(e)全局变量声明。
另一方面,如果定义或声明为一个源文件私有,则最好留在该文件中(并声明为static)。最后,不能把实际的代码或全局变量放在头文件中。
4. Q:可以在一个头文件中包含另一个头文件吗?
A:这是个风格问题。反对者认为,“嵌套包含文件”应该避免,因为它让相关定义更难找到。如果一个文件被包含了两次,它会导致重复定义错误,同时它也会令Makefile的人工维护十分困难。支持者认为,“嵌套包含文件”使模块化使用头文件成为可能(一个头文件可以包含它所需要的一切,而不是让每个源文件都包含需要的头文件)。而自动的Makefile维护工具可以很容易地处理嵌套包含文件的依赖问题。一种流行的头文件定义技巧:
#ifndef __HFILENAME_H__
#define __HFILENAME_H__
...
#endif
5. Q:完整的头文件搜索规则是怎样的?
A:准确的行为是由实现定义的(一般应该有文档说明)。通常,用<>命名的头文件会先在一个或多个标准位置搜索,用""命名的头文件会首先在“当前目录”搜索,如果没有找到再在标准位置搜索。注意“当前目录”的定义不唯一:传统上当前目录是包含#include指令的文件所在的目录,而在其他编译器下,当前目录是编译器启动的目录。
6. Q:sizeof操作符可以用在#if预处理指令中吗?
A:不行。预处理在编译过程之前进行,此时尚未对类型名称进行分析。作为替代,可以考虑使用ANSI的<limits.h>中定义的常量,或者使用配置脚本。当然,更好的方法是编写与类型大小无关的代码。
7. Q:如何用#if表达式来判断机器是高字节在前还是低字节在前?
A:恐怕不能。判断机器字节顺序的代码技术通常都要用到字符数组或联合,但预处理运算仅仅使用长整型,而且没有寻址的概念。另外,通常写出与字节顺序无关的代码更好。