首页 > 其他分享 >12. 预处理

12. 预处理

时间:2023-03-07 12:11:56浏览次数:26  
标签:__ 语句 12 定义 替换 编译 printf 预处理

一、什么是预处理命令

  在编译程序之前对源文件进行简单的加工的过程就称为 预处理。预处理主要是处理以 # 开头的命令。预处理 是 C语言 的一个重大的功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。

  C语言 提供了多种预处理功能,如 宏定义、文件包含、条件编译 等,合理地使用预处理会使编写的程序便于阅读、修改、移植 和 调试,也有利于模块化程序设计。

二、宏定义

  使用 #define 命令就是要定义一个可替换的宏。宏定义是预处理命令的一种,它提供了一种可以替换源代码中字符串的机制。根据宏定义中是否有参数,可以将宏定义分为不带参数的宏定义和带参数的宏定义两种。

2.1、不带参数的宏定义

  宏定义指令 #define 用来定义一个标识符和一个字符串,以这个标识符来代表这个字符串,在程序中每次遇到标识符时就会用所定义的字符串替换它。宏定义的作用相当于给指定的字符串起一个别名。

  宏定义不带参数的一般格式如下:

#define 宏名 字符串
  • # :表示这个一条预处理命令;
  • 宏名:是一个标识符,必须符合 C语言 标识符的规定;
  • 字符串:字符串中国可以含任何字符,可以是常数、表达式、格式化字符串、函数等,预处理程序对它不做任何检查,如有错误,只能在编译已经被展开后的源程序时发现;

  在预处理阶段,对程序中所有出现“宏名”的位置,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。宏定义是由源程序中的宏定义命令 #define 完成的,宏替换是由预处理程序完成的。

#include <stdio.h>

#define PI 3.14

int main()
{
    double radius = 0;
    double perimeter = 0;
    double area = 0;

    printf("请输入圆的半径:");
    scanf("%lf",&radius);

    perimeter = 2*pi*radius;
    printf("圆的周长为:%.2f\n",perimeter);

    area = pi*radius*radius;
    printf("圆的面积为:%.2f\n",area);

    return 0;
}

宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一个简单的替换;

宏定义不是 C 语句,不需要在行末加分号,如果加上分号则连分号也一起替换;

宏名定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开的是由预处理程序层层嵌套;

如果在字符串中含有宏名,则不进行替换;

宏定义用于预处理命令,它不同于定义的变量,它只做字符替换,不分配内存空间;

2.2、带参数的宏定义

  带参数的宏定义不只是简单的字符串替换,还要进行参数的替换,其一般形式如下:

#define 宏名(参数) 字符串
#include <stdio.h>

#define MAX(a,b) ((a>b)?a:b)

int main()
{
    int num1 = 10,num2 = 20;

    printf("num1: %d, num2 = %d\n",num1,num2);
    printf("the max number is: %d\n",MAX(num1,num2));

    return 0;
}

宏定义时,字符串内的形参要加括号,如果不加括号,其结果可能出错。

宏扩展时,必须使用括号来保护表达式中低优先级的操作符,以确保调用时能够达到想要的效果。

在对带参数的宏展开,只是将语句中的宏名后面内的实际参数字符串代替 #define 命令行中的形式参数。

在宏定义时,形参之间可以出现空格,宏名与参数的括号之间不可以加空格,否则会将空格以后的字符都作为替代字符串的一部分。

在带参数的宏定义中,形式参数不会分配内存单元,因此不必指定数据类型,而在宏调用,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型;

  带参宏定义和函数的区别

  宏展开仅仅是字符串的替换,不会带表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。用宏替换代替替代的函数的好处是宏替换增加了代码的速度,因为不存在函数调用,但是可以存在多次宏替换从而提高了代码的长度;

三、include指令

  在一个源文件中使用 #include 指令可以将另一个源文件的全部内容包含进来,也就是将另外的文件包含到本文件中。#include 使编译程序将另一源文件嵌入带有 #include 的源文件中,被读入的源文件必须用 双引号 或者 尖括号 括起来。

