当前位置: 首页 > news >正文

C/C++基础:宏

C/C++基础:宏

  • 简述
  • 宏的简单使用
      • 基础语法
      • 带参宏(宏函数)
      • 宏参字符串化#
      • 宏拼接##
  • 宏的陷阱
      • 多行定义
      • 宏中的空格
      • 宏函数不是函数
      • 行末分号问题
        • 一些建议
  • 宏的奇妙使用

简述

宏作为C/C++最有特色的语言性质之一,犹如魔法一般,合理的使用可以极大的提高开发效率。

宏(Macro) 是C/C++的一个预处理指令,本质上是编译开始前进行的简单文本替换,我们可以定义一组宏代码片段,在程序中多次使用, 从而减少开发时间和精力。甚至我们可以使用宏来实现一些奇怪的操作,例如在C语言中利用宏做到如同模版一样的泛型编程。

宏的简单使用

基础语法

宏的基础使用方式如下:

#define 标识符 替换列表

比如,我们可以定义一个简单的宏PII,将其替换为浮点型字面量3.14

#define PII 3.14// 我们可以在代码里使用PII这个宏
int main(){std::cout << PII << std::endl;		// PII 被展开为 3.14return 0;
}

带参宏(宏函数)

我们同样可以定义一个宏函数,可以让我们像普通的函数一样来调用他,其写法和最普通的文本替换宏无太大区别,参数和函数一样用括号包裹,用逗号(,)分隔即可,如下我们编写一个宏实现乘法的功能。

#define mul(a, b) a*bint main(){std::cout << mul(2, 3) << std::endl;	// mul(2, 3)展开为 2*3return 0;
}

宏参字符串化#

有些时候,我们希望我们往宏传递的参数可以以字符串的形式展开,这个时候我们可以替换列表里使用#来将参数转为字符串。你可能觉得有些抽象,我保证看完下面这个例子,你就会知道什么是宏参字符串化:

#define str1(x) #x
#define str2(x) xint main() {const char* s1 = str1(hello);		// 本行代码展开为 const char* s1 = "hello"// const char* s2 = str2(hello);	// 错误的代码,本行代码展开为 const char* s2 = helloconst char* s3 = str2("hello");		// 本行代码展开为 const char* s2 = "hello"return 0;
}

宏拼接##

我们经常会有这样的需求,我们需要定义很多操作类似的函数,仅仅函数的前缀名不同,比如你可能需要在某处声明一系列这样的函数:

void func_1(int a, int b);
void func_2(int a, int b);
...
void func_n(int a, int b);

如果参数列表非常长的话,重复写这么多相似的函数声明无疑是难受的,你也许会想到利用宏来简化这个问题:

// 注意这是错误的实现
#define XX(n) void func_ n (int a, int b)int main(){XX(1);XX(2);...XX(n);
}

然而代码并没有像我们希望的一样展开,因为 func_ 和 n 被空格分开了,并没有正确拼接在一起, 我们可以通过##将参数和其他东西拼接起来,如下:

#define XX(n) void func_ ## n (int a, int b)int main(){XX(1);		//展开为 void func_1(int a, int b)XX(2);...XX(n);
}

宏的陷阱

看完上面的内容,恭喜你,基本已经学会了宏的语法.但是在你实际运用这些宏之前,还需要了解宏常见的陷阱,防止写出劣质乃至错误的代码.

多行定义

如果你是一个使用宏的新手,且习惯于使用函数,那么你是很有可能写出以下这样的宏的:

#define max(a, b){(a > b) ? a : b
}int main(){int c = max(1, 3);std::cout << c << std::endl;return 0;
}

然而当你兴致勃勃的写完代码,编译,发现编译器无情的报错了,你可以改成一行来保证正确性,如下:

#define max(a, b){(a > b) ? a : b}

但是如果这个宏的替换列表很长呢?写在一行未免过于臃肿了,其实我们通过换行符\来进行换行,如下:

// 这是正确的实现
#define max(a, b){		\(a > b) ? a : b		\
}
// 注意最后一行是不需要\的int main(){int c = max(1, 3);std::cout << c << std::endl;return 0;
}

宏中的空格

你也许会觉得,一个空格有什么大不了的,那么请你看下面的这个例子:

