1.预定义符号
C语言设置了一些预定义符号,这些符号可以被直接使用,预定义符号也是在预处理期间被处理的。
如:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSIC,其值为1,否则为定义
运行结果:
当在vs中,运行__STDC__时,则出现下面的情况:
2.#define定义常量
基本语法:
#define name stuff
例子:
#define MAX 1000
#define reg register //将关键字简写
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttimt:%s\n", \
__FILE__,__LINE__,__DATE__, \
__TIME__)
//当我们发现需要定义的stuff过长时,可以通过在每一行后面加反斜杠(\),将其分成几行来进行书写。
注:在define定义标识符时,在后面最好不要加;
3.#define定义宏
#define机制允许把参数替换到文本中,这种实现通常称为宏
下面是宏的申明方式:
#define name(parament—list) stuff
其中的parament—list是一个由逗号隔开的符号表,它们可能出现在stuff中
注:参数列表的左括号必须和name紧邻,中间不能有空白,如两者中间有空白,参数列表就会被解释成stuff的一部分。宏的参数不会运算,直接替换到宏的体内。
宏的参数中如有操作符,那宏的内容中的操作符会由于存在优先级的问题,可能会导致运算的顺序与你所期望的运算顺序不一致,会导致运算结果不达预期。因此为了解决这一问题,我们在书写宏时,会在宏定义表达式两边加上一对括号。
如下:
#define DOUBLE(n) (n)+(n)
int main()
{
printf("%d\n", 10 * DOUBLE(5));
return 0;
}
上面的代码打印的结果是100吗?
运行结果:
我们发现上述代码的运行结果是55,不是100,这是为什么呢?
我们在上面的学习中,知道了#define宏的参数不会参与运算,直接代入代码中,会得到下面的代码:
#define DOUBLE(n) (n)+(n)
int main()
{
printf("%d\n", 10 * (5) + (5));
return 0;
}
上面的运算会优先算乘法,因此结果是55。我们为了避免这种情况的发生,会在定义宏时,会在表达式两边加上括号,如下:
#define DOUBLE(n) ((n)+(n))
int main()
{
printf("%d\n", 10 * DOUBLE(5));
return 0;
}
这样运行的结果就是100了,如图:
4.带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那你在使用这个宏的时候就可能会出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性结果。
例题:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int main()
{
int x = 5, y = 8;
int m = MAX(x + 1, y + 1);
printf("x=%d y=%d m=%d\n", x, y, m);
int n = MAX(x++, y++);
printf("x=%d y=%d n=%d\n", x, y, n);
return 0;
}
通过观察上面的运行结果,我们可以发现在运行MAX(x++,y++)时,x和y的值发生了永久性的改变,这就是带有副作用的宏参数。因此在使用宏时,我们应尽量避免带有副作用的宏参数。
5.宏的替换规则
在程序中扩展#define定义符号和宏的时候,需要涉及到几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果有,它们首先会被替换掉。
- 替换文本随后被插入到程序中原来的文本位置。对于宏,参数名被它们的值所替换。
- 最后,再次对结果文件进行扫描,看看它们是否包含有任何由#define定义的符号。如果有,则重复上面的操作步骤。
注:
- 宏参数和#define定义中可以出现其他#define定义的符号。但对于宏,不能出现递归
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不会被搜索
6.宏和函数的对比
宏通产被应用于执行简单的运算
宏和函数的对比:
7.#和##
7.1#运算符
#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为“字符串化”
如下:
#define PRINT(n) printf("the value of " #n " is %d\n", n)
int main()
{
int a = 10;
PRINT(a);
return 0;
}
7.2##运算符
##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建表示符。##被称为记号粘合。
这样的连接必须产生一个合法的标识符。否则其结果就是为定义的。
如下:
#define TYPE(type) \
type type##_max(type x,type y) \
{ \
return (x>y?x:y); \
}
TYPE(int);
TYPE(float);
int main()
{
int a = int_max(3, 5);
printf("%d\n", a);
float f = float_max(3.5f, 4.5f);
printf("%f\n", f);
return 0;
}
8.函数和宏的命名约定
宏名全部大写
函数名不要全部大写
9.#undef
这条指令用于移除一个宏定义。
#undef NAME
//若现存的一个名字需要被重新定义,那么他的旧名字要先被移除
10.条件编译
在编译一个程序的时候我们如果要将一条语句编译或者放弃是很方便的。因为我们有条件编译指令。
比如:
调试性的代码,删除可惜,但保留又碍事,因此我们可以选择性的编译。
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i + 1;
#ifdef __DEBUG__
printf("%d ", arr[i]); //观察是否赋值成功
#endif
}
return 0;
}
如果要观察就可以定义__DEBUG__,不过不想观察了,就可以把 __DEBUG__删掉。
常见的条件编译指令:
1.
#if 常量表达式
// ...
#endif
//常量表达式由预处理器求值
2.多个分支的条件编译
#if 常量表达式
// ...
#elif 常量表达式
// ...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
//若被定义
#if !define(symbol)
#ifndef symbol
//若没有被定义
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPYION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
11.头文件的包含
11.1头文件被包含的方式
11.1.1本地文件包含
#include "filename.h"
查找策略:现在源文件所在的目录下查找,如果没有找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果都找不到就会提示编译错误。
11.1.2库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就会提示编译错误。
11.2嵌套文件包含
为了避免同一个头文件被多次重复引入的问题,可以使用条件编译解决这一问题。
每个头文件的开头写:
#ifdef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或
#pragma once
以上两种方式就可以避免头文件的重复引入