1.预处理
1.1#define定义标识符
举个例子:
#define MAX 1000 //将代码中的MAX直接替换成1000
#define reg register //为 register这个关键字,创建一个简短的名字
我们在#define定义标识符的时候需要在后面加上:(分号)吗?如果我们不知道该不该加的时候,不妨来尝试一下加了会有什么后果,看下面的例子:
#define MAX 100;
int main() {
printf("%d\n", MAX);
//相当于 printf("%d\n",100;);
return 0;
}
这段代码根本无法编译,不符合语法规则。这种类似的例子还有很多,所以我们在#define定义标识符时不要带上;(分号)。
1.2#define定义宏
举个例子:
#define SQUARE(x) x*x
这种定义会存在一个问题,
printf("%d\n",SQUARE(1+4));
这个宏的功能是用于求平方,这句代码的本意是想求5的平方,但最后的输出结果却不是这样的,结果是9,这是因为#define的规则是进行替换,上面的代码会直接替换为:
printf("%d\n",1+4*1+4);
如果我们想求的是5的平方,我们需要在定义时加上括号以避免运算符的优先级带来的副作用。正确的定义如下:
#define SQUARE(x) ((x)*(x))
1.3#define的替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。
我们来看一个例子:
#define ptr1 int*
typedef int* ptr2;
int main() {
ptr1 p1, p2;
ptr2 p3, p4;
return 0;
}
在这段代码中,p1、p3、p4都是int*类型,p2是int类型,因为#define是单纯的替换,而typedef就相当于是一个重命名。
1.4宏和函数对比
属性 | #define定义宏 | 函数 |
---|---|---|
代 码 长 度 | 每次使用时,宏代码都会被插入到程序中。除了非 常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码 |
执 行 速 度 | 更快 | 存在函数的调用和返回的额外开 销,所以相对慢一些 |
操 作 符 优 先 级 | 宏参数的求值是在所有周围表达式的上下文环境 里,除非加上括号,否则邻近操作符的优先级可能 会产生不可预料的后果,所以建议宏在书写的时候 多些括号。 | 函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测。 |
带 有 副 作 用 的 参 数 | 参数可能被替换到宏体中的多个位置,所以带有副 作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一 次,结果更容易控制。 |
参 数 类 型 | 宏的参数与类型无关,只要对参数的操作是合法 的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 不同的。 |
调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递 归 | 宏是不能递归的 | 函数是可以递归的 |
1.5条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件 编译指令。
常见的条件编译指令:
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.嵌套指令
#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
1.6文件包含
我们知道两种文件包含的格式,#include<add.h>,#include"add.h",这两种包含的区别在于,前者直接去库中找对应的文件,而后者会先在工程中找相应的文件,找不到的时候再去库中找,所以我们一般引用头文件的规则是:若是我们自己写的头文件,则用“ ”来引用,若是需要库中的头文件,则用< >来引用。
很多时候,难免会出现头文件的重复包含,当重复包含时,我们有两种解决方法:
每个文件开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者:
#pragma once
这样就可以避免重复包含头文件的问题了。