1、预定义符号
__FILE__:进行编译的文件
__LINE__:文件当前的行号
__DATE__:文件编译的日期
__TIME__:文件编译的时间
__STDC__:若编译器遵循ANSI C,其值为1,否则未定义
2、#define
2.1#define 定义标识符
语法形式: #define name stuff
#define MAX 100 #define reg register //使用更为简单的名称 #define ptr "wanyeweiyu" #define CASE break;case int main() { printf("%d \n", MAX); printf("%s", ptr); reg int i = 0; switch (i) { //不用在case以及CASE后加上break,CASE通过预处理替换为 break;case case 1: CASE 2: CASE 3: CASE 4: CASE 5: } return 0; }
如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ , \ __DATE__,__TIME__ )
注意:在#define定义标识符是,不要在后面加";"
2.2#define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
- 宏的声明方式: #define name(parament-list) stuff name表示宏名,()里为参数列表,stuff 为文本
- 声明注意:name必须与左括号紧邻,否则就会被解释为stuff 文本的一部分
- 使用:
#define SQRT(X) X*X #define SQR(X) (X)*(X) #define ADD(x,y) x+y #define ADD1(x,y)( x+y) int main() { int i = 5; printf("%d\n", SQRT(i + 1)); //这里的结果并不是36 //由于是直接替换,则替换后为 i+1*i+1 //结果为11 //解决办法是在定义宏时 将参数用括号括起来 printf("%d\n", SQR(i + 1)); //如果是 printf("%d\n", 10 * ADD(2, 3)); //替换后会由于符号的优先级使运算顺序发生变化 //在宏定义时可将 文本括起来 printf("%d\n", 10 * ADD1(2, 3)); return 0; }
- 提示:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
- 宏的使用:
#define MALLOC(type,num) (type*)malloc((num)*sizeof(type)) int main() { int* i = MALLOC(int,10); return 0; }
.
2.3#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#define M 100 int main() { char arr[] = "SMDGD"; arr[1] = 'M'; //字符串中的M并不会被替换为100 //字符M也不会被替换为100 }
2.3 #和##
如何将参数插入到字符串中?
首先:字符串具有自动连接的特点,最后都会输出 hello wanye
int main() { char* p = "hello ""wanye\n"; printf("hello"" wanye\n"); printf("%s", p); }
#:在文本中把一个宏参数变成对应的字符串,#参数 会被预处理器处理为 "参数"
#define PRINT(format,value) printf("the value of "#value" is "format"\n",value) #define PRINT1(format,value) printf("the value of "#value" is "#format"\n",value) int main() { PRINT("%d", 3 + 1);//参数如果是字符串,插入到字符串中只需要用引号 "参数" PRINT1(% d, 3 + 1);//若参数不是字符串,插入到字符串中 需要用"#参数" }
##:可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
#define Add(Class,value) senior##Class=value int main() { int senior1=0; Add(1, 60); printf("%d", senior1); }
注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
2.4宏和函数对比
优点:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。而宏是类型无关的
- 宏的参数可以出现类型,而函数无法完成
缺点:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
3、 #undef
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
#undef NAME
4、条件编译
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
常见的条件编译指令:
1.
#if 常量表达式 //... #endif //常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.. #endif
2.多个分支的条件编译
#if 常量表达式 //... #elif 常量表达式 //... #else //... #endif
3.判断是否被定义
#if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
4.嵌套指令
4.嵌套指令 #if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
5、文件包含
#include 指令可以使另外一个文件被编译,替换方式是预处理器先删除这条指令,并用包含文件的内容替换。如果.一个源文件被包含10次,那就实际被编译10次。
" "与< >的区别:文件的查找策略不同
包含方式:
- 本地文件包含 " ":先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
- 库文件包含 < >:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
因此,对于库文件也可以使用 " " 的形式包含,但查找效率低,且不易区分库文件和本地文件
嵌套文件包含:例如有两个功能模块,实现格式输出的功能模块需要<stdio.h>文件,打开文件调用fopen函数的功能模块也需要调用<stdio.h>文件,这两个模块的头文件中都包含了stdio ,在最后对两个模块的头文件的包含时,就可能会造成头文件的重复引入
解决方法:
条件编译
#ifndef __TEST_H__ #define __TEST_H__ //头文件的内容 #endif //__TEST_H__
在头文件开头写:
#pragma once
6、offsetof函数的模拟
#define OFFSETOF(type,mem) (size_t)&((type*)0->mem)
//将0转换为结构体类型的指针,然后指向该成员
//取该成员的地址,再强制转换为无符号整形