浅谈C语言预处理

预处理

什么是预处理?预处理是C语言编译的三个过程(预处理、编译、汇编)中的第一步,它主要进行的是一些文件操作,如#define定义的标识符和宏的替换,头文件的包含和注释的删除,等等。

以下,我们将对预处理中的一些操作进行一个较为详细的了解。


1、预定义符号

预定义符号主要有以下5个:
1. __ FILE __: 指明进行编译的源文件
2. __ LINE __:文件中当前语句的行数
3. __ DATE __:文件被编译的日期
4. __ TIME __:文件被编译的时间
5. __ STDC __:即“标准C”,如果当前编译器遵循ANSI C标准,则其值为1,否则未定义

以下我们来使用这几个预定义符号:
在这里插入图片描述
相应的输出结果为:
在这里插入图片描述

关于__ STDC __的使用,我们用VS编译器进行测试:
在这里插入图片描述
我们可以看到,在VS下,__STDC__是未定义的标识符,,说明VS编译器并不严格遵守ANSI C标准。


2、#define定义标识符和宏

A、#define定义标识符

#define定义标识符的用途很广,基本的格式就是:#define A B。其实可以简单的理解成将B重命名为A,A的作用与B等同。

以下仅举几个例子:
无
这里的print就是起到打印haha外加换行的作用
无
特别注意:#define的定义,最后不用加分号

B、#define定义宏

宏的基本格式:宏名、宏参和宏的基本内容

a、宏的定义

在这里插入图片描述
ADD是宏名,ADD后括号内的是宏参,之后的x+y是宏的具体内容

可以定义宏,当然我们也可以取消相应的宏的定义,使用#undef
在这里插入图片描述

b、宏的使用

在这里插入图片描述
这样输出结果就为7

在宏的定义中我们可以发现,宏的内容部分,我们加了三个括号,这是为什么呢?这是为了避免一些由于运算优先级可能会造成的问题。

我们看不加括号的情况:
在这里插入图片描述

这时,不了解宏的话,你可能会认为输出的是16,但实际上输出的是11。

要理解这个,就需要了解宏参的替换问题。

加括号:在宏的具体内容中,一定要给每一个宏参都加上括号,以及宏的具体内容整体也要加上括号。
前者主要是由于宏在传参时,进行的是参数的替换,即如果以一个表达式为参数,参数替换时就将这个表达式替换进去,而不是这个表达式的值,这样在实际中可能就会存在一个运算优先级的问题;后者加括号也是为了将宏视作一个整体,避免一些运算优先级的问题。

我们将宏ADD替换后,实际得到的是:
在这里插入图片描述
宏参是直接替换进去的,而不是将表达式的运算结果传递进去的。因此,在定义宏时,保证准确性,我们最一定要学会加括号

c、宏和函数

通过上面对宏的探讨,可以发现宏和函数有惊人的相似之处,很多函数的功能,我们都可以通过宏来实现。
以下我们将从多个方面来比较宏和函数。

  1. 命名:宏的命名通常要全大写(存在例外,如offsetof宏),而函数的命名通常不要全大写
  2. 参数类型角度:宏的参数是没有限制的,更加自由,但也缺少严谨;相比之下,函数只能传特定类型的参数,限制更多,但也更加严谨
  3. 参数传递角度:宏的参数,准确来说是参数的替换,参数是表达式也一样进行替换;而函数的传参,如果是表达式的话,则是把表达式的值传给相应的形参
  4. 代码长度角度:宏的使用,实际上进行的是代码的替换,如果宏自身较长,而宏的使用也比较多,则会导致预处理后,程序长度大幅增加;而对于函数,每次函数的调用,都是调用同一处函数的代码,所以不存在宏的问题
  5. 调试角度:由于宏在预处理中会进行替换,所以宏是没有办法进行调试的,而函数则可以正常调试
  6. 效率角度:从代码执行的效率来说,如果本身要实现的功能较为简单,使用宏,程序执行的效率会更高;而使用函数的话,可能执行调用函数和函数返回的代码所花费的时间已远远大于实际运算工作所花的时间,效率较低

综述: 综合来看,代码功能相对简单,使用宏可能更好;代码功能相对复杂,使用函数可能更好


3、条件编译

什么是条件编译?顾名思义,条件编译是只有在满足一定条件的情况下才会进行编译,即条件编译的代码只有在满足相应条件时,才会正常执行
以下介绍一些用于条件编译的语句:

  1. #if+#endif
    在这里插入图片描述
    只有当i=5时,才会打印i

  2. #ifdef+#endif
    在这里插入图片描述
    只有当定义了标识符MAX时,才会打印 i

    这种形式还可以有如下写法:
    在这里插入图片描述
    两种写法作用是相同的
    当然,两种写法也都有相应的否定形式
    在这里插入图片描述
    在这里插入图片描述
    这两种写法意思为:只有在未定义MAX时,才会打印 i

  3. 条件编译的选择形式
    这种情况下的条件编译类似于if-else语句:
    在这里插入图片描述

4、头文件包含

头文件的包含使用#include+头文件,头文件通常用尖括号或双引号进行引用

A、两种包含形式

在这里插入图片描述
这两种情况该如何选择呢?
我们通常都是这么说的:库函数头文件的包含使用尖括号,而程序员自己创建的头文件的包含使用双引号。
但实际上,双引号的覆盖范围是包括了尖括号的。

  1. 双引号引头文件:
    这种情况下,编译器会先到代码所在的路径下找相应的头文件,如果找不到,再去VS的库目录下查找。
  2. 尖括号引头文件:
    这种情况下,编译器直接到VS的库目录下查找。

B、防止头文件被重复包含

我们平时写代码,有时候会重复包含头文件,尤其是对于库函数头文件的重复包含,会在程序预处理后,大大增加程序的长度,所以我们该如何避免头文件被重复包含呢?

以下介绍两种方法:

  1. 第一种方法:
    在这里插入图片描述
    在头文件的第一行添加上述代码,使得该头文件只会被包含一次。

  2. 第二种方法:
    在这里插入图片描述
    使用**#ifndef#endif**,说明其中的代码只有在_TEST_H__未定义时,才能正常编译,而实际上一旦包含该头文件一次,_TEST_H__就已经定义了,因此这样就确保了头文件中的内容只会被包含一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值