面试中经常被问到的【宏定义】,改变你对【C\C++】中宏定义的认识。

news/2024/4/20 16:43:25/文章来源:https://blog.csdn.net/weixin_44120785/article/details/129116989

        最近遇到挺多宏定义的代码,其实挺烦的,每次看复杂的宏定义看到一半就懵了,今天盘一盘它。本篇设计宏定义的原理、使用方法、使用技巧。

目录

 一、宏定义原理

二、宏定义定义复杂功能函数

2.1 定义注册函数

三、宏定义实现条件编译

四、宏定义实现代码重用

4.1 代码重用

4.2 宏定义实现简单的类型转换


 一、宏定义原理

        C++的宏定义是一种预处理器指令,它可以用来在编译阶段进行简单的文本替换。当编译器遇到宏定义时,它将会把宏定义中的符号替换为其定义的文本内容,然后再继续编译程序。

        具体来说,C++代码在编译之前首先被送到预处理器进行预处理,预处理器对代码进行扫描和处理,将所有的宏定义替换为宏定义中指定的内容,生成预处理后的代码,然后编译器再对预处理后的代码进行编译生成目标代码。

        下面是一个简单的宏定义示例:

#define PI 3.1415926#define SQUARE(x) ((x) * (x))int main() {int a = 5;int b = SQUARE(a);return 0;
}

        在这个示例中,编译器会把所有出现的“PI”符号替换为其定义的文本内容“3.1415926”。宏定义SQUARE(x)将参数x的平方作为返回值。当宏定义被调用时,编译器将把函数调用中的参数替换成宏定义中的文本内容,从而实现了计算参数平方的功能。

tips:

需要注意的是,函数宏并不是真正的函数,它没有函数调用的开销和参数检查等功能。另外,由于宏定义只是简单的文本替换,因此在使用函数宏时需要注意其可能带来的意外行为,比如参数求值次数不确定、符号重定义等问题。

        这样的宏定义是你熟悉的,下面增加难度。

二、宏定义定义复杂功能函数

2.1 定义注册函数

第一个例子:

        在C++中,可以使用宏定义来模拟注册函数。注册函数是指程序在运行时向一个注册表中添加一个函数指针,从而允许在需要时动态调用这个函数。以下是一个简单的示例:

#include <vector>
#define REGISTER_FUNCTION(FUNC_NAME) \static void FUNC_NAME##_register() __attribute__((constructor)); \static void FUNC_NAME##_register() { \function_registry.push_back(&FUNC_NAME); \} \void FUNC_NAME()std::vector<void (*)()> function_registry;REGISTER_FUNCTION(my_function) {// 函数实现
}

        在上面的示例中,宏定义REGISTER_FUNCTION(FUNC_NAME)定义了一个注册函数,该函数在程序运行时向全局的函数注册表中添加函数指针。宏定义中使用了一些技巧来实现函数的自动注册,具体如下:

  • FUNC_NAME##_register() 定义了一个静态函数,函数名为FUNC_NAME_register,用于在程序启动时自动调用并向注册表中添加函数指针。这里使用了函数名连接符##,将宏定义中的函数名和_register连接起来,生成新的函数名。
  • __attribute__((constructor)) 是GNU C编译器提供的一种函数属性,表示该函数将在程序启动时自动调用。这里将函数FUNC_NAME##_register()设置为该属性,以实现自动注册的功能。
  • function_registry.push_back(&FUNC_NAME) 将函数指针&FUNC_NAME添加到全局的函数注册表中。
  • void FUNC_NAME() 定义了实际的函数实现,这里使用了空函数体,具体的函数实现可以根据需求进行修改。

第二个例子:

