文章目录
- 预定义符号
#define
- 宏和函数的对比
#
运算符和##
运算符#
undef- 条件编译
- 头文件的包含
- 避免头文件重复引用的方法
1.预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。
__FILE__
:进行编译的源文件__LINE__
:文件当前的行号__DATE__
: 文件被编译的日期__TIME__
: 文件被编译的时间__STDC__
:如果编译器遵循ANSIC,其值为1,否则未定义
2.#define
2.1#define定义常量
2.2#define定义宏
2.2.1宏的概念
#define
机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
#define name(parament-list ) stuff
- parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。
- 参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
#define SQUARE(x) x*x
int main()
{
int a = 5;
int ret = SQUARE(a);
printf("%d\n", ret);//25
return 0;
}
2.2.2宏的副作用
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
//写一个宏求两个数的最大值
//#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
int a = 3;
int b = 5;
int m1 = MAX(a, b);
printf("%d\n", m1);//5
int m2 = MAX(a++, b++);//先使用a和b的值,再++
//int m2 = ((a++)>(b++)?(a++):(b++))
// 3 5 - >运算符计算后 a变成4,b变成6 所以m2为6,用完b=6后再++变成7
printf("%d\n", m2);//6 m2的结果为6
printf("a=%d\n", a);//4 a的结果为4
printf("b=%d\n", b);//7 所以b的结果为7
return 0;
}
2.2.3宏的替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
3.宏和函数的对比
宏通常被应用执行于简单的运算
例: 在两个数中找较大值,用宏比函数更有优势
原因:
- 于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于》来比较的类型。宏的参数是类型无关的。
和函数相比宏的劣势
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
3.1宏和函数的命名
宏名全部大写
函数名不要全部大写
宏的参数可以出现参数类型
#define Malloc(n,type) (type*)malloc(n*sizeof(type))
int main()
{
//正常情况下用malloc开辟动态空间
int* p = (int)malloc(10 * sizeof(int));//开辟40个字节的空间
//利用宏来开辟空间
int* ptr = Malloc(10, int);
return 0;
}
4.#
运算符和##
运算符
4.1#
运算符
该运算符所执行的操作可以理解为字符串化
该运算符将宏的一个参数转换为字符串字面量,仅允许出现在带参数的宏的替换列表中
#define Print(n,format) printf("the value "#n" is " format"\n",n)
//#n 将n的位置替换成相对应的字符串常量 a-#n替换成"a"
int main()
{
int a = 10;
//printf("the vaule a is %d\n", a);
Print(a,"%d");
int b = 20;
//printf("the vaule b is %d\n", b);
Print(b, "%d");
float f = 5.6f;
//printf("the vaule f is %f\n", f);
Print(f, "%f");
return 0;
}
4.2##
运算符
该运算符可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符.##
被称为几号粘合
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的
#define GNEERIC_MAX(type) \
type type##_max(type x,type y)\
{ \
return x>y?x:y; \
}
//GNEERIC_MAX(int)
//int int_max(int x, int y)\
//{ \
//return x > y ? x : y; \
//}
GNEERIC_MAX(int);
GNEERIC_MAX(float);//先在主函数外引用宏,生成对应的函数在main函数内使用函数
int main()
{
int r1 = int_max(3, 5);//利用宏生成了一个函数,可以直接使用函数
printf("%d\n", r1);
float r2 = float_max(3.4f, 4.5f);
printf("%f\n", r2);
return 0;
}
5.#
undef
这条指令用于移除一个宏定义
6.条件编译
在编译一个程序的时候,如果想要将一条或者(一组语句)选择性地编译或放弃就可以使用条件编译指令
常见的编译指令有:
-
#if
#endif
-
多个分支的条件编译
#if #elif #else #endif
-
判断是否被定义
#if define(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
- 嵌套指令
#if defined(OS_UNIX) #if defined(os_UNIx)
7.头文件的包含
本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
库文件包含
#include<filename.h>
查找策略:直接去标准路径下去查找,如果找不到就提示编译错误。
对于库文件的也可以使用本地文件的包含方式,这样的弊端是:找的效率低些,也不容易区分是库文件还是本地文件了。
避免头文件重复引用的方法
- 每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
- 在头文件的顶部加入:
#pragma once