一、预处理的定义
在C语言学习中,我们通过预处理这节课了解到;我们用C语言直接写出来的代码是不能被计算机进行识别的,需要被翻译为二进制语言才能被计算机收到并执行;这个过程就叫做翻译。
翻译过程一般有以下几步:
预处理:头文件展开,去注释,宏替换,条件编译等。
编译:将C语言翻译成汇编语言
汇编:将汇编语言转化为可重定向目标文件(可被链接,已经是二进制,但不是可执行文件)
链接:自身程序+库文件进行关联,形成可执行程序
二、头文件展开
在编写C程序时,我们常常需要包括许多头文件;为了达到这个目的,我们常常会使用预处理指令#include来实现。在程序进行预处理时,就会将头文件的内容复制到我们的代码文件中,以便进行函数及变量的调用。
常见的头文件预处理代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
对于#include的使用,有两种实现形式:
1.使用尖括号<>,编译器会从系统路径寻找头文件
三、去注释
在写C语言程序中我们常常会因为便于代码理解而增加一点注释,这些注释在预处理阶段会进行替换;编译器会将这些注释全部更改为空格。
注:在C语言中,我们有两种写注释的写法//和/**/
四、宏替换
4.1什么是宏
在C语言中,一般使用#define来定义宏,这个命令允许把一个名称指定成为任何所需的文本,比如替换为一个常量值。定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会将其用定义时指定的文本替换掉。
代码示例:
#include <stdio.h>
#define PI 3.14159 #define SQUARE(x) ((x) * (x)) #define CIRCLE_AREA(r) (PI * SQUARE(r))
int main() { float radius = 5.0; printf("The area of a circle with radius %f is %f\n", radius, CIRCLE_AREA(radius)); return 0; }
在这个例子中,我们定义了三个宏:
PI 是一个代表圆周率的宏。
SQUARE(x) 是一个计算平方的宏。
CIRCLE_AREA(r) 是一个计算圆面积的宏,它使用了 PI 和 SQUARE(x) 宏。
在 main 函数中,我们使用CIRCLE_AREA 宏来计算半径为 5.0 的圆的面积,并打印结果。
4.2 宏的作用范围
源文件的任何地方都可以定义宏,与是否在函数内外无关,我们习惯将其写在最上方。宏的作用范围是从定义处往后都是有效的,之前无效。#undef可以提前结束宏的作用范围。例:
#include <stdio.h>
#define PI 3.14159 #define SQUARE(x) ((x) * (x)) #define CIRCLE_AREA(r) (PI * SQUARE(r))
int main() { float radius = 5.0; printf("The area of a circle with radius %f is %f\n", radius, CIRCLE_AREA(radius));
#undef PI
// 在这里,PI 宏已经被取消定义
// 尝试再次使用 PI 宏,将会导致编译错误
// printf("PI is now: %f\n", PI); // 这行代码会导致编译错误
return 0;
}
4.3.#和##
#和##是C语言中的预处理指令,它们只能在宏定义中使用。功能如下:
#:当在宏定义中出现使用#参数的形式,就是将参数的字面值转换为字符串。如#define STR(s) #s ,这个宏作用就是把s的字面值转为字符串常量。
##:如果出现aa##bb##cc这种使用双井号定义的宏,作用就是形成一个新的符号aabbcc,注意是符号而不是字符串。
代码示例:
#include <stdio.h>
#define PI 3.14159 #define SQUARE(x) ((x) * (x)) #define CIRCLE_AREA(r) (PI * SQUARE(r)) #define SUM_OF_SQUARES(x, y) (SQUARE(x) + SQUARE(y)) //SUM_OF_SQUARES宏使用#运算符来计算两个数的平方和 #define CONCAT(x, y) x##y //而CONCAT宏使用##运算符来拼接两个变量名。
int main() { float radius = 5.0; printf("The area of a circle with radius %f is %f\n", radius, CIRCLE_AREA(radius));
int a = 3, b = 4;
printf("The sum of squares of %d and %d is %d\n", a, b, SUM_OF_SQUARES(a, b));
int ab = CONCAT(a, b); // 定义了一个名为ab的整数变量,其值为34
printf("The value of ab is %d\n", ab);
return 0;
}
4.4宏替换与去注释
4.4宏替换与去注释
代码示例:
#include<stdio.h>
#define ABS //
int main()
{
ABS printf("hello");
}
在这段代码中: 我们定义了一个ABS宏为//,并在主函数中使用了这个宏。我们不妨来猜测以下程序是否会输出hello字符串?
如果宏替换先于去注释,则会先将ABS替换为//,然后将printf语句注释掉,最后将注释变为空格,程序不会输出。 如果去注释先于宏替换,则会先将//后本行的内容作为注释改为空格,最后ABS宏相当于一个空格,宏替换时将ABS宏文本替换为空格,程序输出字符串hello。
运行结果:
结果输出为hello,说明去注释优先于宏替换进行。
五、条件编译
5.1条件编译的定义
一般情况下,源程序中所有的非注释行都需要参加编译。但是有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”。使用条件编译后,在预处理阶段,如果使用条件编译的代码块满足条件,则保留;如果不满足条件,则预处理器就会将代码裁剪,使其不会在后续阶段被编译。
C语言中常见的条件编译指令有#ifdef,#ifndef,#if,#else,#elif,#if defined(),#endif
5.2条件编译的使用
1.使用#ifdef和#ifndef来检查宏是否已定义:
#ifdef DEBUG printf("Debug mode is enabled.\n");
#endif
#ifndef RELEASE printf("This is not a release build.\n");
#endif
2.使用#if、#elif和#endif来根据常量表达式的值包含或排除代码块:
#define OPTION 1
#if OPTION == 1 printf("Option 1 is selected.\n");
#elif
OPTION == 2 printf("Option 2 is selected.\n");
#else printf("Invalid option.\n");
#endif
3.使用defined运算符来检查宏是否已定义:
#if defined(DEBUG) && defined(RELEASE)
printf("Both debug and release modes are enabled. This is not allowed.\n");
#elif
defined(DEBUG) printf("Debug mode is enabled.\n");
#elif defined(RELEASE)
printf("Release mode is enabled.\n");
#else printf("No special mode is enabled.\n");
#endif
4.使用#undef来取消宏的定义:
#define DEBUG
#ifdef DEBUG printf("Debug mode is enabled.\n"); #undef DEBUG #endif
#ifdef DEBUG printf("This line will not be printed because DEBUG is undefined now.\n"); #endif
5.使用条件编译来包含不同平台的代码:
#ifdef _WIN32
#include <windows.h>
printf("This is a Windows system.\n");
#elif APPLE #include <TargetConditionals.h>
#if TARGET_OS_MAC printf("This is a Mac system.\n");
#endif
#elif linux
#include <unistd.h>
printf("This is a Linux system.\n");
#else printf("Unknown operating system.\n");
#endif
5.3条件编译的作用
一个C工程可能有多个.c文件,而.c文件可能又包含多个.h文件,难免会出现头文件重复包含的现象。这将会导致大量重复的代码被拷贝至我们的文件中,后期编译时会大大降低效率。因此我们可以使用条件编译来防止头文件重复包含。