【C++】宏定义

严格来说,这个题目起名为C++是不合适的,因为宏定义是C语言的遗留特性。CleanCode并不推荐C++中使用宏定义。我当时还在公司做过宏定义为什么应该被取代的报告。但是适当使用宏定义对代码是有好处的。坏处也有一些。

无参宏定义

最常见的一种宏定义,如下:

#define NUM_1 1

需要注意的是,宏定义执行的是替换操作,在预处理阶段就完成了。因此编译期间或者运行期间的代码是感知不到宏定义的存在的,这也是宏定义不被推荐的原因——出了事很难找到问题。关于C++程序生成的各个阶段可以参考我的这篇文章:【C++】template方法undefined reference to(二):C++代码的编译过程

#include <iostream>
#define NUM_1 1

using namespace std;

int main()
{
    cout << NUM_1 << endl;
}

数字的类型是可以通过后缀指定的,比如1U代表unsigned int类型的1。

可以使用const代替常量宏:

const int NUM_1 = 1;

如果你的编译器支持C++11标准,那应该使用constexpr,这样const就只有“只读”的含义了。

constexpr int NUM_1 = 1;

有参宏定义

C++语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。你可以理解为一种“函数”。有参宏定义也是宏的另一个大量使用的用途。

一个最简单的使用三元表达式返回更大值的宏定义:

#define MAX(a, b) a > b? a: b

代码中使用:

#include <iostream>
#define MAX(a, b) a > b? a: b

using namespace std;

const int NUM_1 = 1;

int main()
{
    int a = MAX(1,2);
    cout << a << endl;
}

这种宏定义的优点在于不会受参数类型的影响,现代C++提倡使用模板方法代替之:

template <typename T>
T max(T &a, T *b)
{
    return a > b ? a : b;
}

调用的地方基本一致:

    int b = max(1, 2);
    cout << b << endl;

由于模板方法本质是将函数实现挪到了编译期,对所有模板类型的调用生成对应的函数,所以,它和正常的函数调用没有任何区别。可以直接写在里面:

	cout << max(1, 2) << endl;

模板方法也存在很多问题,比如多文件编译时存在声明实现不可分离的问题
:【C++】template方法undefined reference to

宏定义的副作用

仍然以刚才的MAX宏为例
感谢知乎闪耀大叔提供的例子,原文链接从Linux内核中学习高级C语言宏技巧

为了方便理解,我把所有数字都改成二进制标识:

int main(void)
{
  int i = 0b1110;
  int j = 0b0011;
  printf ("i&0b101 = %d\n",i&0b101);
  printf ("j&0b101 = %d\n",j&0b101);
  printf("max=%d\n",MAX(i&0b101,j&0b101));
  return 0;
}

输出结果为:
输出
显然不符合预期,问题在哪呢?因为>运算符优先级大于&,所以会先进行比较再进行按位与。

Linux 内核中的写法其实是这样的:

#define MAX(x, y) ({        \
  typeof(x) _max1 = (x);      \
  typeof(y) _max2 = (y);      \
  (void) (&_max1 == &_max2);    \
  _max1 > _max2 ? _max1 : _max2; })

具体可以看上面的文章,这里就不展开了。

实现语法糖

C++有一些约定俗成的写法,其实可以用宏定义进行简化。

比如可以将if-continue简化为一个宏:

#define CONTINUE_IF(exp) \
    if (exp)             \
    continue

还有其他比如return相关的:

#define RETURN_IF_VOID(exp) \
    if (exp)                \
    return

#define RETURN_IF(exp, result) \
    if (exp)                   \
    return result

代码里就可以替换,这里仅给出一个例子:

int main()
{
    for (int i = 0; i < 10; i++)
    {
        CONTINUE_IF(i % 2 == 0);
        printf("%d, ", i);
    }
}

另外一种情况,比如C++的多态的一个重要实现就是虚函数和继承,我们可以简化虚函数的写法。定义如下的宏:

#define OVERRIDE(exp) virtual exp override

就可以简化虚函数继承的写法。如下:

struct Student
{
    virtual void printName()
    {
        printf("Student Name\n");
    }
};

struct PrimaryStudent : Student
{
    OVERRIDE(void printName())
    {
        printf("PrimaryStudent Name\n");
    }
};

void printName(Student& student)
{
    student.printName();
}

