C语言中如何去理解预处理阶段

C语言如何去理解预处理阶段

预处理

宏定义( 无参宏定义,带参宏定义)

条件编译

文件包含

预处理操作符号和预定义宏

下面将会依次的去介绍各个阶段中的一些细节性东西

预处理

首先在C语言编译的时候,会经历以下几个步骤:预处理,编译,汇编,链接,然后生成可执行文件。整个过程是一连串动作完成的。而预处理阶段呢,也是在最先执行的一个步骤。相对来说,也是比较重要的一个步骤。

概念: 以“#”号开头的预处理指令如包含#include,宏定义制定#define等,在源程序中这些指令都放在函数之外,而且一般放在源文件的前面 ,所谓预处理其实就是在编译的第一遍扫描之前的所作的工作,预处理是C语言的一个重要的功能,它由预处理程序单独完成,当对一个源文件进行编译时,系统自动引用预处理程序,预处理在 源代码编译之前对其进行的一些文本性质的操作,对源程序编译之前做一些处理,生成扩展的C源程序

预处理阶段做了任务:
1:将头文件中的内容(源文件之外的文件)插入到源文件中
2:进行了宏替换的过程,定义和替换了由#define指令定义的符号
3:删除掉注释的过程,注释是不会带入到编译阶段
4:条件编译

预处理指令:

gcc -E bin/helloworld.i  src/helloworld.c
预处理生成的是.i的文本文件,这个文本文件是可以直接通过cat命令进行文本文件查看的

宏定义

在C语言中允许用一个标识符来表示一个字符串;称为宏,在预处理时,对程序的宏进行替换,其中宏定义是由源程序中的#define来完成 ,而宏的替换,主要是由预处理程序完成的

        #define PI 3.1415

宏定义的规则:

  • #表示一条预处理的指令,以#开头的均是预处理指令
  • #define是宏定义的指令,标识符是所定义的宏名
  • 宏名一般都是大写的字母表示,以便和变量名区别
  • 宏定义其实并不是C语言的语句,所以后面是不用去加;号
  • 宏体可以是常数,表达式,格式化字符串等,为表达式的时候应该用括号阔起来
  • 宏替换不分配内存空间,也不做正确性的检查
  • 宏的范围是从定义后到本源文件的结束,但是可以通过#undef来进行提前取消

宏定义分为有参宏定义和无参宏定义:
无参宏定义:

    语法:
        #define 标识符(宏名)[字符串]
        宏体可缺省:
            #define YES 1
            #define NO  0
            #define OUT printf("Hello world")
            #define WIDTH 80
            #define LENGTH (WIDTH+40)
        宏的移除语法
            #undef 宏名
                功能:删除前面定义的宏
                事例:
                    #undef PI
                    #undef OUT
                    #undef YES
                    #undef  NO

带参宏定义:

        带参宏定义的语法结构
            #define 宏名(形参列表) 字符串(宏体)
        带参数宏定义调用:
            宏名(实参表);
                C语言中允许宏带有参数,在宏定义的参数中称为形式参数,形式参数不分配内存单元,没有类型定义;

                #define S(a,b) a*b;
                area = S(3,2);
                宏展开 area = 3 * 2;
            注意事项:
                带参数宏定义中,宏名和形式参数列表之间不能有空格出现。如
                #define MAX (a,b) (a>b)?a:b  
                此时MAX为无参的宏定义,宏体为(a,b) (a>b)?a:b 
                #define  MAX(a,b) (a>b)?a:b
                宏定义中,形参是一个标识符,而宏调用的参数可以是一个表达式
                宏体内的形参通常要括号阔起来
                #define POWER(x) ((x)*(x))

从带参数的宏定义中我们可以发现,其在很多场合下,是可以看作是一个函数来使用的,但是与真正的函数又是有着很大的区别的,所以以下是总结出来的函数与宏之间的几点区别:
区别:
区别

对比项带参数宏定义函数
处理时间编译时期程序运行时期
参数类型无参数类型有具体的参数类型
处理过程不分配内存,简单字符的置换分配内存,先求得实参,再传递给形参
程序长度会变长不变
运行速度不会占用运行时间调用和返回会占用时间
支持递归宏定义是不会支持递归的函数支持递归

条件编译

