C语言:预处理中做的那些事


1、写在前面的话

这篇博客主要讲下C语言预处理中的那些事,其实做的事还是很多的,这里都来一一理清,可以结合之前的一篇文章一起学。说句题外话,最近开学了,可能每天花大量时间整理以前学习的知识了,但还是会坚持每天写一篇的,积累就是财富。


2、预处理的作用

预处理器对程序源码进行一些预先的处理,主要时处理掉一些非核心的功能,为后续编译打好基础后,再由编译器编译,主要作用就是使编译器实现功能变得更为专一。


3、预处理的内容


01 文件包含

解释:在预处理阶段,会将所有的头文件包含,用文件中的内容原封不动的替换。

备注:#include <> 和 #include ""的区别
01 尖括号专门用来包含系统提供的头文件(操作系统自带的,不是程序员自己写的),双引号用来包含自己写的头文件。
02 尖括号表示的话,C语言编译器只会到系统指定目录去寻找这个头文件,隐含的意思就是不会再当前的目录下寻找,如果找不到就会提示这个头文件不存在。
03 双引号包含的头文件,编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后才会去系统指定的目录下寻找,如果仍找不到就会提示这个头文件不存在。
04 系统指定的目录是指编译器中配置或者操作系统配置的目录,比如Ubuntu中是/usr/include目录,编译器还允许用-I来附加指定其他的包含路径。
05 如果自己写的,并且集中放在一个专门存放头文件的目录下,建议在编译器中使用-I参数来寻找头文件,在这种情况下使用尖括号<>。


02 宏定义

解析:
01 在预处理阶段由预处理器进行原封不动的宏定义替换。
02 替换会递归进行,直到替换的结果本身不是一个值为止。
03 一个完整的宏定义应该包含三个部分:#define、宏名和剩下的所有
04 宏可带参数,称为带参宏。带参宏的使用和带参函数非常像,但使用上有一些差异,在定义带参宏时,每一个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可。

无参宏:#define 标识符 宏体
标识符是符号常量,宏体为常数、表达式、格式串等。

带参宏:宏名中含有参数
需要特别注意加括号,防止有关优先级的问题发生,如若未加,极易出错。需要特别宏定义和带参宏的区别,宏定义是在预处理期间处理的,而函数是在编译期间处理的,也就是,宏定义最终是在调用宏的地方把宏体给原地展开,而函数是在调用处跳转到函数中去执行,执行完后再跳转回来。

备注:
01 宏定义是原地展开的,没有调用开销,而函数有。
02 带参宏不会检查参数的类型,返回值也不会附带类型,而带参函数有明确的参数类型和返回值类型,调用函数的时候,编译器会做参数的静态类型检查,如果发现实际传参和参数声明不同时会报警告或者错误。因此,时候函数参数方面出错的话,编译器会报警告,但是带参宏这方面的问题需要我们自己注意。
03 函数调用的开销主要出现于函数传参需要参数、返回地址等的压栈和出栈,栈变量的开辟和销毁等。
04 可以定义只有宏名,没有宏体的宏,比如#define DEBUG,这个是条件编译,主要是用这种类似开关的方式,实现跨平台,实现不同版本的程序。


03 注释

一般,源程序的有效注释量必须在15%以上,常见的注释有模块注释、头文件开头的版权和版本的声明、函数的说明,另外还有一些重要的代码行的注释。

解释:在预处理过程中,预处理器会通过移除所有注释用一个空格替换,所以到了编译器进行编译的时候,程序中已经没有注释了。

注意:
01 注释格式尽量统一,建议使用/* */,Linux内核中基本都是使用这个。
02 注释应考虑程序易读及排版的优美,注释语言应尽量统一。
03 函数头部都应该注释,也可以使用代码自注释。
04 建议边写代码边注释,更改代码时同时改注释,保证注释和代码一致。
05 防止注释出现二义性,所以注释必须要简洁明了,而且尽量不要使用缩写。
06 在单行代码注释时,注释应放在代码的上方或者右方。
07 有些特殊内容晦涩难懂的,一定要进行注释,加一些具有特殊意义的数。
08 注释和代码同缩进,并且注释与上方代码之间要空出一行。
09 如果代码本身很清新易懂的话,就没有必要加注释,否则只会多此一举。


04 条件编译

背景:有时希望程序中有多种配置,编写好各种配置代码之后,给个配置开关,在源代码级别去修改配置开关来让程序编译出不同的效果。

形式1:#ifndef #define #endif(特别常用)

// 引例
#ifndef __ASM_ARM_BITOPS_H__  // 如果不存在asm-arm/bitops.h
#define __ASM_ARM_BITOPS_H__ // 就引入asm-arm/bitops.h
// ...头文件的内容
#endif // 否则就不引入asm-arm/bitops.h

主要是为了避免重复包含bitops.h文件,当第一次包含这个头文件的时候,会定义出
__ASM_ARM_BITOPS_H__宏,后续再次包含该文件的时候,因为有了该宏定义,后续的
命令#define __ASM_ARM_BITOPS_H__就不会成立,所以头文件不会再次包含。

形式2:#if define #endif

// 引例
#if define (x)
// ...code
#endif
在这个#if define中,不管括号里面的x的逻辑是真还是假,其只管这个程序的前面有
没有定义“x”这个宏,如果定义了x这个宏,那么编译器会编译中间的...code...代码,
否则直接会忽略中间的...code...代码。
另外,#if define(x)用法其实和#ifdef x是相似的。

形式3:#if !define #endif

// 引例
#if !define (x)
// ...code
#endif
在这个#if !define中,不管括号里面的x的逻辑是真还是假,其只管这个程序的前面有
没有定义“x”这个宏,如果没有定义x这个宏,那么编译器会编译中间的...code...代码,
否则直接会忽略中间的...code...代码。
另外,#if !define (x)的作用其实和#ifndef x是相似的。

备注:#ifdef和#if defined的区别
#ifdef和#if defined区别在于#if defined可以组成复杂的预编译条件,例如:

#if defined (A) && defined (B)
// code
#endif
表示只有A和B这两个宏定义都存在的时候才编译代码,而#ifdef只能判断单个宏定义,
不能判断多个复杂条件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学不懂啊阿田

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值