3.2.4 #和##
使用 # , 把一个宏参数变成对应的字符串
1. #include <stdio.h> 2. #define PRINT(n) printf("the value of " #n " is %d\n", n) 3. //#n相当于字符串("a","b"),插入到这个位置,三个都是字符串,//不加引号(;) 4. 5. int main() 6. { 7. int a = 10; 8. PRINT(a); 9. int b = 20; 10. PRINT(b); 11. printf("the value of " "b" " is %d\n", b);//the value of 是一个字符串,b是一个字符串,is %d\n 是一个字符串 12. return 0; 13. }
注意:字符串是有自动连接的特点
## 可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
1. #include <stdio.h> 2. #define CAT(Class, num) Class##num 3. 4. int main() 5. { 6. int Class1 = 100; 7. printf("%d\n", CAT(Class, 1));//Class1 8. return 0; 9. }
打印结果:100
3.2.5 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
1. #include <stdio.h> 2. 3. int main() 4. { 5. int a = 2; 6. int b = 0; 7. b = ++a;//a = 3; b = 3//这个就有副作用 8. a = 2; 9. b = a + 1;//a = 2; b = 3 这个没有副作用 10. return 0; 11. }
1. #include <stdio.h> 2. #define MAX(x, y) ((x) > (y)? (x): (y)) 3. int main() 4. { 5. int a = 3; 6. int b = 5; 7. int m = 0; 8. m = MAX(a++, b++);//((a++) > (b++) ? (a++) : (b++))后置++,先使用后++ 3>5 a=4 b=6 m=5 b=7 9. printf("%d\n", m); 10. printf("a = %d, b = %d", a, b); 11. return 0; 12. }
m=6 a=4 b=7
注意是替换
3.2.6 宏和函数对比
宏通常被应用于执行简单的运算
1.用于调用函数和从函数返回的代码可能比实际执行小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹 。
2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之 宏是类型无关的 。
缺点: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。2. 宏是没法调试的。(宏是替换)(把代码插入到相应的位置)3.宏由于类型无关,也就不够严谨4.宏可能会带来运算符优先级的问题,导致程容易出现错误。
宏的参数可以出现类型,但是函数不可以
1. #include <stdio.h> 2. #define MALLOC(num,type) (type*)malloc(num*sizeof(type)) 3. int main() 4. { 5. int* p1 = (int*)malloc(10 * sizeof(int)); 6. int* p2 = MALLOC(10, int); 7. //p1 p2效果一样 8. return 0; 9. }
3.2.7 命名约定
习惯:把宏名全部大写 函数名不要全部大写
3.3 #undef
这条指令用于移除一个宏定义。
#undef NAME
// 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
1. #include <stdio.h> 2. #define MAX(x, y) ((x) > (y) ? (x) : (y)) 3. 4. int main() 5. { 6. #undef MAX 7. int m = 0; 8. m = MAX(2, 3);//此时,这个代码无法用MAX这个宏 9. printf("%d\n", m); 10. return 0; 11. }
3.4 命令行定义
许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 编译时进行赋值,代码中并没有进行赋值
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)
3.5 条件编译
条件编译指令:在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃编译
比如:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
1. #include <stdio.h> 2. int main() 3. { 4. int i = 0; 5. for (i = 0; i < 10; i++) 6. { 7. #if 0 8. //1的时候就可以运行,不能是变量 9. printf("%d\n", i);//此时,这个代码就不运行了 10. #endif 11. //这是预处理命令 12. //其他 13. } 14. return 0; 15. }
常见的条件编译指令:
1.
#if 常量表达式
//...
#endif
// 常量表达式由预处理器求值。
2. 多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3. 判断是否被定义
(1)定义
#if defined(symbol)
//...
#endif
例如:#define M 100;symbol就是指M
(2)定义
#ifdef symbol
//...
#endif
(3)没有定义
#if !defined(symbol)
//...
#endif
(4)没有定义
#ifndef symbol
//...
#endif
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
3.6 文件包含
#include 指令可以使另外一个文件被编译。
预处理器先删除这条指令,并用包含文件的内容替换。 如果一个源文件被包含10 次,那就实际被编译 10 次。
3.6.1 头文件被包含的方式
(1)本地文件包含 : #include "filename"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
准位置查找头文件。
(2)库文件包含 : #include <filename.h>
查找头文件直接去标准路径下去查找
库文件也可以用""的形式包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
3.6.2 嵌套文件包含
如果文件被多次包含,这样就会造成文件内容的重复
解决办法:
(1)
#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif //__TEST_H__ 根据文件名改变
(2)
#pragma once
以上两种办法可以避免头文件的重复引入