主要是因为以前键盘上没有某些字符,所以为了使用这些字符,必须使用多个可以打印字符来标识这个不能打印的字符,三字符的替换发生在所有操作之前;
??= |
# |
??( |
[ |
??< |
{ |
??/ |
\ |
??) |
] |
??> |
} |
??' |
^ |
??! |
| |
??- |
~ |
不过 现在编译器默认是关掉替换的,替换会造成很多有趣的事情,如:
int ci=1; //接下来的??/ ++ci; printf("%d\n",ci); /* ??/会被替换为'\';而'\'作为一个行连接符会让"++ci"被注释掉 * 不过现在编译器默认是关掉替换的 */
首先根据','将实际参数分割成一个个记号,此时不会被实际参数进行扩展;
#define cat(x,y) x;y; cat(int a;,int b;);/* 宏展开: int a;;int b;; */ /* 根据','分割记号 */
用实参产生的记号替换宏中未用引号引起来的相应形式参数的标识符
如果标识符前面没有'#',或者前者或后面都没有'##'符号;则检查实际参数产生的记号,并在必要的时刻进行宏扩展
根据','将实际参数分成记号,并匹配宏定义中的形参,此时位于引号或者嵌套括号的逗号不会用于分割,如
/** * 根据 func 的返回值测试 func 是否成功执行,若出错,则抛出 std::runtime_error() 类型的异常,异常字符串为:文件名;行号;func;format.. * @param func 若 func 返回 int 类型,则当返回值不为0时,认为 func 出错;若 func 返回指针类型,则当返回值为 0 时,认为出错. */ #define TF(func,format,...) testSystemCall(func,__FILE__,LINE_TO_STR(__LINE__),#func,format,##__VA_ARGS__) TF(socket(AF_INET,SOCK_STREAM,0),"HELLOWORLD"); // 此时根据','进行分割,匹配情况为: // func = socket(AF_INET,SOCK_STREAM,0) // format = "HELLOWORLD" // 所以此时替换后内容为:testSystemCall(socket(2,1,0),"main.cc","13","socket(AF_INET,SOCK_STREAM,0)","HELLOWORLD"); // 即 socket() 内的","不会用于分割.
如果在上述替换时发现形式参数标识符之前存在'#'符号,则
首先在实际参数产生的记号两端添加"符号,使得记号变成一个字符串字面值
然后再用该记号替换 '#'与其后的标志符
实际参数中的字符串字面值,字符常量两边或内部的每个双引号( " )或反斜杠( \ )前面都会由预编译器自动插入一个反斜杠( \ )。
#define mStr(x) #x printf("%s\n",mStr("3\3")); /* 此时宏展开 "\"3\\3\"" */
在形式参数都被替换后都要把##及其前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。
#define cat(x,y) x ## y printf("%d\n",cat(1,2));
重复扫描替换记号序列以查找更多的己定义标识符。但是, 当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变 。
printf("%d\n",mtest(m,test)(3,3)); /* 第一次扫描:mtest(3,3);此时因为mtest() * 在上一层扫描中已经被展开了,所以此时不会再展开 */ printf("%d\n",mtest(m,test1)(3,3)); /* 第一次扫描:mtest1(3,3);然后 * 下一次扫描再展开mtest1 */
#include 把该行(直接在当前行)替换为文件名指定的文件的内容 ,三种形式:
#include <文件>
#include "文件"
#include 宏调用
#define incFile(file) #file #include incFile(___Dlove_HTTPSend.h); /* 就是直接 #inlcude "___Dlove_HTTPSend.h" */
#if 整数表达式 文本 #else 文本 #endif
"文本" 是指任何不属于条件编译指令结构的程序代码,它可以包含预处理指令,也可以为空 。
因为预编译器不进行计算,所以#if后只能用整数表达式(必须是整型,并且其中不包含sizeof,强制类型转换运算符或枚举常量);
defined 标志符 或者 defined(标志符);
如果该标识符在预处理器中已经定义,则用 1 替换它,否则,用 0 替换。预处理器进行宏扩展之后仍然存在的任何标识符都将用0来替换 。
加粗字体。。我不甚了解,大概就是因为在宏的多次扫描中已经扩展过的标志符不会在被扩展,差不多这个意思;
#error 描述
将终止编译并打印指定的诊断信息。
通过在宏定义中使用"...",在宏拓展中使用"__VA_ARGS__"来实现具有可变参数的宏,如
#define errRep(func,...) \ fprintf(stderr,func,__VA_ARGS__);
#define err(format,...) fprintf(stderr,format,__VA_ARGS__) int main(int argc,char *argv[]){ err("HelloWord"); err("Hello;%d;%f",33,3.3); return 0; }
$ g++ -E main.cc # 1 "main.cc" # 1 "<built-in>" # 1 "<命令行>" # 1 "main.cc" int main(int argc,char *argv[]){ fprintf(stderr,"HelloWord",); # 当没有参数时,__VA_ARGS__ 相当于空,所以此时会多了一个逗号. fprintf(stderr,"Hello;%d",360); return 0; }
使用 ##__VA_ARGS__ 来消除,
#define err(format,...) fprintf(stderr,format,##__VA_ARGS__)
$ g++ -E main.cc # 1 "main.cc" # 1 "<built-in>" # 1 "<命令行>" # 1 "main.cc" int main(int argc,char *argv[]){ fprintf(stderr,"HelloWord"); # 多余的逗号不见了 fprintf(stderr,"Hello;%d",360); return 0; }
__LINE__ | 当前行号 |
__FILE__ | 当前源文件名 |
__DATE__ | 当前编译日期 |
__TIME__ | 当前编译时间 |
__STDC__ | 当程序严格遵循STDC时为1 |
__cplusplus | 当程序使用C++语言编写为1 |
#include<iostream> using namespace std; int main(){ cout<<"源文件为: "<<__FILE__<<endl<<"编译于:"__DATE__<<" "<<__TIME__<<endl; cout<<"使用"; #ifdef __cplusplus cout<<"C++"; #else cout<<"C" #endif cout<<"编写 "<<endl; #ifndef __STDC__ cout<<"不"; #endif cout<<"遵循ANSC标准"<<endl; return 0; }
1.一般用在宏定义中,如:
/* * 功能:往stderr流中写入出错信息,以退出程序 * 参数:与 fprintf() 参数一致 */ #define mErrRep(errfuc,...) \ do{ \ fprintf(stderr,errfuc,##__VA_ARGS__); \ exit(1); \ }while(0);
此时 mErrRep() 宏有两条语句,并且经常出现在 if() 后面;此时使用 do{}while(0) 可以很安全的这样:
if((dirs=opendir("/root")) == NULL) mErrRep("main:opendir: %s\n",strerror(errno)); /* 会被替换为: */ if(dirs=opendir("/root") ==NULL ) do{ fprintf(stderr,"main:opendir: %s\n",strerror(errno)); exit(1); }while(0) 如果不使用 do{}while(0) 的话在每一个if()后面都应该加上一个{}以括起宏定义中可能含有的多条语句