#include <iostream>
#include <string>
#include <unordered_map>using namespace std;// 定义一个宏,用于简化类的注册函数的定义
#define REGISTER_CLASS(class_name)                                          \class class_name;                                                       \namespace {                                                             \struct Register_##class_name {                                       \Register_##class_name() {                                        \ClassFactory::getInstance().registerClass(#class_name, []() -> void* { return new class_name; }); \}                                                               \};                                                                  \static Register_##class_name s_register_##class_name;                \}                                                                       \// 类工厂,用于注册和创建类
class ClassFactory {
public:static ClassFactory& getInstance() {static ClassFactory instance;return instance;}void registerClass(const string& className, void* (*creator)()) {m_classRegistry[className] = creator;}void* createClass(const string& className) {auto it = m_classRegistry.find(className);if (it == m_classRegistry.end()) {return nullptr;}else {return it->second();}}private:unordered_map<string, void* (*)()> m_classRegistry;
};// 定义一个基类
class BaseClass {
public:virtual ~BaseClass() {}virtual void print() = 0;
};// 定义一个派生类1
class DerivedClass1 : public BaseClass {
public:void print() override {cout << "DerivedClass1" << endl;}
};// 定义一个派生类2
class DerivedClass2 : public BaseClass {
public:void print() override {cout << "DerivedClass2" << endl;}
};// 注册类
REGISTER_CLASS(DerivedClass1);
REGISTER_CLASS(DerivedClass2);// 主函数
int main() {auto obj1 = static_cast<BaseClass*>(ClassFactory::getInstance().createClass("DerivedClass1"));obj1->print();auto obj2 = static_cast<BaseClass*>(ClassFactory::getInstance().createClass("DerivedClass2"));obj2->print();return 0;
}

在上面的代码中,我们使用了#define宏定义了一个宏REGISTER_CLASS,用于简化类的注册函数的定义。这个宏定义的具体实现包括以下几个步骤:

  1. 定义一个类对象,这个类对象用于触发类的注册操作。

  2. 定义一个匿名命名空间,在这个命名空间中定义一个结构体Register_##class_name,这个结构体在构造函数中调用类工厂的registerClass函数,将类的名称和一个创建类的函数关联起来。在结构体定义之后,我们创建一个静态结构体对象s_register_##class_name,这个对象的唯一作用就是调用结构体的构造函数,从而触发类的注册操作。

  3. 最后,我们在类的定义后面使用REGISTER_CLASS宏来注册这个类。

使用宏定义可以让我们在类的定义中加入很少的代码,就可以将类注册到类工厂中。

三、宏定义实现条件编译

        为实现代码的移植能力,我们可以使用宏定义设置编译条件。例如实现调试模式、跨平台支持等。这种宏定义通常在开发过程中使用较多。

#ifdef DEBUG// 调试模式代码
#endif#ifdef _WIN32// Windows 平台代码
#endif#ifdef __linux__// Linux 平台代码
#endif

          另一个例子:

#include <iostream>
using namespace std;#define DEBUGint main() {#ifdef DEBUGcout << "Debug version" << endl;#elsecout << "Release version" << endl;#endifreturn 0;
}

         我们使用了#define宏定义了一个名为DEBUG的宏。在主函数中,我们使用#ifdef指令和#endif指令将一段代码包裹起来,这段代码只有在DEBUG宏被定义的情况下才会被编译。在这个例子中,我们只是简单地输出一句话,但在实际的应用中,我们可以在条件编译中包含或者排除一些特定的代码,以实现特定的功能或者优化。   

        需要注意的是,条件编译是在预处理阶段完成的,而不是在编译阶段。在预处理阶段,预处理器会扫描源代码中的宏定义和条件编译指令,并根据它们生成一份新的代码,这份新的代码会成为编译器的输入。因此,在编译时,条件编译指令不会再起作用,已经被处理掉了

        我们不能将宏定义当成if else来使用,会出问题的。

四、宏定义实现代码重用

        例如实现多次调用相同的代码片段、实现简单的类型转换等。这种宏定义通常使用较多。

#define MY_MACRO(code) do { \// 执行代码片段 code \// ... \
} while (0)#define CAST(type, ptr) reinterpret_cast<type>(ptr)

4.1 代码重用

#include <iostream>#define PRINT_MSG(msg) cout << "Message: " << msg << endlusing namespace std;int main() {PRINT_MSG("Hello World!");PRINT_MSG("Welcome to the world of macros!");return 0;
}

        我们使用了#define宏定义了一个名为PRINT_MSG的宏。这个宏接受一个参数msg,在控制台输出一个以"Message:"开头的字符串和msg参数。在main函数中,我们两次调用这个宏,分别输出了"Hello World!""Welcome to the world of macros!"

4.2 宏定义实现简单的类型转换

