编译预处理&文件包含
前言:C语言中为了实现程序功能,经常使用的三类语句分别是声明语句、执行语句和编译预处理语句,所谓的编译预处理就是在对源程序进行编译之前,先对源程序的编译预处理命令进行处理,然后再将出列的结果和源程序一起进行编译,得到目标代码,具体的过程可理解如下:
本文着重对编译预处理进行记录,并分别对编译预处理的宏定义、条件编译和文件包含进行说明。
本文目录:
一、宏定义
宏定义是指将一个标识符定义为一个替换文本,在编译预处理时将这个标识符替换成对应的文本,但宏定义也分为无参宏定义和带参宏定义。
(1)无参宏定义
无参宏定义通俗来说就是无脑的替换,宏定义是什么,就替换为什么。
一般形式:
#define 标识符 替换文本
实例如下:
#include <stdio.h>
#define M (y*y + 3*y)
int main() {
int s, y;
printf("please input a number:\n");
scanf("%d",&y); //4
s = 3*M + 4*M + 5*M;
printf("s = %d\n", s); //336
return 0;
}
无参宏定义要点如下:
- 习惯上宏名用大写表示;
- 替换文本中可以包含任何字符,编译预处理时不作任何检查,正式编译时才检查;
- 宏定义不是声明和执行语句,行末不需要加分号;
- 一个#define只能定义一个宏,需要多个宏那就写多个#denfine;
- 宏定义一行写不下可以用‘\’进行续行;
- 宏定义可以写在任何地方,但通常写在函数之外,作用域从宏定义到#undef,如果没有#undef就到文件结束;
- 宏名若用双引号括起来,仅作为一般字符,如printf(“宏名”);
- 宏定义允许嵌套,只是宏展开时需要层层替换。
(2)带参宏定义
带参宏定义是在无参宏定义的基础上加了参数
一般形式:
#define 标识符(形参表) 替换文本
实例如下:
#include <stdio.h>
#define MAX(a,b) ((a>b) ? a:b)
int main() {
int x, y, max;
printf("please input two numbers:\n");
scanf("%d,%d",&x, &y); //4,5
max = MAX(x, y);
printf("max = %d\n", max); //5
return 0;
}
带参宏定义除了要满足无参宏定义的要求之外,还要补充如下:
- 宏名与后边的()不能有空格;
- 替换文本的形参要用括号括起来,避免出错;
- 宏定义可以定义多个语句,例如:#define SSSV(s1, s2, s3, s4) s1 = s1 * s1; s2=s2 * s2; s3 = s3 * s3; s4 = s4 * s4;
可能带参宏定义看起来有点像函数,但是他们之间还是有区别的:
1. 定义方式不同,带参宏定义需要#define;
2. 参数性质不同,带参宏定义不需要说明参数类型;
3. 实现方式不同,带参宏定义在编译预处理时进行,不耽误运行时间,函数是在运行时进行,耽误运行时间;
4. 参数传递不同,如果参数是表达式,带参宏定义只是进行表达式的搬运,而函数需要计算表达式的值再传递;
5. 返回值不同,带参宏定义无返回值。
这里给出一段带参宏定义与函数比较的经典代码:
#include <stdio.h>
#define SQ_MACOR(y) ((y) * (y))
int SQ_fun(int y){
return (y)*(y);
}
int main() {
int i = 1;
printf("SQ_fun:\n");
while(i<=5)
printf("%d\t", SQ_fun(i++));
printf("\n");
i = 1; //reset i
printf("SQ_MACOR:\n");
while(i<=5)
printf("%d\t", SQ_MACOR(i++));
return 0;
}
/*SQ_fun:
1 4 9 16 25
SQ_MACOR:
2 12 30*/
二、条件编译
条件编译就是在编译之前,根据条件决定编译的范围,具体来说是满足条件编译这部分,不满足条件编译那部分。条件编译有三种形式:
(1) 一般形式1
#ifdef 标识符
程序段1
#else
程序段2
#endif
实例如下:
#include <stdio.h>
#define DEBUG
int main(){
int a = 4;
#ifdef DEBUG
printf("the DEBUG has been defined");
#elif
printf("the DEBUG has not been defined")
#endif
}
(2) 一般形式2
#ifndef 标识符
程序段1
#else
程序段2
#endif
这种形式常和文件包含结合起来,防止头文件被重复包含,至于文件包含内容将在文章下一标题继续说明。
(3) 一般形式3
#if 标识符
程序段1
#else
程序段2
#endif
这种形式类似选择结构,标识符非0就编译,是0就不编译。
注意:
if后边的标识符必须是常量,如#define flag 1
三、文件包含
文件包含就是一个文件包含另一个文件的全部内容,使之成文本文件的一部分,文件包含有两种形式:
#include <文件名> //形式一
#include “文件名” //形式二
虽说在编译预处理时都是将指定文件包含进来,但这两种形式在包含头文件的时候还是有区别的,在编译预处理时,形式一只在系统规定的include子目录中找,找不到就出错,常用于标准头文件,形式二先在include子目录中找,找不到去当前工作目录找,常用于自定义头文件。
但在实际应用中,文件包含并不仅仅是引用一下这么简单,尤其是对于编写头文件来说,需要注意以下要点:
(1)与条件编译联合起来的应用
#ifndef _头文件名_h
#define _头文件名_h
写入您的头文件全部代码,注意,是全部
#endif
这样可以有效避免因为此头文件被重复包含而出问题;
(2)只编译一次的应用
#pragma once
(3)分清层级关系很重要
上边(1)和(2)的做法是虽然可以在每个头文件里都把其他头文件引用一个遍,但是这样做对于代码的后期维护来说相当麻烦。
(4)文件包含经典案例
包含关系如下:
common.h文件同时被1.h和2.h包含,所以必须使用条件编译。
(1) common.h 文件
#ifndef _common_H
#define _common_H
void output_common(){
printf("这里是共用体\n");
}
#endif //_common_H
common.h文件也可以写成:
#pragma once
void output0(){
printf("这里是共用体\n");
}
(2) 1.h 文件
#ifndef _1_H
#define _1_H
#include "common.h"
void output1(){
output_common();
printf("这里是1\n");
}
#endif //_1_H
(3) 2.h 文件
#ifndef _2_H
#define _2_H
#include "0.h"
void output2(){
output0();
printf("这里是2\n");
}
#endif //_2_H
(4) main.c文件
#include <stdio.h>
#include "1.h"
#include "2.h"
int main() {
output1();
output2();
return 0;
}
运行结果如下: