如何提高代码逼格?宏定义-从入门到放弃

道哥分享
关注

一、前言

二、预处理器的操作

三、宏扩展

四、符号:# 与 ##

五、可变参数的处理

六、奇思妙想的宏

七、总结

一、前言

一直以来,我都有这样一种感觉:当我学习一个新领域的知识时,如果其中的某个知识点在刚开始接触时,我感觉比较难懂、不好理解,那么以后不论我花多长时间去研究这个知识点,心里会一直认为该知识点比较难,也就是说第一印象特别的重要。

就比如 C 语言中的宏定义,好像跟我犯冲一样,我一直觉得宏定义是 C 语言中最难的部分,就好比有有些小伙伴一直觉得指针是 C 语言中最难的部分一样。

宏的本质就是代码生成器,在预处理器的支持下实现代码的动态生成,具体的操作通过条件编译和宏扩展来实现。我们先在心中建立这么一个基本的概念,然后通过实际的描述和代码来深入的体会:如何驾驭宏定义。

所以,今天我们就来把宏定义所有的知识点进行汇总、深挖,希望经过这篇文章,我能够摆脱心理的这个魔障。看完这篇总结文章后,我相信你也一定能够对宏定义有一个总体、全局的把握。

二、预处理器的操作 

1. 宏的生效环节:预处理

一个 C 程序在编译的时候,从源文件开始到最后生成二进制可执行文件,一共经历 4 个阶段:

我们今天讨论的内容就是在第一个环节:预处理,由预处理器来完成这个阶段的工作,包括下面这 4 项工作:

文件引入(#include);条件编译(#if..#elif..#endif);宏扩展(macro expansions);行控制(line control)。2. 条件编译

一般情况下,C 语言文件中的每一行代码都是要被编译的,但是有时候出于对程序代码优化的考虑,希望只对其中的一部分代码进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。

简单的说:就是预处理器根据我们设置的条件,对代码进行动态的处理,把有效的代码输出到一个中间文件,然后送给编译器进行编译。

条件编译基本上在所有的项目代码中都被使用到,例如:当你需要考虑下面的几种情况时,就一定会使用条件编译:

需要把程序编译成不同平台下的可执行程序;同一套代码需要运行在同一平台上的不同功能产品上;在程序中存在着一些测试目的的代码,不想污染产品级的代码,需要屏蔽掉。

这里举 3 个例子,在代码中经常看到的关于条件编译:

示例1:用来区分 C 和 C++ 代码

#ifdef __cplusplus extern "C" { #endif  void hello(); #ifdef __cplusplus } #endif

这样的代码几乎在每个开源库中都可能见到,主要的目的就是 C 和 C++ 混合编程,具体来说就是:

如果使用 gcc 来编译,那么宏 __cplusplus 将不存在,其中的 extern "C" 将会被忽略;如果使用 g++ 来编译,那么宏 __cplusplus 就存在,其中的 extern "C" 就发生作用,编译出来的函数名 hello 就不会被 g++ 编译器改写,因此就可以被 C 代码来调用;

示例2:用来区分不同的平台

#if defined(linux) || defined(__linux) || defined(__linux__)    sleep(1000 * 1000); // 调用 Linux 平台下的库函数#elif defined(WIN32) || defined(_WIN32)    Sleep(1000 * 1000); // 调用 Windows 平台下的库函数(第一个字母是大写)#endif

那么,这些 linux, __linux, __linux__, WIN32, _WIN32 是从哪里来的呢?我们可以认为是编译目标平台(操作系统)为我们预先准备好的。

示例3:在编写 Windows 平台下的动态库时,声明导出和导入函数

#if defined(linux) || defined(__linux) || defined(__linux__)    #define LIBA_API #else  #ifdef LIBA_STATIC    #define LIBA_API  #else      #ifdef LIBA_API_EXPORTS          #define LIBA_API __declspec(dllexport)      #else          #define LIBA_API __declspec(dllimport)      #endif  #endif#endif
// 函数声明LIBA_API void hello();

这段代码是直接从我之前在 B 站录制的一个小视频里的示例拿过来的,当时主要是演示如何如何在 Linux 平台下使用 make 和 cmake 构建工具来编译,后来又小伙伴让我在 Windows 平台下也用 make 和 cmake 来构建,所以就写了上面这段宏定义。

在使用 MSVC 编译动态库时,需要在编译选项(Makefle 或者 CMakeLists.txt)中定义宏 LIBA_API_EXPORTS,那么导出函数 hello 的最前面的宏 LIBA_API 就会被替换成:__declspec(dllexport),表示导出操作;在编译应用程序的时候,使用动态库,需要 include 动态库的头文件,此时在编译选项中不需要定义宏 LIBA_API_EXPORTS,那么 hello 函数最前面的 LIBA_API 就会被替换成 __declspec(dllimport),表示导入操作;补充一点:如果使用静态库,编译选项中不需要任何宏定义,那么宏 LIBA_API 就为空。3. 平台预定义的宏

上面已经看到了,目标平台会为我们预先定义好一些宏,方便我们在程序中使用。除了上面的操作系统相关宏,还有另一类宏定义,在日志系统中被广泛的使用:

FILE:当前源代码文件名;
LINE:当前源代码的行号;
FUNCTION:当前执行的函数名;
DATE:编译日期;
TIME:编译时间;

例如:

printf("file name: %s, function name = %s, current line:%d ", __FILE__, __FUNCTION__, __LINE__);

声明: 本文由入驻OFweek维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存