#include <iostream>#define FLOAT_TO_INT(x) (int)(x)using namespace std;int main() {float f = 3.14159;int i = FLOAT_TO_INT(f);cout << "The value of float f is " << f << endl;cout << "The value of int i is " << i << endl;return 0;
}

        在上面的例子中,我们使用了#define宏定义了一个名为FLOAT_TO_INT的宏。这个宏接受一个浮点数类型的参数x,将其强制转换为整数类型,并返回整数值。在main函数中,我们定义了一个浮点数变量f,并将其赋值为3.14159。然后,我们调用FLOAT_TO_INT宏,将f转换为整数类型,并将转换后的值赋值给整数变量i。最后,我们分别输出了fi的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_71577.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

扬帆优配|五千亿巨头一度涨停! 4天3倍,港股又现“狂飙”股!

周一&#xff0c;A股三大指数走势分化。到午间收盘&#xff0c;沪指震荡走高涨近1%&#xff0c;深证成指涨0.75%&#xff0c;创业板指继续弱势调整。 盘面上&#xff0c;钢铁、煤炭、大金融等权重板块团体走强&#xff0c;三大通讯运营商一同拉升&#xff0c;其间我国电信盘中一…

超25亿全球月活,字节依然没有流量

&#xff08;图片来源于网络&#xff0c;侵删&#xff09; 文|螳螂观察 作者| 搁浅虎鲸 注意看&#xff0c;这个男人叫梁汝波&#xff0c;是字节跳动的联合创始人&#xff0c;也是接棒张一鸣的新任CEO。 在字节跳动十周年之际&#xff0c;他发表了激情昂扬的演讲。“激发创…

linux高级命令之互斥锁

互斥锁学习目标能够知道互斥锁的作用1.互斥锁的概念互斥锁: 对共享数据进行锁定&#xff0c;保证同一时刻只能有一个线程去操作。注意:互斥锁是多个线程一起去抢&#xff0c;抢到锁的线程先执行&#xff0c;没有抢到锁的线程需要等待&#xff0c;等互斥锁使用完释放后&#xff…

02- OpenCV绘制图形及图像算术变换 (OpenCV基础) (机器视觉)

知识重点 OpenCV用的最多的色彩空间是HSV. 方便OpenCV做图像处理img2 img.view() # 浅拷贝img3 img.copy() # 深拷贝split(mat) 分割图像的通道: b, g, r cv2.split(img) # b, g, r 都是数组merge((ch1, ch2, ch3)) 融合多个通道cvtColor(img, colorspace): 颜…

Learning C++ No.11【string类实现】

引言&#xff1a; 北京时间&#xff1a;2023/2/19/8:48&#xff0c;昨天更新了有关进程状态的博客&#xff0c;然后在休息的时候&#xff0c;打开了腾讯视屏&#xff0c;然后看到了了一个电视剧&#xff0c;导致上头&#xff0c;从晚上6点看到了10点&#xff0c;把我宝贵的博客…

【NestJS】中间件

中间件是在路由处理程序之前调用的函数&#xff0c;所以在中间件函数中可以访问请求和响应。 中间件函数需要执行 next() 将控制传递给下一个中间件函数&#xff0c;否则请求会被挂起。 可以使用 nest g mi XXX 创建中间件。 局部中间件 nest g res usernest g mi ajax、编写…

klog bug:仅输出到日志文件,不打印到命令行/stderr

一、 问题描述 开发k8s插件&#xff0c;使用klog作为日志工具&#xff0c;开发完成发现在设置将日志打印到文件后&#xff0c;Error级别的日志信息仍然会输出到命令行&#xff0c;过多日志打印会使后期将服务部署于docker有卡死的风险&#xff08;docker的bug&#xff0c;日志…

kubectl常用的命令

目录 安装 kubectl 一、命令自动补全 二、常用命令 1、查看所有pod列表 2、查看RC和service列表 3、显示Node的详细信息 4、显示Pod的详细信息, 特别是查看Pod无法创建的时候的日志 5、 根据yaml创建资源, apply可以重复执行&#xff0c;create不行 6、基于nginx.yaml…

优思学院:六西格玛中的水平对比方法是什么?

水平对比&#xff0c;就是比较不同事物之间的差异。 这个概念在六西格玛管理中也很重要&#xff0c;也就是我们经常说的标杆管理&#xff0c;经常被用来寻找行业中最好的做法&#xff0c;以帮助组织改进自身的绩效。 在六西格玛管理中&#xff0c;水平对比有三种常见的应用方式…