override关键字只有在C++11以后才能生效,我们可以使用__GNUC__宏来判断。 __GNUC__ 的值表示gcc的版本。需要针对gcc特定版本编写代码时,可以使用该宏进行条件编译。C++11标准从GCC4.8.1版本完全支持,__GNUC____GNUC_MINOR____GNUC_PATCHLEVEL__分别代表gcc的主版本号,次版本号,修正版本号。我们可以写出如下判断:

#ifdef __GNUC__
    printf("__GNUC__ = %d\n", __GNUC__);
#endif
#ifdef __GNUC_MINOR__
    printf("__GNUC_MINOR__ = %d\n", __GNUC_MINOR__);
#endif
#ifdef __GNUC_PATCHLEVEL__
    printf("__GNUC_PATCHLEVEL__ = %d\n", __GNUC_PATCHLEVEL__);
#endif

输出结果为:
gcc版本
和控制台的输出是一致的:

PS D:\Codes\CPP\VSCodeProjects\2024\June\CPPMacros> g++ --version
g++.exe (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

接下来我们写一个条件判断宏,仅在4.8.1版本后在虚函数后加上overrride标识。由于gcc版本宏判断过于复杂,我们用一个宏先将版本号转为整数:

#define GCC_VERSION (__GNUC__ * 10000 \
                   + __GNUC_MINOR__ * 100 \
                   + __GNUC_PATCHLEVEL__)

然后判断版本号是否大于40801,否则不使用override关键字:

#if GCC_VERSION > 40801
#define OVERRIDE(exp) virtual exp override
#else
#define OVERRIDE(exp) virtual exp
#endif

遗憾的是我本地没有多个编译器,没法判断这个代码是否成功了。

简化代码

我们有时会碰到一个类有多个类似的方法的情况,比如:

struct Student
{
public:
    int getAge();
    int getNumber();
    int getPoint();
};

此时可以使用宏来简化这种写法。__VA_ARGS__ 是一个可变参数的宏。将宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,替换省略号所代表的字符串。##运算符可以用于函数宏的替换部分,这个运算符把两个语言符号组合成单个语言符号。

可以写出如下代码:

#define GET(...) int get##__VA_ARGS__()
struct Student
{
public:
    GET(Age);
    int getNumber();
    int getPoint();
};

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

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

相关文章

Python 面试【中级】

欢迎莅临我的博客 &#x1f49d;&#x1f49d;&#x1f49d;&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【游戏引擎之路】登神长阶(五)

5月20日-6月4日&#xff1a;攻克2D物理引擎。 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 6月23日-6月30日&#xff1a;攻克《Windows游戏编程大师技巧》。 …

厦门新能安科技Ampace校招实习待遇及Verify测评SHL演绎数字推理历年真题题库

一、厦门新能安科技公司介绍 厦门新能安科技有限公司主要业务包括电池制造和销售&#xff0c;电容器及其配套设备制造与销售&#xff0c;电池零配件生产与销售。此外&#xff0c;公司还提供包括技术服务、技术开发、技术咨询、技术交流、技术转让和技术推广在内的全方位服务。公…

安卓开发app-基础的java项目构建补充知识

安卓开发app-基础的java项目构建补充知识&#xff01;上一次分享了基础的项目构建&#xff0c;但是还遗漏了一些基础的内容。今天补充完整。 首先&#xff0c;是关于项目的一些配置文件的信息。 第一个配置文件&#xff1a;{setting.gradle} 国内阿里云仓库地址信息&#xff1…

深度学习基准模型Mamba

