C语言中预处理阶段

本文详细介绍了C语言的预处理阶段,包括头文件的作用与影响,如如何避免重复包含;宏定义的使用,如常量、表达式宏及其优缺点;条件编译的概念及解决重复包含头文件的方法;以及#pragma指令的使用,如#pragma once和#pragma warning。通过实例展示了多文件编译的情况,帮助理解C语言的编译流程和最佳实践。
摘要由CSDN通过智能技术生成


预处理阶段,预处理器执行包含指定头文件(将源文件中以"#include"包含的文件复制到编译的源文件中)、宏替换(用字面值替换"#define"定义的标识符)以及条件编译(根据“#if”后面的条件决定需要编译的代码),以#开头的命令行就是预处理器处理的对象。这些命令行的语法独立于语言的其他部分,它们可以出现在任何地方,其作用延续到所在翻译单元的末尾(与作用域无关)。

1.头文件定义

(1)基本概念

头文件当中一般包含条件编译、宏定义、函数声明、结构体定义、类型定义以及#开头的一些预处理命令等等。
**【注意】**头文件一般只进行声明,定义放入相应的源文件中。
test.h头文件

//条件编译
#ifndef _TEST_H_
#define _TEST_H_

//结构体定义
struct Student
{
   
    char ch;
    int num;
    double score;
};

//定义新的类型
typedef struct Student STU;

//定义全局变量
int global;

//声明函数
void print_messaga(STU *p);

#endif 

test.c

/*
   #include <文件名> 的命令行,直接从系统路径中查找对应的头文件。
   #include "文件名" 的命令行,先从当前路径下查找头文件,如果当前路径查找不到,就去系统路径当中查找。
*/
#include <stdio.h>
#include "test.h"

void print_message(STU *p)
{
   
    printf("%c  %d  %lf\n", p->ch, p->num, p->score);
}

int main() 
{
   
    STU stu = {
   'm', 12, 99.5};
    
    print_message(&stu);

    return 0;
}

运行结果:
在这里插入图片描述

(2)补充

(a)头文件对编译效率的影响

某产品做过一个实验,把所有函数的实现通过工具注释掉,其编译时间减少了不到10%,究其原因,在于头文件A包含了B,B包含了C,C包含了D,最终几乎每一个源文件都包含了项目组所有的头文件,从而导致了巨大部分编译时间都花在解析头文件上面了。
对编译影响最大的是预处理阶段解析头文件的过程,
因为这个过程可以拆分成这几个步骤:
1、搜索头文件位置
2、打开头文件
3、读入头文件
4、关闭头文件
所以头文件过多,或者头文件依赖(包含头文件)过多,是很耗时间的,降低编译的效率。
对于小项目来说,可能编译过程最耗时间;但对于大型工程项目,头文件绝对最耗时间的,对编译效率影响最大。

(b)文件包含#include

文件包含有两种格式:
• #include <文件名> 直接从系统路径中查找对应的头文件
• #include “文件名” 先从当前路径下查找头文件,如果当前路径查找不到,就去系统路径当中查找。
• 使用#include时,被包含文件可以是绝对路径,也可以是相对路径,总之,只要头文件的存放路径与当前源文件的关系正确即可。

一个源文件包含了一个头文件,这个源文件就可以使用该头文件定义的内容。
注意】#include不仅仅能包含.h类型的头文件,理论上它可以包含任意类型的文件,例如包含一个.c文件等,但我们通常都用于包含.h类型的头文件。

2.宏定义

(1)基本概念

• 预处理器#define 定义的是不带数据类型的常数,只进行简单的字符替换。 在预编译的时候起作用,不存在类型检查。
• 宏定义,又称宏替换,用#define定义宏,指的是用字面值直接替换#define定义的标识符(用大写形式表示)。
一般形式:#define 标识符 字面值
注意】字面值一般为整型/浮点型/字符型/字符串常量;标识符用大写形式表示,以和变量名作区分。

(2)宏定义常量

• #define 定义的宏常量可以直接使用。字面值一般为整型/浮点型/字符型/字符串常量);标识符为大写形式表示。
• 在预处理阶段直接将定义的宏进行文本替换
对上面的test.h做简单修改如下:

#include <stdio.h>
#include "test.h"
#define SEX  'm'
#define NUM  100
#define SCORE 99.5
#define PRINT "Show current message!"

void print_message(STU *p)
{
   
    printf("%c  %d  %lf\n", p->ch, p->num, p->score);
}

int main() 
{
   
    STU stu = {
   SEX, NUM, SCORE};
    
    print_message(&stu);
    printf("%s\n", PRINT);

    return 0;
}

运行结果:
在这里插入图片描述

(3)宏定义的表达式(带参数和不带参数)

• #define 定义带参数的表达式称为宏函数,但没有任何调用开销,因为宏定义不分配内存 。并且#define 定义的表达式在有些时候比函数更加强大。
• 宏函数,其字面值是某种表达式,标识符用大写形式表示,后面有括号和对应的宏函数形参,宏函数形参没有数据类型,且宏函数用实参完全替代形参,不进行任何运算。
• #define 表达式很容易出错,一定要对整个表达式套上括号。注意,宏定义只是简单替换,不能出现递归定义。
代码分析:

#include <stdio.h>
#define MAX(a,b) (a > b?a:b)

int main() 
{
   
    printf("a & b of max is %d\n", MAX(100,10));

    return 0;
}

(4)总结

• 预处理器直接对宏进行替换
• 预处理器不会对宏定义进行语法检查
• 宏定义时出现的语法错误,只能被编译器检测到
• 宏定义的效率高于函数调用
• 宏定义容易出错

3.条件编译

(1)基本概念

条件编译类似于C语言的if……else……,是一种预编译指示指令,用于控制是否预编译某段代码。在预编译阶段,将条件编译指令处理完了,就只剩下条件编译中为真的那段代码。

(2)条件编译的指令

(a)

#define  TEST   //undef	TEST
#ifdef   TEST   //ifndef	TEST	
    代码1
#else 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值