C 语言函数宏的几种封装方式

目录

1. 函数宏介绍

2. {} 方式

3. do{...}while(0) 方式

4. ({}) 方式

5. 总结


1. 函数宏介绍

函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。

函数宏本质上为宏,可以直接进行定义,例如:

#define INT_SWAP(a,b) \int tmp = a;    \a = b;          \b = tmp

但上述的宏具有一个明显的缺点:当遇到 ifwhile 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b  b = tmp 均不受控制语句所作用。

因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}do{...}while(0)  ({})。下文将一一对三种方式进行分析,比较各自的优劣点。

2. {} 方式

INT_SWAP 宏使用 {} 封装后形态如下:

#define INT_SWAP(a,b)\
{                   \int tmp = a;    \a = b;          \b = tmp;        \
}

此时,直接调用与在无花括号的控制语句(如 ifwhile)中调用均能正常运行,例如:

#define INT_SWAP(a,b) \
{                   \int tmp = a;    \a = b;          \b = tmp;        \
}int main()
{int var_a = 1;int var_b = 2;INT_SWAP(var_a, var_b);printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1if (1)INT_SWAP(var_a, var_b);printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2
}

但当无花括号的 if 语句存在其他分支(else ifelse 等)如:

if (1)INT_SWAP(var_a, var_b);
elseprintf("hello world!\n");

会发现编译出错:

...
/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’else

这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。

因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如:

/* port.1 */
if (1)INT_SWAP(var_a, var_b)
else
{printf("hello world!\n");
}/* port.2 */
if (1)
{INT_SWAP(var_a, var_b);
}
else
{printf("hello world!\n");
}

可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。

优缺点总结:

  • 优点:简单粗暴。

  • 缺点:不能在无花括号且有分支的 if 语句中直接调用;能够不带 ; 直接调用。

3. do{...}while(0) 方式

INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:

#define INT_SWAP(a,b)   \
do{                     \int tmp = a;        \a = b;              \b = tmp;            \
}while(0)

do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;

由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:

#define INT_SWAP(a,b)  \
do{                 \if (a < 0 || b < 0) \break;   \int tmp = a;     \a = b;           \b = tmp;         \
}while(0)

由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:

#define INT_SWAP(a,b)  \
do{                 \if (a < 0 || b < 0) \break;   \int tmp = a;     \a = b;           \b = tmp;         \
}while(0)int main()
{int var_a = 1;int var_b = 2;if (1)INT_SWAP(var_a, var_b);elseprintf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;
}

C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:

if (1)INT_SWAP(var_a, var_b)
else
{printf("hello world!\n"); 
}

优缺点总结:

  • 优点:支持在无花括号且有分支的 if 语句中直接调用;支持提前退出函数宏;强制调用时必须使用 ;

  • 缺点:无返回值,不能作为表达式的右值使用。

4. ({}) 方式

