一:概述
前面各章中,已经多次使用过#include命令。使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。
预处理主要是处理以#开头的命令,例如#include <stdio.h>等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。我们把 C 预处理器(C Preprocessor)简写为 CPP。所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
下面列出了所有重要的预处理器指令:
指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
二:#include命令
文件包含命令,主要用来引入对应的头文件。#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
#include有两种使用方式:
- #include <stdio.h>
- #include “myHeader.h”
使用尖括号<>和双引号""的区别在于头文件的搜索路径不同,包含标准库的头文件建议用尖括号,包含自定义的头文件建议用双引号。
说明:
一个#include命令只能包含一个头文件,多个头文件需要多个#include命令。
文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
三:C语言宏定义
宏(Macro)是预处理命令的一种,它允许用一个标识符来表示一个字符串。
先看一个例子:
#include <stdio.h>
#define N 100
int main(){
int sum = 20 + N;
printf("%d \n", sum);
return 0;
}
运行结果:120
该示例中的语句int sum = 20 + N;,N被100代替了。
#define N 100就是宏定义,N为宏名,100是宏的内容。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:#define 宏名 字符串
程序中反复使用的表达式就可以使用宏定义,例如:#define M (n*n+3*n)
它的作用是指定标识符M来表示(yy+3y)这个表达式。在编写代码时,所有出现 (yy+3y) 的地方都可以用 M 来表示,而对源程序编译时,将先由预处理程序进行宏代替,即用 (yy+3y) 去替换所有的宏名 M,然后再进行编译。
对宏定义的几点说明:
-
宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
-
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
-
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。例如:
#define PI 3.14159 int main(){ // Code return 0; } #undef PI void func(){ // Code } 表示 PI 只在 main() 函数中有效,在 func() 中无效。
-
代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:
#include <stdio.h> #define OK 100 int main(){ printf("OK\n"); return 0; } 运行结果: OK 该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。
-
宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:
#define PI 3.1415926 #define S PI*y*y /* PI是已定义的宏名*/ 对语句: printf("%f", S); 在宏代换后变为: printf("%f", 3.1415926*y*y);
-
习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
-
可用宏定义表示数据类型,使书写方便。例如:
#define UINT unsigned int
在程序中可用 UINT 作变量说明:UINT a, b;
应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
请看下面的例子:#define PIN1 int * typedef int *PIN2; //也可以写作typedef int (*PIN2); 从形式上看这两者相似, 但在实际使用中却不相同。
下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:
PIN1 a, b;
在宏代换后变成:int * a, b;
表示 a 是指向整型的指针变量,而 b 是整型变量。然而:PIN2 a,b;
表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外小心,以避出错。
四:C语言带参数宏定义
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:#define 宏名(形参列表) 字符串
,在字符串中可以含有各个形参。
带参宏调用的一般形式为:宏名(实参列表);
例如:
#define M(y) y*y+3*y // 宏定义
k = M(5); // 宏调用
实例:输出两个数中的较大的数
#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main(){
int x, y, max;
printf("please input your number: ");
scanf("%d %d", &x, &y);
max = MAX(x, y);
printf("max = %d \n", max);
return 0;
}
结果:
┌──(root💀kali)-[~/Desktop/c_test]
└─# ./hong
please input your number: 1
2
max = 2
对带参宏定义的说明:
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把:
#define MAX(a,b) (a>b)?a:b
写为:#define MAX (a,b) (a>b)?a:b
将被认为是无参宏定义,宏名 MAX 代表字符串(a,b) (a>b)?a:b
。宏展开时,宏调用语句:max = MAX(x,y);
将变为:max = (a,b)(a>b)?a:b(x,y);
这显然是错误的。 - 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
#define SQ(y) (y)*(y)
C语言带参宏定义和函数的区别:
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算,宏在编译之前就被处理掉了,没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
实例:
1)用函数计算平方值
#include <stdio.h>
int SQ(int y){
return (y*y);
}
int main(){
int x;
int i;
printf("input your number: ");
scanf("%d", &x);
i = SQ(x);
printf("%d 的平方是: %d", x, i);
}
结果:
┌──(root💀kali)-[~/Desktop/c_test]
└─# ./pingfanghanshu
input your number: 2
2 的平方是: 4
2)用宏计算平方值
#include <stdio.h>
#define SQ(y) (y*y)
int main(){
int x;
printf("input your number: ");
scanf("%d", &x);
int i;
i = SQ(x);
printf("%d 的平方是: %d", x, i);
}
结果:
┌──(root💀kali)-[~/Desktop/c_test]
└─# ./pingfanghong
input your number: 3
3 的平方是: 9
C语言条件编译:
能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
假如现在要开发一个C语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?
这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同的平台。Windows 有专有的宏_WIN32
,Linux 有专有的宏__linux__
,以现有的知识,我们很容易就想到了 if else
实例:
#include <stdio.h>
int main(){
if(_WIN32){
system("color 0c");
printf("www.qq.com \n");
}else if (__linux__)
{
printf("\033[22;31mwww.qq.com \n\033[22;30m");
}else{
printf("www.qq.com \n");
}
return 0;
}
但这段代码是错误的,在windows下报错error: '__linux__' undeclared,在Linux下报错error: ‘_WIN32’ undeclared
所以需要进行改进:
#include <stdio.h>
int main(){
#if _WIN32
system("color 0c");
printf("www.qq.com \n");
#elif __linux__
printf("\033[22;31mwww.qq.com \n\033[22;30m");
#else
printf("www.qq.com \n");
#endif
return 0;
}
结果:
┌──(root💀kali)-[~/Desktop/c_test]
└─# ./tiaojianbianyi2
www.qq.com // 红色字体
#if 命令
#if 命令的完整格式为:
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
#ifdef 命令
#ifdef 宏名
程序段1
#else
程序段2
#endif
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
#ifndef 命令
#ifndef 宏名
程序段1
#else
程序段2
#endif
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。