#define max (a, b){	\
(a > b) ? a : b		\
}int main() {int c = max(1, 3);std::cout << c << std::endl;return 0;
}/*
max(1, 3)展开如下
(a, b){
(a>b)? a: b
}(1,3)
/*

发现区别了吗?没错,我们在定义宏的时候,max和(a, b)之间不小心多写了一个空格,这会导致max(1, 3)完全展开为不同的结果.然而,在定义函数的时候,我们将函数名和()之间添加一个空格是完全无影响的,宏则需要完全避免这类事情。

宏函数不是函数

观察下面的简单宏函数和普通函数:

#define mul_m(a, b) a*bint mul_f(int a, int b){return a * b;
}

你觉得这个宏可以完全替代函数的功能吗?观察以下的代码:

#define mul(a, b) a*bint main(){std::cout << mul(2 + 1, 3) << std::endl;return 0;
}

看出问题了吗?mul(2+1, 3)被展开为了2 + 1 * 3 ,由于运算符的优先级问题,我们得到了期望外的结果,如何避免这种问题呢?其实只要加上括号来保证优先级即可,如下:

#define mul(a, b) ((a) * (b))

这下看起来终于完美了!
还是说,并没有?考虑下面这个代码,思考为什么这个代码和我们的预期不太一样:

#define mul(a) ((a) * (a))int main(){int a = 2;std::cout << mul(++a)<< std::endl;return 0;
}

相信通过文本展开你已经发现了,没错,mul(++a)被展开后出现了两个++a,这不是我们期望的,所以我们并不能盲目的用宏来替换函数。
实际上,宏还不能像函数那样进行自递归定义,如下的这样的宏是错误的:

#define A(x) 3
#define B(x) A(x) + B(x) + C(x)
#define C(x) A(x) + B(x)

行末分号问题

考虑下面的代码:

#define print(str) printf(#str);int main() {if (true) print(hello);elseprint(world);return 0;
}

你能发现问题吗?问题其实出在第一个print,这里展开后printf(“hello”)后面有两个分号,导致else与if无法进行匹配了。这里有这许多的解决方案,我们很容易保证分号的数量不出错。

一些建议

事实上,对于一部分宏函数我们总是建议可以利用do{}while(0)语句包裹起来,这样可以很好的处理宏的一些副作用,如下:

#define print(str) do {printf(#str)} while(0)

这样不仅保证了宏内的生命周期,还强制调用宏的时候需要像函数调用一样,在其后加上分号(考虑直接使用{} 包裹的场景)。

宏的奇妙使用

你现在已经学完了宏的使用方式,那么如何实现文章开头提到的泛型编程呢?其实十分简单,实现如下:

#include <iostream>
#define mySwap(T, a, b) do{ T tmp = a; a = b; b=tmp;}while(0)int main() {int a = 1, b = 0;mySwap(int, a, b);std::cout << a << ' ' << b << std::endl;return 0;
}

相关文章:

C/C++基础:宏

C/C基础&#xff1a;宏 简述宏的简单使用基础语法带参宏&#xff08;宏函数&#xff09;宏参字符串化#宏拼接## 宏的陷阱多行定义宏中的空格宏函数不是函数行末分号问题一些建议 宏的奇妙使用 简述 宏作为C/C最有特色的语言性质之一&#xff0c;犹如魔法一般&#xff0c;合理的…...

「豆包Marscode体验官」AI加持的云端IDE——三种方法高效开发前后端聊天交互功能

豆包 MarsCode 是一个集成了AI功能的编程助手和云端IDE&#xff0c;旨在提高开发效率和质量。它支持多种编程语言和IDE&#xff0c;提供智能代码补全、代码解释、单元测试生成和问题修复等功能&#xff0c;同时具备AI对话视图和开发工具。 豆包 MarsCode 豆包 MarsCode 编程助…...

一文带你掌握C++虚函数·和多态

9. C虚函数与多态 虚函数 virtual修饰的成员函数就是虚函数 虚函数对类的内存影响:需要增加一个指针类型的内存大小无论多少虚函数&#xff0c;只会增加一个指针类型的内存大小虚函数表的概念: 指向虚函数的指针 我们自己也可以通过虚函数表指针去访问函数(一般做这样的操作…...

OpenCV 4.10 + OpenCV_contrib配置教程 仅供参考

参考&#xff1a;https://blog.csdn.net/qq_27278957/article/details/108224325 https://blog.csdn.net/weixin_43763292/article/details/130232863 OpenCV&#xff1a;https://github.com/opencv/opencv/releases/tag/4.10.0 OpcenCV_contrib: https://github.com/opencv/o…...

ClkLog:开源用户行为分析框架,让数据分析更轻松

ClkLog&#xff1a;开源用户行为分析框架&#xff0c;让数据分析更轻松 在数据驱动的时代&#xff0c;找到一个好用的用户行为分析工具真是难上加难。但是今天你有福了&#xff0c;开源免费的 ClkLog 就是你的不二选择&#xff01;本文将为你详细介绍 ClkLog 的功能特点、技术架…...

Spring源码学习笔记之@Async源码

文章目录 一、简介二、异步任务Async的使用方法2.1、第一步、配置类上加EnableAsync注解2.2、第二步、自定义线程池2.2.1、方法一、不配置自定义线程池使用默认线程池2.2.2、方法二、使用AsyncConfigurer指定线程池2.2.3、方法三、使用自定义的线程池Excutor2.2.4、方法四、使用…...

面试题:如何验证代码的可靠性

代码结构上的&#xff1a; 1 可扩展性 是否否和开闭原则 2 性能&#xff0c;数据结构用的是否合理&#xff0c;算法等是否效率高。 3 安全性 是否存在潜在的安全 整数溢出 SQL注入 等 4 代码复杂度 圈负杂度 if嵌套深度 函数长度等 5 函数变量的命名是否具有自解释性 1. …...

前端开发的十字路口,薪的出口会是AI吗?

前言 在数字化转型的浪潮中&#xff0c;前端开发一直扮演着至关重要的角色&#xff0c;它连接着用户与产品之间的桥梁。然而&#xff0c;随着技术的不断进步和社会经济环境的变化&#xff0c;前端开发领域也面临着前所未有的挑战和机遇。 前端开发的困境 前端开发领域的竞争…...

pdf太大怎么压缩大小?这几种压缩方法操作起来很简单!

pdf太大怎么压缩大小&#xff1f;在数字化洪流席卷的当下&#xff0c;PDF文件的“臃肿”难题如同巨石般横亘于高效办公之路&#xff0c;它们不仅贪婪地吞噬着宝贵的存储空间&#xff0c;更如沉重的枷锁&#xff0c;拖曳着我们的工作进度&#xff0c;步入迟缓之境&#xff0c;试…...

leetcode-148. 排序链表

题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例 3&#x…...

16 html网页服务和nginx服务

第十六次7.29 1.静态页面 1安装httpd [rootweb ~]# yum -y install httpd 2.真机访问页面 [rootweb html]# echo "静态html文件" > index.html 传入照片再次访问 静态资源&#xff0c;根据开发着保存在项目资源目录中的路径访问静态页面的资源 2.Apache 1.安…...

C语言:扫雷游戏实现

一、扫雷游戏的分析和设计 扫雷游戏想必大家都玩过吧&#xff0c;初级的玩法是在一个9*9的棋盘上找到没有雷的格子&#xff0c;而今天我们就要做的就是9*9扫雷游戏的实现。 1、游戏功能和规则 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘…...

算法入门:Java实现排序、查找算法

链接&#xff1a;算法入门&#xff1a;Java实现排序、查找算法 (qq.com) 冒泡/选择/插入/希尔排序代码 (qq.com) 快排/归并/堆排/基数排序代码 (qq.com)...

【初阶数据结构篇】顺序表的实现(赋源码)

文章目录 本篇代码位置顺序表和链表1.线性表2.顺序表2.1 概念与结构2.2分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现2.3.1动态顺序表的初始化和销毁及打印2.3.2动态顺序表的插入动态顺序表的尾插动态顺序表的头插动态顺序表的在指定位置插入数据 2.3.3动态顺序表…...

移动式气象站:便携科技的天气守望者

在科技日新月异的今天&#xff0c;我们身边的许多设备都在向着更加智能化、便携化的方向发展。而在气象观测领域&#xff0c;移动式气象站的出现&#xff0c;不仅改变了传统气象观测的固有模式&#xff0c;更以其灵活性和实时性&#xff0c;在气象监测、灾害预警等领域发挥着越…...

软件测试必备 - 14个接口与自动化测试练习网站

随着互联网和移动应用的快速发展,接口和自动化测试的重要性日益凸显。越来越多的企业开始重视API测试,因为它不仅能提升开发效率,还能确保系统的稳定性和安全性。这些练习网站为测试人员提供了宝贵的资源,帮助他们掌握必要的技能,应对日益复杂的测试需求。 在软件测试的世…...

基于 HTML+ECharts 实现的数据可视化大屏案例(含源码)

数据可视化大屏案例&#xff1a;基于 HTML 和 ECharts 的实现 数据可视化已成为企业决策和业务分析的重要工具。通过直观、动态的图表展示&#xff0c;数据可视化大屏能够帮助用户快速理解复杂的数据关系&#xff0c;发现潜在的业务趋势。本文将介绍如何利用 HTML 和 ECharts 实…...

vardaccico前端私有库

vardacico docker pull verdaccio/verdaccio:4 docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio Docker | Verdaccio 拷贝docker中的配置到宿主机 进入docker内部 docker exec -it verdaccio /bin/sh 进入到指定目录 cd /verdaccio 开始拷贝到指定目…...

先用先发!小样本故障诊断新思路!Transformer-SVM组合模型多特征分类预测/故障诊断(Matlab)

先用先发&#xff01;小样本故障诊断新思路&#xff01;Transformer-SVM组合模型多特征分类预测/故障诊断&#xff08;Matlab&#xff09; 目录 先用先发&#xff01;小样本故障诊断新思路&#xff01;Transformer-SVM组合模型多特征分类预测/故障诊断&#xff08;Matlab&#…...

学习大数据DAY26 简单数据清洗练习和 Shell 脚本中的数据库编程

目录 上机练习 14 mysql 命令 sql 语句实现步骤 shell 脚本导入 csv 格式文件到 mysql 数据库 secure-file-priv 特性 把文件拷贝到 mysql 指定目录下 上机练习 15 mysqldump 命令 上机练习 16 上机练习 14 运用上一节课学的 Shell 工具完成 1. 清洗数据《infotest.t…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...