【C++】宏定义

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

无参宏定义

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

#define NUM_1 1

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

#include <iostream>
#define NUM_1 1using 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: busing 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();
};

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

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

相关文章

新声创新20年:无线技术给助听器插上“娱乐”的翅膀

听力损失并非现代人的专利&#xff0c;古代人也会有听力损失。助听器距今发展已经有二百多年了&#xff0c;从当初单纯的声音放大器到如今的全数字时代助听器&#xff0c;助听器发生了翻天覆地的变化&#xff0c;现代助听器除了助听功能&#xff0c;还具有看电视&#xff0c;听…

C++ 和C#的差别

首先把眼睛瞪大&#xff0c;然后憋住一口气&#xff0c;读下去&#xff1a; 1、CPP 就是C plus plus的缩写&#xff0c;中国大陆的程序员圈子中通常被读做"C加加"&#xff0c;而西方的程序员通常读做"C plus plus"&#xff0c;它是一种使用非常广泛的计算…

Maya崩溃闪退常见原因及解决方案

Autodesk Maya 是一款功能强大的 3D 计算机图形程序&#xff0c;被电影、游戏和建筑等各个领域的设计师广泛使用。然而&#xff0c;Maya 就像任何其他软件一样可能会发生崩溃问题。在前文中&#xff0c;小编给大家介绍了3ds Max使用V-Ray渲染时的崩溃闪退解决方案&#xff1a; …

后端之路第三站(Mybatis)——JDBC跟Mybatis、lombok

一、什么是JDBC JDBC就是sun公司研发的一套通过java来操控数据库的工具&#xff0c;对应不同的数据库系统有不同的JDBC&#xff0c;而他们统称【驱动】&#xff0c;这就是上一篇我们提到创建Mybatis项目时要引入的依赖、以及连接数据库四要素里的第一要素。 JDBC有自己一套原始…

Elasticsearch:Painless scripting 语言(一)

Painless 是一种高性能、安全的脚本语言&#xff0c;专为 Elasticsearch 设计。你可以使用 Painless 在 Elasticsearch 支持脚本的任何地方安全地编写内联和存储脚本。 Painless 提供众多功能&#xff0c;这些功能围绕以下核心原则&#xff1a; 安全性&#xff1a;确保集群的…

近红外光谱脑功能成像(fNIRS):1.光学原理、变量选取与预处理

一、朗伯-比尔定律与修正的朗伯-比尔定律 朗伯-比尔定律 是一个描述光通过溶液时被吸收的规律。想象你有一杯有色液体&#xff0c;比如一杯红茶。当你用一束光照射这杯液体时&#xff0c;光的一部分会被液体吸收&#xff0c;导致透过液体的光变弱。朗伯-比尔定律告诉我们&#…

redis主从复制哨兵模式集群管理

主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。缺陷&#xff1a;故障恢复无法自动化&#xff1b;写操作无法负载均衡&…

HbuilderX:安卓打包证书.keystore生成与使用

前置条件 已安装jdk或配置好jre环境。 .keystore生成 打开cmd,切换到目标路径,输入以下命令, keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore 输入密钥库口令(要记住), 然后输入一系列信息, …

ui.perfetto.dev sql 查询某个事件范围内,某个事件的耗时并降序排列

ui.perfetto.dev sql 查询某个事件范围内,某个事件的耗时并降序排列 1.打开https://ui.perfetto.dev 导入Chrome Trace Json文件2.ParallelMLP.forward下的RowParallelLinear.forward3.点击Query(SQL),在输入框中输入以下内容,按CtrlEnter,显示查询结果4.点击Show timeline,点击…

2024年07年01日 Redis数据类型以及使用场景

String Hash List Set Sorted Set String&#xff0c;用的最多&#xff0c;对象序列化成json然后存储 1.对象缓存&#xff0c;单值缓存 2.分布式锁 Hash&#xff0c;不怎么用到 1.可缓存经常需要修改值的对象&#xff0c;可单独对对象某个属性进行修改 HMSET user {userI…

C++基础(三):C++入门(二)