({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。

INT_SWAP 宏使用 ({}) 封装后形态如下:

#define INT_SWAP(a,b)   \
({                      \int tmp = a;        \a = b;              \b = tmp;            \
})

与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:

#define INT_SWAP(a,b)  \
({                 \int tmp = a;    \a = b;          \b = tmp;        \
})int main()
{int var_a = 1;int var_b = 2;if (1)INT_SWAP(var_a, var_b);elseprintf("hello world!\n");printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;
}

与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?

答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:

int main()
{int a = ({10;1000;});printf("a = %d\n", a);      // a = 1000
}

因此,({}) 可以为函数宏提供返回值。例如:

#define INT_SWAP(a,b)  \
({                 \int ret = 0;  \if (a < 0 || b < 0) \{     \ret = -1;  \}     \else    \{     \int tmp = a;    \a = b;          \b = tmp;        \}     \ret;    \
})int main()
{int var_a = 1;int var_b = 2;if (INT_SWAP(var_a, var_b) != -1)printf("swap success !!\n");     // swap success !!elseprintf("swap fail !!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;
}

可见,此时的 INT_SWAP 宏已与函数十分接近。

优缺点总结:

  • 优点:支持在无花括号且有分支的 if 语句中直接调用;有返回值,支持作为表达式的右值。

  • 缺点:不支持提前退出函数宏;非 C 的原生语法,编译器可能不支持。

5. 总结

综上,在 {}do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。

本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

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

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

相关文章

羊城杯2023 部分wp

目录 D0nt pl4y g4m3!!!(php7.4.21源码泄露&pop链构造) Serpent(pickle反序列化&python提权) ArkNights(环境变量泄露) Ez_misc(win10sinpping_tools恢复) D0nt pl4y g4m3!!!(php7.4.21源码泄露&pop链构造) 访问/p0p.php 跳转到了游戏界面 应该是存在302跳转…

el-table根据data动态生成列和行

css //el-table-column加上fixed后会导致悬浮样式丢失&#xff0c;用下面方法可以避免 .el-table__body .el-table__row.hover-row td{background-color: #083a78 !important; } .el-table tbody tr:hover>td {background: #171F34 !important; }html <el-table ref&quo…

1783_CMD启动MATLAB同时执行一个脚本

全部学习汇总&#xff1a; GitHub - GreyZhang/g_matlab: MATLAB once used to be my daily tool. After many years when I go back and read my old learning notes I felt maybe I still need it in the future. So, start this repo to keep some of my old learning notes…

ELK框架Logstash配合Filebeats和kafka使用

ELK框架Logstash配合Filebeats和kafka使用 本文目录 ELK框架Logstash配合Filebeats和kafka使用配置文件结构input为标准输入&#xff0c;output为标准输出input为log文件output为标准输出output为es input为tcpspringboot配置logstash配置 input为filebeatsfilebeats配置logsta…

Matlab论文插图绘制模板第111期—带线标记的图

本期分享的是带线标记注释的图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下载。有需要的朋友可以关注同名公号【阿昆的科研日常】&#xff0c;后台回复关键词【绘图桶】查看加入方式。 模板中最…

PV PVC in K8s

摘要 在Kubernetes中&#xff0c;PV&#xff08;Persistent Volume&#xff09;和PVC&#xff08;Persistent Volume Claim&#xff09;是用于管理持久化存储的重要资源对象。PV表示存储的实际资源&#xff0c;而PVC表示对PV的声明性要求。当应用程序需要使用持久化存储时&…

Android 网络配置

adb root adb shell 改变网卡网址 ifconfig eth0 192.168.0.167 up 添加虚拟网卡 ifconfig eth0:0 192.168.10.10 up 以上的命令就可以在eth0网卡上创建一个叫eth0:0的虚拟网卡,他的地址是:192.168.10.10 删除虚拟网卡 ifconfig eth0:0 down ip route 查看路由表的内容 …

【数据结构】如何设计循环队列?图文解析(LeetCode)

LeetCode链接&#xff1a;622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; 目录 做题思路 只开辟 k 个空间 多开一个空间 代码实现 1. 循环队列的结构 2. 开辟空间 3. 判断空 4. 判断满 5. 队尾插入数据 6. 队头删除数据 7. 获取队头元素 8. 获取队尾元…

炫酷的开关--20230907

Night && Day Toggle ☀️/&#x1f319; [Completed It!] HTML&#xff1a; <div class"controls"><label for"sync">Sync <body></label><input id"sync" type"checkbox"/> </div> &l…

基于javaweb的网上图书销售系统(servlet+jsp)

系统简介 本项目采用eclipse工具开发&#xff0c;jspservletjquery技术编写&#xff0c;数据库采用的是mysql&#xff0c;navicat开发工具。 角色&#xff1a; 管理员普通用户 模块简介 管理员&#xff1a; 登录用户管理图书分类管理图书管理图书订单管理图书评论管理数据统…

【通俗理解】CNN卷积神经网络 - 附带场景举例

一. CNN 算法概述 CNN的全称是Convolutional Neural Networks, ConvNets&#xff0c;称之为卷积神经网络&#xff0c;是深度学习的经典算法之一。 CNN一般用于图片分类、检索、人脸识别、目标定位等。在常规的图像处理的过程中&#xff0c;存在以下两个问题&#xff1a; 图像…

【2023高教社杯】D题 圈养湖羊的空间利用率 问题分析、数学模型及MATLAB代码

【2023高教社杯】D题 圈养湖羊的空间利用率 问题分析、数学模型及MATLAB代码 1 题目 题目 D 题 圈养湖羊的空间利用率 规模化的圈养养殖场通常根据牲畜的性别和生长阶段分群饲养&#xff0c;适应不同种类、不同阶段的牲畜对空间的不同要求&#xff0c;以保障牲畜安全和健康&a…

离线数仓同步数据3

业务数据_增量表数据同步 1&#xff09;Flume配置概述2&#xff09;Flume配置实操3&#xff09;通道测试4&#xff09;编写Flume启停脚本 1&#xff09;Flume配置概述 Flume需要将Kafka中topic_db主题的数据传输到HDFS&#xff0c;故其需选用KafkaSource以及HDFSSink&#xff…

内网穿透实现Windows远程桌面访问Ubuntu,简单高效的远程桌面解决方案

文章目录 前言1. ubuntu安装XRDP2.局域网测试连接3.安装cpolar内网穿透4.cpolar公网地址测试访问5.固定域名公网地址 前言 XRDP是一种开源工具&#xff0c;它允许用户通过Windows RDP访问Linux远程桌面。 除了Windows RDP外&#xff0c;xrdp工具还接受来自其他RDP客户端(如Fre…

牛客SQL练习三

21查找所有员工自入职以来的薪水涨幅情况 题目描述 查找所有员工自入职以来的薪水涨幅情况&#xff0c;给出员工编号emp_no以及其对应的薪水涨幅growth&#xff0c;并按照growth进行升序 CREATE TABLE employees ( emp_no int(11) NOT NULL, birth_date date NOT NULL, first…

腾讯实习测试开发岗位面试总结

腾讯测开的部门是用 go 语言,面试者是 java 技术栈。这场面试,最后还问了一个智力题,果然是腾讯的风格,目前只发现腾讯爱问这类题目,主要是考察面试者聪明不聪明。 问题记录 对go了解吗?部门主要是偏go语言的 答:说了之前学过一段时间,但是太长时间没有接触了,如果给…

uni-app 之 uni.request 网络请求API接口

uni-app 之 uni.request 网络请求API接口 image.png <template><!-- vue2的<template>里必须要有一个盒子&#xff0c;不能有两个&#xff0c;这里的盒子就是 view--><view>--- uni.request 网络请求API接口 ---<view><!-- 免费的测试接口 --…

android——服务JobService

JobService是Android L时候官方新增的组件&#xff0c;适用于需要特定条件才执行后台任务的场景。由系统统一管理和调度&#xff0c;在特定场景下使用JobService更加灵活和省心&#xff0c;相当于是Service的加强或者优化。 JobService是JobScheduler的回调&#xff0c;是安排的…

制造企业如何优化物料控制?

导 读 ( 文/ 2127 ) 物料控制是指对制造过程中所涉及的物料流动和库存进行有效管理和控制的过程。它包括物料需求计划、供应商管理、物料采购、物料接收和入库、物料库存管理以及物料发放和使用等关键环节。通过精确的物料需求计划和库存管理&#xff0c;物料控制可以确保物料供…

转载: 又拍云【PrismCDN 】低延时的P2P HLS直播技术实践

低延时的P2P HLS直播技术实践本文是第二部分《PrismCDN 网络的架构解析,以及低延迟、低成本的奥秘》低延时的P2P HLS直播技术实践 [首页 > Open Talk NO.41 | 2018 音视频技术沙龙深圳站 > 低延时 WebP2P 直播技术实践https://opentalk-blog.b0.upaiyun.com/prod/2018-0…