编译预处理——文件包含

编译预处理&文件包含

前言: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;
}

无参宏定义要点如下:

  1. 习惯上宏名用大写表示;
  2. 替换文本中可以包含任何字符,编译预处理时不作任何检查,正式编译时才检查;
  3. 宏定义不是声明和执行语句,行末不需要加分号;
  4. 一个#define只能定义一个宏,需要多个宏那就写多个#denfine;
  5. 宏定义一行写不下可以用‘\’进行续行;
  6. 宏定义可以写在任何地方,但通常写在函数之外,作用域从宏定义到#undef,如果没有#undef就到文件结束;
  7. 宏名若用双引号括起来,仅作为一般字符,如printf(“宏名”);
  8. 宏定义允许嵌套,只是宏展开时需要层层替换。
(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;
}

带参宏定义除了要满足无参宏定义的要求之外,还要补充如下:

  1. 宏名与后边的()不能有空格;
  2. 替换文本的形参要用括号括起来,避免出错;
  3. 宏定义可以定义多个语句,例如:#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;
}

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


在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值