上一篇博客我们正式进入C的学习&#xff0c;这一篇博客我们继续学习C入门的基础内容&#xff0c;一定要学好入门阶段的内容&#xff0c;这是后续学习C的基础&#xff0c;方便我们后续更加容易的理解C。 目录 一、内联函数 1.0 产生的原因 1.1 概念 1.2 特性 1.3 面试题 …

用随机森林算法进行的一次故障预测

本案例将带大家使用一份开源的S.M.A.R.T.数据集和机器学习中的随机森林算法&#xff0c;来训练一个硬盘故障预测模型&#xff0c;并测试效果。 实验目标 掌握使用机器学习方法训练模型的基本流程&#xff1b;掌握使用pandas做数据分析的基本方法&#xff1b;掌握使用scikit-l…

珠江电缆,承载您梦想的每一度电

在现代社会&#xff0c;电力无处不在&#xff0c;它不仅是经济发展的动力&#xff0c;更是每个人生活中不可或缺的能量来源。而在这个电力驱动的世界里&#xff0c;有一家企业默默地承载着千家万户的梦想&#xff0c;它就是珠江电缆。 连接梦想的每一度电 珠江电缆成立于2001…

绝区零国际服下载 一键下载绝区零国际服教程

绝区零是一款米哈游倾情打造的全新都市幻想动作角色扮演游戏。在游戏中&#xff0c;我们将扮演一名绳匠&#xff0c;这是为出于各种原因需要进入危险空洞的人提供指引的专业人士。您将与独特的角色一起踏上冒险之旅&#xff0c;携手探索空洞&#xff0c;对战强大敌人&#xff0…

Steam夏促怎么注册 Steam夏促账号注册教程

随着夏日的炙热渐渐充斥着每一个角落&#xff0c;Steam平台也赶来添热闹&#xff0c;推出了一系列让人眼前一亮的夏季促销活动。如果你也是游戏爱好者&#xff0c;我们肯定不能错过这次的steam夏促。正直本次夏日促销有着很多的游戏迎来史低和新史低&#xff0c;有各种各样的游…

20240703在飞凌OK3588-C开发板上刷Rockchip原厂的Buildroot20220811

20240703在飞凌OK3588-C开发板上刷Rockchip原厂的Buildroot20220811 2024/7/3 18:25 详细的刷机LOG&#xff1a; [BEGIN] 2024/7/3 18:18:49 rootRK3588:/# DDR Version V1.07 20220412 LPDDR4X, 2112MHz channel[0] BW16 Col10 Bk8 CS0 Row16 CS1 Row16 CS2 Die BW16 Size204…

TP8/6 更改后台入口地址admin改为myadmin 隐藏真实后台网址

原来www.xxx.com/admin 改后www.xxx.com/myadmin config/app.php // 应用映射&#xff08;自动多应用模式有效&#xff09;app_map > [admintest>admin],

为何同一PDF文档用不同软件打印效果不同?

通过扫描仪生成的同一PDF文档&#xff0c;同样的设置&#xff0c;为什么别的电脑打出来是白底我的打出来有灰色格子背景&#xff1f;这种情况通常是由于PDF阅读软件的不同造成的差异。 ### 可能的原因和解决方法&#xff1a; 1. **PDF阅读软件的不同**&#xff1a; - **解决方…

Vue3轻松创建交互式仪表盘

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 Plotly.js 的 Vue 仪表盘组件 应用场景介绍 仪表盘是一种交互式可视化工具&#xff0c;用于监控和分析关键指标。它广泛应用于各种行业&#xff0c;例如金融、医疗保健和制造业。 代码基本功能介绍 本…

FFmpeg 命令行 音视频格式转换

&#x1f4da;&#xff1a;FFmpeg 提供了丰富的命令行选项和功能&#xff0c;可以用来处理音视频文件、流媒体等&#xff0c;掌握命令行的使用&#xff0c;可以有效提高工作效率。 目录 一、视频转换和格式转换 &#x1f535; 将视频文件转换为另一种格式 &#x1f535; 指定…