【C++】宏定义

news2024/10/6 1:45:42

严格来说,这个题目起名为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();
};

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

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

相关文章

怎么监控公司文件?高效省力的7个办法,企业都在用

公司文件监控方法主要包括以下几个方面&#xff0c;以确保数据安全和防止文件泄密&#xff1a; 使用专业监控软件&#xff1a;如安企神等专业的企业级监控软件&#xff0c;可以详细记录员工的电脑操作&#xff0c;包括文件访问、修改、删除、复制等行为&#xff0c;以及外设使用…

CLAY或许是今年最值得期待的3D生成模型,号称质量最好+布线最好+支持的输入模态最多+支持材质生成。

CLAY是一种大规模可控生成模型,用于创建高质量的3D资产,它结合了多分辨率变分自编码器和简化的潜在扩散变压器,通过多种输入形式生成详细的3D几何结构和物理渲染材质。 CLAY或许是今年最值得期待的3D生成模型,号称质量最好+布线最好+支持的输入模态最多+支持材质生成。 相…

1、音视频解封装流程---解复用

对于一个视频文件(mp4格式/flv格式)&#xff0c;audio_pkt或者video_pkt是其最基本的数据单元&#xff0c;即视频文件是由独立的视频编码包或者音频编码包组成的。 解复用就是从视频文件中把视频包/音频包单独读取出来保存成独立文件&#xff0c;那么如何得知packet是视频包还是…

【高考】人生规划指南

作为一个正处在这个选择的十字路口的高考考生&#xff0c;我认为在选择专业和学校时&#xff0c;要根据自己的具体情况和个人目标来权衡。首先&#xff0c;我认为专业是首要考虑因素。因为专业是直接决定未来职业发展方向的&#xff0c;如果不喜欢或者不适合的专业选择&#xf…

Elasticsearch开启认证|为ES设置账号密码|ES账号密码设置|ES单机开启认证|ES集群开启认证

文章目录 前言单节点模式开启认证生成节点证书修改ES配置文件为内置账号添加密码Kibana修改配置验证 ES集群开启认证验证 前言 ES安装完成并运行&#xff0c;默认情况下是允许任何用户访问的&#xff0c;这样并不安全&#xff0c;可以为ES开启认证&#xff0c;设置账号密码。 …

51单片机学习——LED功能一系列实现

目录 一、开发前准备 二、点亮LED 三、LED闪烁 四、LED流水灯 五、LED流水灯plus 一、开发前准备 开发工具软件 烧录软件 其次还需要一块51单片机学习开发板及原理图 keil创造project文件及开启生成.hex文件 二、点亮LED 看二位进制对照原理图&#xff1b; #include <…

面试突击:HashMap 源码详解

本文已收录于&#xff1a;https://github.com/danmuking/all-in-one&#xff08;持续更新&#xff09; 数据结构 JDK1.8 之前 JDK1.8 之前 HashMap 采用 数组和链表 结合的数据结构。如下图&#xff1a; HashMap 将 key 的 hashCode 经过扰动函数处理过后得到 hash 值&#…

Echarts地图实现:杭州市困难人数分布【动画滚动播放】

Echarts地图实现&#xff1a;杭州市困难人数分布 实现功能 杭州市地区以及散点图分布结合的形式数据展示动画轮播可进去杭州市下级地区可返回杭州市地图展示 效果预览 实现思路 使用ECharts的地图和散点图功能结合实现地区分布通过动画轮播展示数据变化实现下级地区数据的展…

VTK学习日志:基于VTK9.3.0+Visual Studio c++实现DICOM影像MPR多平面重建+V R体绘制4个视图展示功能的实现(二)

前段时间对VTK9.3.0进行了编译&#xff0c;开发了MPRVR实现的demo,显示效果不是很理想&#xff0c;正好趁着周末有时间&#xff0c;再度对之前的程序进行优化和完善&#xff0c;先展示下效果&#xff1a; VTK实现MPRVR四视图 再次讲解下基于VTK的MPRVR实现的简单项目创建过程&a…

