在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉
惯例将宏名称每个字母采用大写,这有助于区分宏与一般的变量
出现在字符串字面量中的宏名称不会被展开,因为整个字符串字面量算作一个预处理器记号
无法通过宏展开的方式创建预处理器命令。即使宏的展开结果会生成形式上有效的命令,但预处理器不会执行它
预定义的标识符__func__可以在任一函数中使用,该标识符是表示当前函数名的字符串
#define ARRAY_SIZE 100 double data[ARRAY_SIZE];
作为一种状态,可以在程序的其他位置判断宏是否定义了,从而编译时期就表现出不同的行为
function-like macro
“形参列表”是用逗号隔开的多个标识符,它们都作为宏的形参。
当定义一个宏时,必须确保宏名称与左括号之间没有空白符。
#define 宏名称( [形参列表] ) 替换文本 #define 宏名称( [形参列表 ,] ... ) 替换文本
替换文本中所有出现的形参,应该使用括号将其包围
#define DISTANCE( x, y ) ((x)>=(y) ? (x)-(y) : (y)-(x)) d = DISTANCE( a, b+0.5 );
C99 标准允许定义有省略号的宏,省略号必须放在参数列表的后面,以表示可选参数。你可以用可选参数来调用这类宏。
一元运算符 # 常称为字符串化运算符(stringify operator 或 stringizing operator),因为它会把宏调用时的实参转换为字符串
# 的操作数必须是宏替换文本中的形参
当形参名称出现在替换文本中,并且具有前缀 # 字符时,预处理器会把与该形参对应的实参放到一对双引号中,形成一个字符串字面量
#define printDBL( exp ) printf( #exp " = %f ", exp ) printDBL( 4 * atan(1.0)); // atan()在math.h中定义 printf( "4 * atan(1.0)" " = %f ", 4 * atan(1.0)); // 等效 printf( "4 * atan(1.0) = %f ", 4 * atan(1.0)); // 等效
运算符是一个二元运算符,可以出现在所有宏的替换文本中
该运算符会把左、右操作数结合在一起,作为一个记号,因此,它常常被称为记号粘贴运算符(token-pasting operator)
如果结果文本中还包含有宏名称,则预处理器会继续进行宏替换
出现在 ## 运算符前后的空白符连同 ## 运算符本身一起被删除
通常,使用 ## 运算符时,至少有一个操作数是宏的形参
#define TEXT_A "Hello, world!" #define msg(x) puts( TEXT_ ## x ) msg(A); puts( TEXT_A ); // 等效 puts( "Hello, world!" ); // 等效
在替换实参,以及执行完 # 和 ## 运算之后,预处理器会检查操作所得的替换文本,并展开其中包含的所有宏。但是,宏不可以递归地展开:如果预处理器在 A 宏的替换文本中又遇到了 A 宏的名称,或者从嵌套在 A 宏内的 B 宏内又遇到了 A 宏的名称,那么 A 宏的名称就会无法展开。
无法再次使用 #define 命令重新定义一个已经被定义为宏的标识符,除非重新定义所使用的替换文本与已经被定义的替换文本完全相同
// 取消定义一个宏 // 当某个宏首次遇到它的 #undef 命令时,它的作用域就会结束 // 如果没有关于该宏的 #undef 命令,那么它的作用域在该翻译单元结束时终止 #undef 宏名称 // 如果上面指定的标识符并非一个已定义的宏名称,那么预处理器会忽略这个 #undef 命令 // 即使准备取消定义的宏是带有参数的,也不需要在 #undef 命令中指定参数列表
// 代码来自网络 // check cuda error inline void check(cudaError_t call, const char* file, const int line) { if (call != cudaSuccess) { std::cout << "cuda error: " << cudaGetErrorName(call) << std::endl; std::cout << "at file: " << file << ", line: " << line << std::endl; std::cout << cudaGetErrorString(call) << std::endl; } } #define CHECK(call) (check(call, __FILE__, __LINE__))