#include <头文件名.h>
#include "头文件名.h"

  使用 尖括号 时,系统带存放C库函数头文件所在的目录中寻找要包含的文件;使用 双引号 时,系统先在用户当前目录中寻找要包含的文件,若找不到,再到存放C库函数头文件所在的目录中寻找要包含的文件。

  通常情况下,如果为调用库函数用 #include 命令来包含相关的头文件,则用尖括号可以节省查找的时间。如果要包含的时用户自己编写的文件,一般用双引号,用户自己编写的文件通常是在当前目录中,如果文件不在当前目录中,双引号中可以给出为文件路径。

  经常用在文件头部的被包含的文件成为“标题文件”或“头部文件”,一般以 .h 为后缀。一般我们会把以下内容放在头文件中:

  • 宏定义
  • 结构体、联合体 和 枚举声明
  • typeof 声明
  • 外部函数声明
  • 全局变量声明

一个 #include 命令只能指定一个被包含的文件;

文件包含是可以嵌套的,即在一个被包含文件中还可以包含另一个被包含的文件;

四、条件编译

  预处理器提供了条件编译功能,一般情况下,源程序中所有的行都参与编译,但是有时候希望只对其中一部分内容在满足一定条件下时才进行编译,这时就需要使用到一些条件编译命令。使用条件编译可方便地处理程序的调试版本和正式版本,同时还会增强程序的可移植性。

4.1、#if、#elif、#else命令

  #if 的基本含义是:如果 #if 命令后的参数表达式为真,则编译 #if#endif 之间的程序段,否则跳过这段程序。#endif 命令用来表示 #if 段的结束。#if 命令的一般格式如下:

#if 常量表达式
    语句段
#endif

  #else 的作用是为 #if 为假时提供另有一种选择:

#if 常量表达式
    语句段1
#else
    语句段2
#endif

  #elif 指用用来建立一种“如果...或者如果...”这样阶梯状多重编译操作选择:

#if 常量表达式1
    语句段1
#elif 常量表达式2
    语句段2
...
#elif 常量表达式n
    语句段n
#endif
#include <stdio.h>

#define NUM 10

int main()
{
    #if NUM < 10
        printf("该数比10小\n");
    #elif NUM > 10
        printf("该数比10大\n");
    #else
        printf("该数等于10\n");
    #endif

    printf("该数为:%d\n",NUM);

    return 0;
}

4.2、#ifdef、#ifndef命令

  #ifdef#ifndef 命令,分别表示“如果有定义”与“如果没有定义”。#ifdef 的一般形式如下:

#ifdef 宏替换名
    语句段
#endif

  其含义是:如果宏替换名已经定义过,则对“语句段”进行编译;如果为定义 #ifdef 后面的宏替换名,则不对语句进行编译;

  #ifdef 可以与 #else 连用,构成的一般形式如下:

#ifdef 宏替换名
    语句段1
#else
    语句段2
#endif

  其含义是:如果宏替换名已经被定义过,则对“语句段1”进行编译;如果未定义 #ifdef 后面的宏替换名,则对“语句段2”进行编译。

#include <stdio.h>

#define STR "Believe yourself\n"

int main()
{
    #ifdef STR
        printf(STR);
    #else
        printf("Don't give up\n");
    #endif

    return 0;
}

  #ifndef 的一般格式如下:

#ifndef 宏替换名
    语句段
#endif

  其含义是:如果未定义 #ifndef 后面的宏替换名,则对“语句段”进行编译;如果定义 #ifndef 后面的宏替换名,则不执行语句段。

  同样,#ifndef 也可以与 #else 连用,构成的一般形式如下:

#ifndef 宏替换名
    语句段1
#else
    语句段2
#endif

  其含义是:如果未定义 #ifndef 后面的宏替换名,则对“语句段1”进行编译;如果定义 #ifndef 后面的宏替换名,则对“语句段2”进行编译。

#include <stdio.h>

int main()
{
    #ifndef STR
        printf("Don't give up\n");
    #else
        printf(STR);
    #endif

    return 0;
}

4.3、#undef命令

  使用 #undef 命令可以删除事先定义的宏。#undef 命令的一般格式如下:

#undef 宏替换名
#include <stdio.h>

#define STR "Believe yourself\n"

int main()
{
    #ifdef STR
        printf(STR);
    #else
        printf("Don't give up\n");
    #endif

    #undef STR

    #ifndef STR
        printf("Don't give up\n");
    #else
        printf(STR);
    #endif

    return 0;
}

undef 的主要目的是将宏名局限在仅需要它们的代码段中;

4.4、#line命令

  #line 命令可以改变 __LINE____FILE__ 的内容,__LINE__ 当前编译行的行号,__FILE__ 存放当前编译的文件名。#line 命令的一般形式如下:

