参考: 里科《C和指针》
__FILE__ // 进行编译的源文件名
__LINE__ // 文件当前行的行号
__DATE__ // 文件被编译的日期 Jan 31 1997
__TIME__ // 文件被编译的时间
写法:
1)宏的名字全大写,以区分宏与函数。
2)如果一个现存的名字需要重新定义,首先需要使用#undef name移除旧定义。
3)在那些对表达式进行求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义的两边也加上括号(避免因运算符优先级不同造成歧义)
使用宏而非函数的情况:
1)宏的规模和速度更快(函数存在调用、返回的开销)
2)宏与类型无关,函数的参数必须声明为一种特定类型
3)某些无法使用函数的情况
宏的缺点是每次使用时,必须将一份宏定义代码拷贝到程序中,因此除非宏非常短,不然可能大幅增加程序的长度
// 可以有多行,但是要加\
// 这个适合多次打印信息的情况,比如我需要知道某个变量在某些操作后的结果
// 因为define是直接替换的,一定不要多输;
#define DEBUG_PRINT printf("xxx" \
xxxx \)
#define SQUARE(x) x * x
int main()
{
int i = 2;
// 输出25
printf("%d\n", SQUARE(5));
// 实际替换为 i + 1 * i + 1,所以是5
// 可以将define语句改为:#define SQUARE(x) (x) * (x)
printf("%d\n", SQUARE(i + 1 ));
return 0;
}
#define DOUBLE(x) (x) + (x)
a = 5;
// 此时会先计算乘法,所以应该修改define为#define DOUBLE(x) ( (x) + (x) )
printf("%d\n", 10 * DOUBLE( a ));
//利用邻近字符串自动连接特性
#define PRINT(FORMAT, VALUE) \
printf("The value is " FORMAT "\n", VALUE)
int i = 2;
PRINT("%d", i + 3);
// 如果希望将表达式转为字符串,用#
#define PRINT(FORMAT, VALUE) \
printf("The value of " #VALUE \
" is " FORMAT "\n", VALUE)
// The value of i + 3 is 5
PRINT("%d", i + 3);
// 如果需要传递一个标识符名字,比如想把一个值加到sum1、sum2、sum3...
// 使用##。但是需要保证该名字的变量是有的
#define ADD_TO_SUM( NUM, VAL ) \
sum ## NUM += VAL
int sum1 = 0;
ADD_TO_SUM(1, 25);
printf("%d", sum1); // 25
// 无法使用函数的情况下,使用宏
#define MALLOC(n, type) \
( (type *)malloc( (n) * sizeof( type ) ) )
int* pi = MALLOC(25, int);
带副作用的宏参数
副作用指表达式求值时会出现永久后果。比如 x + 1不会改变表达式里x的值,但是x++就会。getchar()也有副作用,因为它会消耗输入的一个字符,后续调用会得到不同的字符。
#define MAX( a, b ) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX( x++, y++);
// 此时x = 6, y = 10, z = 9
// 因为带入后z = ( (x++) > (y++) ? (x++) : (y++) )
// 所以y增加了两次,z取的是y增加一次时的值
命令行定义
许多C编译器允许在命令行中定义符号,比如跨平台运行时,某个数组的大小可以根据平台能力修改。下面的ARRAY_SIZE可以在命令行中指定。
int array[ARRAY_SIZE];
比如unix中,编译命令可以是:(格式:-Dname=value)
cc -DARRAY_SIZE=100 prog.c
但是在程序中只能是用参数的地方才可以用,比如循环内部需要使用字面值常量访问数组时,就不能用。如果要去除这个定义,使用-Uname。
例如,调试代码可以写成下面这样,如果要debug,用#define DEBUG 1即可,如果要忽略这段代码,定义为0。
#if DEBUG
printf("x=%d", x);
#endif
elif可以有很多个
#if constant-expression
...
#elif constant-exp
...
#else
..
#endif
是否被定义可以使用#ifdef或者#ifndef,但是用#if更好些,因为可以扩展条件
// symbol是否被定义?
#ifdef symbol
#ifndef symbol
// 等价
#if defined(symbol)
#if !defined(symbol)
// 但是if可以判断多个条件
#if X > 0 || defined( ABC ) && defined( BCD )
#if和#ifdef这些可以嵌套,如果某个部分非常长,最好在#endif后加一些注释说明每个选择的含义
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif /* sth about OPTION1 */
#ifdef OPTION2
unix_version_of_option2();
#endif /* sth about OPTION2 */
#elif defined( OS_MSDOS )
#ifdef OPTION2
msdos_version_of_option2();
#endif /* sth */
#endif
#include
包含库头文件用<>,本地头文件用“”。也可以使用绝对路径
#include
#include "errno.h"
标准要求编译器必须支持至少8层的头文件嵌套,但没有规定最大值,但实际操作中,一般会避免#include指令的嵌套深度超过2层。嵌套可能有多次包含同一个文件,为了消除这个可以使用条件编译,这样第二次被包含时其内容会被忽略,但是读取文件仍然会拖慢编译速度,所以还是应该避免多次包含。
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
/* All the stuff that you want in header file */
#endif
#error
产生错误信息
#elif ...
...
#else
#error No option selected!
#line
很少用,最常用在将其他语言的代码转成C代码时。C编译器产生的错误信息可以引用源文件而非翻译程序产生的C中间源文件的文件名和行号。
它通知预处理器number是下一行输入的行号。如果给出了“string”,预处理器就把string当作当前文件的名字。因此会修改__LINE__和__FILE__的值。
#line number "string"
#progma
因编译器而异,不能移植,允许编译器提供不标准的处理过程,比如向一个函数插入内联的汇编代码。
无效指令null directive
就是一个#开头的空行,跟空行一样起到一个分隔作用,但是预处理器会直接删除