深度学习基准模型Mamba Mamba(英文直译&#xff1a;眼镜蛇)具有选择性状态空间的线性时间序列建模&#xff0c;是一种先进的状态空间模型 (SSM)&#xff0c;专为高效处理复杂的数据密集型序列而设计。 Mamba是一种深度学习基准模型&#xff0c;专为处理长序列数据而设计&…

WAIC2024 | 华院计算邀您共赴2024年世界人工智能大会,见证未来科技革新

在智能时代的浪潮汹涌澎湃之际&#xff0c;算法已成为推动社会进步的核心力量。作为中国认知智能技术的领军企业&#xff0c;华院计算在人工智能的广阔天地中&#xff0c;不断探索、创新&#xff0c;致力于将算法的潜力发挥到极致。在过去的时日里&#xff0c;华院计算不断探索…

界面控件Telerik UI for Winforms 2024 Q2新版亮点 - 发布全新的AI相关组件

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件&#xff0c;所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 本文将介绍界面组件Telerik UI for Winform…

4个文章生成器免费版分享,让文章创作更轻松便捷

在当今这个信息飞速传播的时代&#xff0c;文章创作的重要性愈发凸显。无论是从事内容创作的专业人士&#xff0c;还是偶尔需要撰写文章的普通大众&#xff0c;都希望能更高效地完成文章创作任务。而在实际操作中&#xff0c;我们常常会遇到思路卡顿、没有创作灵感的问题。今天…

AWT的菜单组件

AWT的菜单组件 前言一、菜单组件的介绍常见的菜单相关组件常见菜单相关组件集成体系图菜单相关组件使用小要点 二、AWT菜单组件的代码示例示例一示例二实现思路 前言 推荐一个网站给想要了解或者学习人工智能知识的读者&#xff0c;这个网站里内容讲解通俗易懂且风趣幽默&…

pyqt 文件浏览列表视图和图标视图

pyqt 文件浏览列表视图和图标视图 目的效果代码 目的 使用pyqt实现文件浏览列表视图和图标视图&#xff0c;像电脑文件浏览一样。如下图所示。 效果 代码 import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QToolBar, QAction, Q…

同元软控受邀出席2024年工业软件与新质生产力创新发展论坛

近日&#xff0c;由广东省工业软件学会主办的“2024年工业软件与新质生产力创新发展论坛”在广州成功举办。同元软控深圳子公司副总经理周胜受邀出席&#xff0c;并作《数智驱动创新&#xff0c;科学计算与系统建模仿真加速新质生产力进化》主题演讲。 本次论坛集结工业软件界…

用pycharm进行python爬虫的步骤

使用 pycharm 进行 python 爬虫的步骤&#xff1a;下载并安装 pycharm。创建一个新项目。安装 requests 和 beautifulsoup 库。编写爬虫脚本&#xff0c;包括获取页面内容、解析 html 和提取数据的代码。运行爬虫脚本。保存和处理提取到的数据。 用 PyCharm 进行 Python 爬虫的…

【日常记录】【JS】SSE 流式传输 ChatGPT 的网络传输模式

文章目录 1、SSE 流式传输2、后端代码3、前端代码5、SSE和WS 对比6、chatgpt SSE的服务端返回的数据参考链接 单工通信是一种单向的通信方式&#xff0c;其中信息只能从发送端传输到接收端&#xff0c;而接收端不能向发送端发送任何信息。在Web开发中&#xff0c;Server-Sent E…

【UML用户指南】-24-对高级行为建模-进程和线程

目录 1、概念 2、主动类 3、通信 4、同步 5、常用建模技术 5.1、对多控制流建模 5.2、对进程间通信建模 在UML中&#xff0c;可以将每一个独立的控制流建模为一个主动对象&#xff0c;它代表一个能够启动控制活动的进程或线程。 进程是一个能与其他进程并发执行的重量级…

chrome.storage.local.set 未生效

之前chrome.storage.local.set 和 get 一直不起作用 使用以下代码运行成功。 chrome.storage.local.set({ pageState: "main" }).then(() > {console.log("Value is set");});chrome.storage.local.get(["pageState"]).then((result) > …

JAVA学习笔记-JAVA基础语法-DAY19-File类、递归

第一章 File类 1.1 概述 java.io.File 类是文件和目录路径名的抽象表示&#xff0c;主要用于文件和目录的创建、查找和删除等操作。 1.2 构造方法 public File(String pathname) &#xff1a;通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。public File(St…

〔002〕虚幻 UE5 发送 get、post 请求、读取 json 文件

✨ 目录 ▷ 安装 varest 扩展▷ 开启 varest 扩展▷ 发送 get 请求▷ 发送 post 请求▷ 读取 json 文件 ▷ 安装 varest 扩展 打开 虚幻商城&#xff0c;搜索 varest 关键字进行检索&#xff0c; varest 是一个 api 调用插件&#xff0c;支持 http/https 请求&#xff0c;也支…

【成都活动邀请函】7月6 | PowerData 数字经济-“成都“开源行!

【成都活动邀请函】7月6 | PowerData 数字经济-"成都"开源行&#xff01; 活动介绍活动信息线上直播扫码报名往期活动回顾专注数据开源&#xff0c;推动大数据发展 活动介绍 九天开出一成都&#xff0c;万户千门入画图。 自古以来&#xff0c;成都便是国家发展的重要…

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 下载页 ->Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人…