条件编译的概念
1:一般情况下,源程序中所有的行都进行编译,但是有时希望对其中一部分内容满足一定条件下才进行编译,也就是对一部分内容指定编译条件,这就是条件编译

条件编译的优点
条件编译可以指定代码的一部分是被正常编译还是被完全忽略
条件编译有利于提升程序的可移植性,增强程序 的灵活性

条件编译的相关语法
条件编译语法一:

 #ifdef 标识符(宏名) //或者#if define(标识符) 
    程序段1 
 #else
    程序段2 
 #endif

实例代码:防止多重包含的问题产生
#define HELLO "helloworld"
#ifdef HELLO
    #define HI "welcome"
#endif

条件编译语法二:

  #ifndef 标识符(宏名) //或#if !define(标识符) 
    程序段1 
  #else
    程序段2 
  #endif

  实例:
    #ifndef __HELLO_H__
        #define __HELLO_H__
        void out();
    #else
        //程序段2
    #endif

条件编译语法三:

    #if(常量表达式)
        程序段1 
    #elif(常量表达式2)
        程序段2 
    #else
        程序段3 
    #endif

实例代码:
    #define C1 0
    #define C2 0 
    #define C3 1
    #if(C1)
        #include "c1.h"
    #elif(C2)
        #include "c2.h"
    #elif(C3)
        #include "c3.h"
    #else
        #include "c.h"
    #endif

文件包含

文件包含的概念
文件包含是C预处理程序的另一个重要的功能,被包含的文件名字必须使用双引号”“(自定义头文件),或者<>(标准库文件)括起来,

文件包含的语法:

#include <文件名>
或者
#include "文件名"

文件包含的功能:
一个源文件可以将另外一个源文件的内容包含进来,从而把指定的文件和当前的源文件连成一个源文件

文件包含的处理过程:
在预处理的时候,用被包含文件的内容取代该文件包含指令,再对包含后的文件作一个源文件编译

文件包含的搜索方式

#include<文件名>
若指定文件目录(如include)则会从指定目录中去进行查找,否则就会按照标准的方式进行查找
如:(gcc -o bin/hello -Iinclude src/hello)
标准方式:从系统标准文件所在的目录中去寻找要包含的文件
linux下:/usr/include或者/usr/lib中主要存放的是标准库文件


#include "文件名"
先从存放C源文件的目录中查找,然后从指定的目录中去查找,最后再从C语言的标准库文件中去查找

重要:文件的多重包含问题

概念:同一个文件被包含了多次

结果:多重包含可能会出现重复定义的编译错误

解决方式:使用条件编译(只能是一个源文件中去解决)来防止多重包含,凡是在头文件前后,用条件编译去编译

如标准头文件的写法:
#ifndef __HEADER_NAME_H__
#define __HEADER_NAME_H__
    #include "headername.h"
//其他的代码
#endif

如果在多个源文件中进行多重包含的话,使用多重包含的话是解决不了的。需要检查。

建议注意:
在头文件中尽量不要去定义一些全局变量,可以在源文件中去定义,用extern去修饰,将变量的作用于释放带整个程序

预处理操作符和预定义宏
预处理操作符号:#和##

    C语言中有两个预处理操作符号#和##,它可以在#define中使用
    操作符号#通常成为字符串化的操作符号,它把其后的串变成用双引号包围的串
    如:#define PRINT(FORMAT,VALUE) printf("the value of" #value "is" FORMAT "\n",VALUE)
    PRINT("%d",x+3);
    连接操作符号##可以把两个独立的字符串链接成一个字符串
    如:
    #define ADD_TO_SUM(sum_number,value) sum##sum_number +=value
    ADD_TO_SUM(5,25);

预定义宏和其他指令:

__FILE__ 进行编译的源文件名称
__LINE__ 文件当前的行号
__DATE__文件被编译的日期
__TIME__文件被编译的时间
__fun__当前所在的函数名称

其他预处理
#error  自定义输出的错误,是不能链接生成可执行文件的
#line 设置当前的文件从哪一行开始计算
    #line 100 "hello.c"
#pragma 字节对齐预处理指令
    #pragma message("helloworld");----->输出提示相关信息

以上是个人在学习过程中所做的一些总结性东西,如有不对的地方,希望可以及时指出,欢迎继续访问。

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值