记一次IDE的Docker插件实战(Dockfile篇)

IDEA下使用Docker插件制作镜像、推送及运行 前言 本部分主要根据IDEA的Docker插件实战(Dockerfile篇)_程序员欣宸的博客-CSDN博客_idea编写dockerfile一文所述内容进行实践&#xff0c;并对其中遇到的问题进行解答&#xff0c;从而串接多个知识点。 如何编写Dockfile 在Int…

【YOLOv5】 3060显卡 GPU版本环境搭建与运行

YOLOv5环境搭建步骤创建虚拟环境使用anaconda新建一个python版本为3.7的虚拟环境查看电脑支持的cuda版本由于30系列的的显卡暂时不支持CUDA11以下版本。因此&#xff0c;这里得安装超过CUDA11.0的版本。通过如下命令来查看可以安装的cuda的版本&#xff1a;conda search cuda安…

JVM类加载子系统

1、类加载子系统在内存结构中所处的位置通过内存结构图&#xff0c;我们先知道类加载子系统所处的位置&#xff0c;做到心中有图。2、类加载器作用类加载器子系统负责从文件系统或者网络中加载Class文件&#xff0c;class文件在文件开头有特定的文件标识。ClassLoader只负责cla…

火花幻境加深与亚马逊云科技的合作,为玩家创造更加美好的游戏体验

元宇宙可谓是2022年科技界的“当红炸子鸡”&#xff0c;该概念的兴起为游戏产业带来了更多的发展机遇&#xff0c;游戏与元宇宙的结合&#xff0c;为玩家带来了更好的全息感官体验与游戏乐趣。元宇宙游戏市场前景广阔&#xff0c;企业能否快速抢滩市场&#xff0c;不断缩短游戏…

如何配置git,使其支持多用户

如何配置git&#xff0c;使其支持多用户&#xff1f; 在多数时候&#xff0c; 我们使用git进行操作时&#xff0c;只需要在本地配置一个用户的ssh key&#xff0c;就可以完成基本的pull/push操作。如果现在我有两个github的账号&#xff0c;并需要在一台电脑中操作其中的repo&…

java网络编程-nio学习:阻塞和非阻塞

一、阻塞 阻塞模式下&#xff0c;相关方法都会导致线程暂停 ServerSocketChannel.accept 会在没有连接建立时让线程暂停 SocketChannel.read 会在没有数据可读时让线程暂停 阻塞的表现其实就是线程暂停了&#xff0c;暂停期间不会占用 cpu&#xff0c;但线程相当于闲置 单线…

PyQt5数据库开发1 4.3 QSqlTableModel 之 相关槽函数的实现(多图长文详解)

目录 一、打开数据库表 1. 写打开数据库的槽函数 2. 运行后发现数据库可以打开了 3. ODBC配通了&#xff0c;数据库还是打不开 4. 写在tableView上显示数据库表的函数 5. 运行后发现表可以显示了 6. 代码分析 7. 添加列名称 8. 根据内容调整列宽 9. 备注&#xff1a;…

三、NetworkX工具包实战3——特征工程【CS224W】(Datawhale组队学习)

开源内容&#xff1a;https://github.com/TommyZihao/zihao_course/tree/main/CS224W 子豪兄B 站视频&#xff1a;https://space.bilibili.com/1900783/channel/collectiondetail?sid915098 斯坦福官方课程主页&#xff1a;https://web.stanford.edu/class/cs224w NetworkX…

数据库事务AICD以及隔离级别

目录一.事务的ACID二.隔离级别三.并发事务中的问题1.脏写2.脏读3.不可重复读4.幻读四.MVCC机制五.读锁与写锁六.大事务的影响七.事务优化一.事务的ACID 原子性(Atomicity)&#xff1a;一个事务中的所有操作&#xff0c;要么全部成功&#xff0c;要么失败全部回滚&#xff0c;不…

linux集群技术(一)--LVS(负载均衡)(一)

集群功能分类负载均衡LVS概述LVS工作模式轮训算法 1.集群功能分类 1.1 LB &#xff08;一&#xff09;简介 LB&#xff1a;Load Balancing&#xff0c;负载均衡&#xff08;增加处理能力&#xff09;,有一定高可用能力&#xff0c;但不是高可用集群&#xff0c;是以提高服务的…