#line 行号["文件名"]

  其中,行号为任一正整数,可选的文件名为任意有效文件标识符,行号为源程序中当前行号,文件名为源文件的名字。#line 命令主要用于调试及其它特殊应用。

#include <stdio.h>

#line 100 "test.c"

int main()
{
    printf("当前编译的文件:%s\n",__FILE__);
    printf("当前行号为:%d\n",__LINE__);

    return 0;
}

  ANSI 标准说明了以下 5 个预定义宏替换名:

  • __LINE__:其含义是当前被编译代码的行号;
  • __FILE__:其含义是当前源程序的文件名称;
  • __DATA__:其含义是当前源程序的创建日期;
  • __TIME__:其含义是当前源程序的创建时间;
  • __STDC__:其含义是用来判断当前编译器是否为标准C,若其值为 1 则表示符合标准C,否则不是标准C;

4.5、#parama命令

  #parama 命令的作用是设定编译器的状态,或者指示编译器完成一些特定的动作。#parama 命令的一般形式如下:

#param 参数

  其中,参数可以分为以下几种:

  • messaage 参数能够在编译信息输出窗口中输出相应的信息;
  • cod_seg 参数设置程序中函数代码存放的代码段;
  • once 参数保证头文件被编译一次

标签:__,语句,12,定义,替换,编译,printf,预处理
From: https://www.cnblogs.com/kurome/p/17187593.html

相关文章

  • PAT Basic 1012. 数字分类
    PATBasic1012.数字分类1.题目描述:给定一系列正整数,请按要求对数字进行分类,并输出以下5个数字:\(A1\)=能被5整除的数字中所有偶数的和;\(A2\)=将被5除后余......
  • UVA-212 医院设备利用 题解答案代码 算法竞赛入门经典第二版
    ​​GitHub-jzplp/aoapc-UVA-Answer:算法竞赛入门经典例题和习题答案刘汝佳第二版​​这也是一道根据根据时间做出行为的题目,我的做法和之前的UVA822类似,也是用优先队......
  • UVA-12333 Fibonacci的复仇 题解答案代码 算法竞赛入门经典第二版
    ​​GitHub-jzplp/aoapc-UVA-Answer:算法竞赛入门经典例题和习题答案刘汝佳第二版​​算法竞赛入门经典书中给出了大数类的算法,直接照抄即可。我的做题过程:1.照着书......
  • 12:swift-下标
      正文 /*1:下表1.1:类、结构体和枚举可以定义下标,它可以作为访问集合、列表或序列成员元素的快捷方式。1.2:你可使用下标通过索引值来设置或检索值而不......
  • 12_JDBC
    一.JDBC简介1.什么是JDBC?JDBC就是使用java语言来操作关系型数据库的一套API官方sun公司定义的一套操作所有关系型数据库的规则,即接口;2.JDBC的好处?可随时更换底层......
  • 7 odoo12 全屏模块开发
    全屏功能开发:1定义web模板t-name=”WebFullScreen”,预置全屏和退出全屏的a标签,退出全屏默认隐藏<?xmlversion="1.0"encoding="UTF-8"?><templatexml:space="pre......
  • Apinto V0.12 发布:新增流量镜像与 Mock 插件,路由特性更丰富!
    Hello~各位开发者朋友们好呀,Eolink旗下开源网关Apinto本周又更新啦!这次的更新我们给大家带来了2个好用的插件,且目前已经支持静态资源路由了!希望新的功能能让大家的......
  • P1273 有线电视网
      f[u][j]=max(f[y][k]+f[u][j-k]-w[i])#include<bits/stdc++.h>usingnamespacestd;constintN=3002,M=N*5,inf=0x7f7f3f;intn,m,sz[N];inta[N],n......
  • ORA-12528: TNS: 监听程序: 所有适用例程都无法建立新连接
    今天通过DatabaseControl日常查看Oracle数据库情况,结果报错了:ORA-12528:TNS:listener:allappropriateinstancesareblockingnewconnections然后百度,看......
  • thinpphp6+php8.0.2链接PGSQL12
    1、提示   如果是pgsql12以下的执行CREATEORREPLACEFUNCTIONpgsql_type(a_typevarchar)RETURNSvarcharAS$BODY$DECLAREv_typevarchar;BEGINIF......