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

news2025/7/16 0:43:05

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

目录

 一、宏定义原理

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

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 DEBUG

int main() {
    #ifdef DEBUG
        cout << "Debug version" << endl;
    #else
        cout << "Release version" << endl;
    #endif

    return 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 << endl

using 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.coloradmin.cn/o/361447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【OpenCV学习笔记01】- 初步使用OpenCV实现人脸识别

想要使用opencv实现人脸识别&#xff0c;我们需要做这样几步&#xff1a; 1.opencv-python的安装 这里我们使用的python的opencv-python库&#xff0c;在安装opencv-python库之前&#xff0c;我们需要安装numpy, matplotlib。 # 安装指令 # 安装 numpy pip install numpy # …

Chirp-Z变换(线性调频Z变换)原理

Chirp-Z变换&#xff08;Chirp-Z Transform&#xff0c;CZT&#xff09; 采用FFT算法可以很快地计算出全部DFT值&#xff0c;即Z变换在单位圆上的全部等间隔采样值。 在实际情况中&#xff0c;并不需要对整个单位圆的频谱进行分析&#xff0c;例如&#xff0c;对于窄带信号&am…

运动型蓝牙耳机推荐哪款、最新运动蓝牙耳机推荐

提起运动耳机&#xff0c;如今很多运动爱好者和职业教练员们&#xff0c;都会向萌新推荐骨传导运动耳机。骨传导耳机解决了入耳式蓝牙耳机掉落的问题&#xff0c;佩戴相当舒服。骨传导耳机在佩戴过程中解放了双耳&#xff0c;不会因为耳机堵住耳朵&#xff0c;听不到环境音&…

【Spring6】| Spring启示录、Spring概述

目录 一&#xff1a;Spring启示录 1. OCP开闭原则 2. 依赖倒置原则DIP 3. 控制反转IoC 二&#xff1a;Spring概述 1. Spring简介 2. Spring8大模块 3. Spring特点 一&#xff1a;Spring启示录 引言&#xff1a;前面我们已经学习了三层架构&#xff1a;表示层、业务层、…

【工作笔记】syslog,kern.log大量写入invalid cookie错误信息问题

任务描述 错误出现出现过四五次&#xff0c;应该是诊断单元tf卡读写出问题导致下面这条告警一直高频写入到/var/log/下的syslog、kern.log、messages中 Nov 23 06:25:12 embest kernel: omap_hsmmc 48060000.mmc: [omap_hsmmc_pre_dma_transfer] invalid cookie: data->hos…

将maven项目打包成可执行的jar(加入外部依赖)

在有些场景下我们需要将编写的Java工程编译打包成为一个完整的jar包&#xff0c;如果你的项目是使用maven构建的话可以通过以下方法来完成这个打包的过程。添加maven打包插件。在项目的pom.xml配置文件的build标签中添加以下代码&#xff0c;其中 mainClass 属性需要替换成你项…

多种调度模式下的光储电站经济性最优储能容量配置分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

安装MQTT Server遇到报错“cannot verify mosquitto.org‘s certificate”,该如何解决?

MQTT是基于发布/订阅的轻量级即时通讯协议&#xff0c;很适合用于低带宽、不稳定的网络中进行远程传感器和控制设备通讯等操作中。在我们的软件研发中&#xff0c;也经常使用MQTT协议进行消息通信等。今天来和大家分享一些关于在安装MQTT Server中遇到的疑难问题及解决思路。当…

为什么阳康后,感觉自己变傻了?

不少人在阳康后出现脑力下降的情况&#xff0c;好像脑子里被雾笼罩。脑雾并不是新名词&#xff0c;已经存在了十几年。以前慢性疲劳综合征患者和脑震荡患者会用它来形容自己的症状。脑雾其实是认知障碍&#xff0c;它可由多种原因引起。比如过度劳累、长期酗酒、缺乏睡眠、久坐…

Semi-supervised(半监督)布料缺陷检测实战

数据及源码链接见文末 1.任务目标和流程概述 对于常规的缺陷检测,常常需要我们准备好数据,使用分割或者检测的方法选择模型,进行训练。但是有一个问题。在日常生产中,我们接触到的往往都是正常的,缺陷数据往往很难收集,更何况我们还要打标签。我们能不能通过训练正常数据…

独立搭建 handle server

本节主要介绍,如何搭建一个与 GHR隔离的 handle sever,不与外界有任何连通。 下载文件 访问地址下载最新版:http://www.handle.net/download_hnr.html 这里以 9.3.0 版本作为讲解 解压服务端,解压客户端 # 解压 tar -xzvf handle-9.3.0-distribution.tar.gz# 到目录下 …

NestJS学习:图片上传、下载

参考 大神的这两篇文章讲的很详细&#xff0c;这里自己也来试一下 小满nestjs&#xff08;第十三章 nestjs 上传图片-静态目录&#xff09; 小满nestjs&#xff08;第十四章 nestjs 下载图片&#xff09; 上传图片 安装包 需要&#xff1a;multer 和 nestjs/platform-expre…

深入理解Java的Lambda原理

1、前言 Lambda函数的概念其实有很久远的历史了&#xff0c;在Lisa&#xff0c;C#中早有实现。且近年来&#xff0c;开发者对语言的表现力有了更高的要求&#xff0c;Java也在JDK 1.8 中引入了Lambda函数这一概念。虽然截止到写下这段文字的一刻已经过去七年之久&#xff0c;但…

Mybatis框架详解(全)

目录 MyBatis简介 MyBatis整体架构及运行流程 1.数据源配置文件 2.Sql映射文件 3.会话工厂与会话 4.运行流程 mybatis实现增删改查 Mybatis的获取参数的方式 mapper中自定义映射 mybatis注解开发 mybatis缓存 mybatis分页插件 MyBatis简介 MyBatis 是一款优秀的持久…

程序员如何向架构师转型?看完就明白该怎么做了

软件行业技术开发从业人员众多&#xff0c;但具备若干年开发经验的普通的开发人员往往面临个人发展的瓶颈&#xff0c;即如何从普通开发人员转型成高层次的系统架构师和技术管理人员。想成为一名架构师&#xff0c;应当具备全面的知识体系&#xff0c;需要进行系统的学习和实践…

数组(一)-- LeetCode[26][80] 删除有序数组中的重复元素

1 删除有序数组中的重复项 1.1 题目描述 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次&#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度&#xff0c…

FreeRTOS与UCOSIII任务状态对比

FreeRTOS任务状态 1、运行态 正在运行的任务&#xff0c;正在使用处理器的任务。 单核处理器中任何时候都有且只有一个任务处于运行态。 2、就绪态 已经准备就绪&#xff08;非阻塞或挂起&#xff09;&#xff0c;可以立即运行但还没有运行的任务。 正在等待比自己高优先级…

Kafka——消息队列学习总结

定义 Kafka是一个分布式的基于发布/订阅模式的消息队列&#xff08;MQ)&#xff0c;主要应用于大数据实时处理领域。 模式 点对点模式 一对一&#xff0c;消费者主动拉取数据&#xff0c;消费收到后消息清除。 发布/订阅模式 一对多&#xff0c;消费者消费数据后不会清除消息…

JavaScript:使用for in不是一个很好的抉择

for in 如果让你遍历对象中的key和value&#xff0c;你第一个想到的一定是使用for in const o{name:"chengqige",age:23 } for (let key in o){console.log(key,o[key]); }看起来是没有问题的&#xff0c;但是如果我在下面加一行代码&#xff0c;输出的结果就可能让…

JUC并发编程——进程与线程

目录一、进程和线程的概念1.1 进程1.2 线程1.3 进程与线程对比二、并行和并发的概念三、线程基本应用3.1 多线程应用——异步调用一、进程和线程的概念 1.1 进程 ● 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 …