HTTPS是什么?原理是什么?用公钥加密为什么不能用公钥解密?

HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是HTTP的安全版本&#xff0c;它通过在HTTP协议之上加入SSL/TLS协议来实现数据加密传输&#xff0c;确保数据在客户端和服务器之间的传输过程中不会被窃取或篡改。 HTTPS 的工作原理 客户端发起HTTPS请求&…

第三天:LINK3D核心原理讲解【第1部分】

第三天:LINK3D核心原理讲解 LINK3D学习笔记 目标 了解LINK3D velodyne64线激光雷达LINK3D质心点提取效果: 分布在车道与墙体的交界处。 课程内容 LINK3D论文精讲LINK3D聚合关键点提取代码讲解LINK3D描述子匹配代码讲解除了ALOAM的线特征、面特征,还有其他点云特征吗,是…

【项目】论坛系统项目自动化测试

论坛系统项目自动化测试 前述一、脑图二、代码编写1.公共类InitAndEnd1.登录页面测试ForumLoginTest正常登录&#xff1a;异常登录&#xff1a; 3.注册页面测试ForumRegisterTest注册成功&#xff1a;注册失败&#xff1a; 4论坛列表页面测试ForumListTest登录状态下&#xff1…

<电力行业> - 《第10课:变电》

1 变电 变电环节&#xff0c;顾名思义就是改变电压的环节&#xff0c;主要是在变电站和变电所完成的。变电站和变电所主要区别在于&#xff1a;变电站比变电所更大。 发电厂的变压器和配电变压器也属于“变电”&#xff0c;但我们在说电网环节时&#xff0c;变电特指电网公司…

python基础:设置代码格式

随着编写的程序越来越长&#xff0c;有必要了解一些代码格式的约定&#xff0c;让你的代码尽可以能易于阅读。 python代码编写规范为PEP8&#xff0c;有兴趣的朋友可以下载观看&#xff0c;这里仅作简要说明。 1、缩进 PEP8建议每级缩进都使用4个空格。多数情况下编程语言的…

无人机智能追踪反制系统技术详解

随着无人机技术的飞速发展&#xff0c;无人机在各个领域的应用越来越广泛。然而&#xff0c;无人机的无序飞行和非法使用也带来了一系列安全隐患和威胁。因此&#xff0c;无人机智能追踪反制系统应运而生&#xff0c;成为维护公共安全和防止无人机滥用的重要工具。本文将详细介…

深度学习评价指标:Precision, Recall, F1-score, mIOU, 和 mDice

在深度学习和机器学习中&#xff0c;评价模型性能是至关重要的一环。本文将详细讲解一些常见的评价指标&#xff0c;包括精确率&#xff08;Precision&#xff09;、召回率&#xff08;Recall&#xff09;、F1-score、平均交并比&#xff08;mIOU&#xff09;和平均Dice系数&am…

ConcurrentHashMap是如何保证线程安全的-put方法简要分析

简介 ConcurrentHashMap 是 Java 中并发编程中常用的线程安全的哈希表&#xff08;HashMap&#xff09;实现。它具有以下几个显著的特点和优点&#xff0c;适合在特定的并发场景中使用&#xff1a; 线程安全性&#xff1a; ConcurrentHashMap 提供了并发访问的线程安全保证&am…

AWT的菜单组件

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

高考结束,踏上西北的美食之旅

高考的帷幕落下&#xff0c;暑期的阳光洒来&#xff0c;是时候放下书本&#xff0c;背上行囊&#xff0c;踏上一场充满期待的西北之旅。而在甘肃这片广袤的土地上&#xff0c;除了壮丽的自然风光&#xff0c;还有众多令人垂涎欲滴的美食等待着您的品尝。当您踏入甘肃&#xff0…

创建一个vue3+vite+ts项目

目录 创建项目 ​编辑 下载jsx 插件 在根目录在新建.env vue.config.js tsconfig.json tsconfig.node.json 下载ui组件库和路由&#xff08;组件库根据自己的项目需要选择&#xff09; 在根目录下新建views/index.tsx 在根目录下新建router/index.ts 修改App.vue 创建…