1.预定义符号
C语言设置了一些预定义的符号,可以直接使用,预定义符号也是在预处理期间处理的。
__FILE__ ---%s //进行编译的源文件
__LINE__ ---%d //文件当前的行号
__DATE__ ---%d //文件被编译的日期
__TIME__ ---%d //文件被编译的时间
__STDC__ ---%d //如果编译器遵循ANSI C,其值为1,否则未定义
vs不支持STDC
2.#define定义
宏:允许把参数替换到文本中
基本语法:
#define name变量名(参数列表) stuff内容
其中,参数列表是一个由逗号隔开的符号表,他们可能出现在内容中
注意:参数列表的左括号必须与name紧邻,如果两者之间由空格,参数列表会被解释为stuff的一部分
举例:
#define SQUARE(x) (x)*(x)
这个宏接受一个参数x,如果在上述声明之后,预处理器会用x*x直接替换SQUARE(x)。所以对于数值表达式进行求值的宏定义应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
3.带有副作用的宏参数
当宏参数在宏定义中出现超过一次时,如果参数带有副作用,那么使用时可能出现危险,导致不预测的后果,副作用是表达式求值时出现的永久性效果。
例如:
x+1;//不带副作用
x++;//带有副作用
4.宏替换规则
在程序中扩展#define定义符号和宏时,涉及以下步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果有,会先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件扫描
注意:宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。
5.宏和函数对比
宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个时,写成下面的宏,更有优势一些。
#define MAX(a,b) ((a)>(b)?(a):(b))
- 由于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。
- 函数的参数必须声明为特定类型,而宏可以使用整形,长整型,浮点型等这些可以用>来比较的类型,宏的参数是类型无关的。
和函数相比宏的劣势:
- 每次使用宏,需要将宏定义的代码插入程序中,太长了会增加程序长度。
- 宏是没法调试的
- 宏与类型无关,不够严谨
- 宏可能会带来运算符优先级问题
#define MALLOC(n,type) (type*)malloc(n*sizeof(type))
int main()
{
int *p = MALLOC(10,int);
//相当于int *p = (int*)malloc(10 * sizeof(int));
}
实现逻辑比较简单,使用宏
6.#和##
6.1#运算符
#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为”字符串化“
#define PRINT(format,n) printf("the value of "#n" is " format "\n",n) int main() { int a = 10; PRINT("%d", a); //相当于printf("the value of " "a" " is ""%d""\n", a); }
这里 #n 就转换为 a 输出
6.2 ##运算符
## 可以把位于他两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。##被称为记号粘合,这样的连接必须产生一个合法的标识符,否则其结果就是未定义的。
//##运算符用法——求两个数较大值
//生成函数的模板
#define GENERIC_MAX(type)\
type type##_max(type x,type y)\
{\
return x > y ? x : y; \
}
//使用这个模板生成名字叫:int_max的函数
GENERIC_MAX(int)//没有分号
/*相当于
int int_max(int x, int y)\
{
return x > y ? x : y; \
}*/
int main()
{
printf("%d\n", int_max(3, 5));
}
7.命名约定
一般来说函数的宏的使用语法很相似。语言本身没法区分,所以我们一般
把宏名全部大写
函数名不要全部大写
8.#undef移除宏定义
用于移除一个宏定义
#undef NAME
9.头文件
头文件包含的两种形式:
- #include<stdio.h> 库文件的包含,一般指标准库中头文件的包含
- #include"xxx.h" 本地文件包含,一般是自己创建的头文件的包含。先在源文件所在的目录下查找,如果未找到该头文件,编译器就像查找库函数头文件一样在标准位置找头文件,如果找不到就显示编译错误。
对于库文件也可以使用 “ ” ,但是效率会变低且不易区分。