C语言中函数宏的三种封装方式详解

news2024/9/21 19:27:40

 

目录

​编辑

1. 函数宏介绍

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 = 1
 
 if (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);
else
 printf("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);
 else
  printf("hello world!\n"); 
 printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1

 return 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);
 else
  printf("hello world!\n");
 printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
 
 return 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 !!
 else
  printf("swap fail !!\n"); 
 printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
 
 return 0;
}

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

优缺点总结:

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

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

5. 总结

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

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

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

相关文章

Winform从入门到精通(37)—FolderBrowserDialog(史上最全)

文章目录 前言1、Name2、Description3、RootFolder4、SelectedPath5、ShowNewFolderButton前言 当需要获取一个可以通过用户自由选择路径的时候,这时候就需要FolderBrowserDialog控件 1、Name 获取FolderBrowserDialog对象 2、Description 用于指示对话框的描述,如下: …

leetcode刷题(10)二叉树(4)

各位朋友们&#xff0c;大家五一劳动节快乐啊&#xff0c;在这里祝大家假期玩得愉快&#xff01;但是在玩耍的期间不要忘记了敲代码哦。今天我为大家分享的是二叉树的第四篇&#xff0c;废话不多说&#xff0c;我们一起来看看吧。 文章目录 二叉树的最近公共祖先题目要求做题思…

Stable Diffusion Controlnet V1.1 的14种基础标志用法

用于ControlNet和其他基于注入的SD控件的WebUI扩展。 针对 AUTOMATIC1111 的 Stable Diffusion web UI 网络用户界面的扩展&#xff0c;它可以让网络界面在原始的 Stable Diffusion 模型中添加 ControlNet 条件来生成图像。这种添加是实时的&#xff0c;不需要进行合并。 Con…

【openAI】Whisper如何高效语音转文字(详细教程)

文章目录 前言一、准备二、使用Whisper进行语音转文字三.Whisper转换结果分析总结 前言 语音转文字在许多不同领域都有着广泛的应用。以下是一些例子&#xff1a; 1.字幕制作&#xff1a;语音转文字可以帮助视频制作者快速制作字幕&#xff0c;这在影视行业和网络视频领域非常…

【软件下载】换新电脑记录下下载的软件时所需地址

1.idea https://www.jetbrains.com/zh-cn/idea/download/other.html 2.oracle官方&#xff08;下载jdk时找的&#xff09; https://www.oracle.com/ 3.jdk8 https://www.oracle.com/java/technologies/downloads/ 下拉找到jdk8 切换windows &#xff08;需要注册个oracle账…

TabError: inconsistent use of tabs and spaces in indentation

错误原因是tab制表符和空格混用了。从其他地方复制源码容易出现此错误 解决办法&#xff1a;把处于同级缩进的所有缩进修改统一 比较流行的几个编辑器都能标识tab和空格&#xff0c;比如我用的vscode 用鼠标框选不知道是tab还是空格的部分。 若是空格则显示为上图73行所示的点…

自动化运维工具一Ansible Roles实战

目录 一、Ansible Roles概述 1.1.roles官方的目录结构 1.2.Ansible Roles依赖关系 二、Ansible Roles案例实战 2.1.Ansible Roles NFS服务 2.2 Roles Memcached 2.3 Roles-rsync服务 一、Ansible Roles概述 之前介绍了 Playbook 的使用方法&#xff0c;对于批量任务的部…

C++程序设计——常见C++11新特性

一、列表初始化 1.C98中{}的初始化问题 在C98中&#xff0c;允许使用花括号{}对数组元素进行统一的列表初始化值设定&#xff0c;比如&#xff1a; 但是对于一些自定义类型&#xff0c;就无法使用这样的方式进行初始化了&#xff0c;比如&#xff1a; 就无法通过编译&#xff…

HIT-CSAPP实验二gdb和edb的配置

笔者只是根据自己的电脑进行环境的配置&#xff0c;不一定适配所有的电脑&#xff0c;也不是万能的方法&#xff0c;如果读者使用本人的方法没有配置成功本人深表抱歉。 gdb的使用 通过网上查阅一些资料获得 gdb查看内存和寄存器以及中断设置&#xff08;转&#xff09;_gdb…

关于安装PicGo后启动无界面问题

关于安装PicGo后启动无界面问题 其实我遇到的这个也不算是问题&#xff0c;也挺无语的。 最近为了搭建图床&#xff0c;需要使用PicGo&#xff0c;第一次搭建图床也是第一次使用PicGo。在安装了PicGo后发现启动不了&#xff0c;查看后台发现PicGo在运行着&#xff0c;但是没有界…

数据结构与算法九 树进阶

一 平衡树 之前我们学习过二叉查找树&#xff0c;发现它的查询效率比单纯的链表和数组的查询效率要高很多&#xff0c;大部分情况下&#xff0c;确实是这样的&#xff0c;但不幸的是&#xff0c;在最坏情况下&#xff0c;二叉查找树的性能还是很糟糕。 例如我们依次往二叉查找…

User Experience Design and Information Architecture

&#x1f4a5;(1) What is IA (Information Architecture)? Definition of four sentences I. Information Architecture is "The structure design of shared informaiton environments-共享信息环境的结构设计" II. Information Architecture is "The sy…

ChatGPT提示词工程(三):Summarizing概括总结

目录 一、说明二、安装环境三、概括总结&#xff08;Summarizing&#xff09;1. 简单地概括总结&#xff0c;只有字数限制2. 概括总结需要关注的某些点 四、用“提取”代替“总结”&#xff08;Try "extract" instead of "summarize"&#xff09;五、概括总…

Mysql第二章 多表查询的操作

这里写自定义目录标题 一 外连接与内连接的概念sql99语法实现 默认是内连接sql99语法实现左外连接&#xff0c;把没有部门的员工也查出来sql99语法实现右外连接&#xff0c;把没有人的部门查出来sql99语法实现满外连接&#xff0c;mysql不支持这样写mysql中如果要实现满外连接的…

生成对抗网络原理

GAN的原理 GAN是在2014年由Ian Goodfellow等人提出的&#xff0c;发表在论文“Generative Adversarial Networks”中。 GAN的主要灵感来源于博弈论中零和博弈的思想&#xff0c;应用到深度学习神经网络上来说&#xff0c;就是通过生成网络G&#xff08;Generator&#xff09;和…

系统架构设计

高性能 客户端内部缓存客户端到服务器之间缓存&#xff1a;CDN,网络专线数据库前加缓存Sessioin等信息共享NoSQL数据库分片&#xff0c;读写分离web层无关态集群负载均衡GeoDNS 就近原则&#xff0c;边缘计算存储异步&#xff0c;解耦&#xff0c;削峰&#xff1a;消息队列离线…

MySQL学习笔记第五天

第06章多表查询 多表查询概述&#xff1a; 多表查询&#xff0c;也称为关联查询&#xff0c;指两个或更多个表一起完成查询操作。前提条件&#xff1a;这些一起查询的表之间是有关系的&#xff08;一对一、一对多&#xff09;&#xff0c;它们之间一定是有关联字段&#xff0…

第二十章 渲染管线

渲染管线是计算机图形中最基础最核心的部分&#xff0c;它是将3D场景显示到2D平面的技术过程。在DirectX课程中&#xff0c;我们就介绍了渲染管线&#xff0c;分为固定渲染管线和可编程渲染管线&#xff08;Shader&#xff09;。但是在DirectX 10版本之后统一了渲染架构&#x…

【Java】面试常问知识点(Java基础)

JVM java 栈&#xff1a;线程私有&#xff0c;生命周期和线程&#xff0c;每个方法在执行的同时都会创建一个 栈帧用于存储局部变量表&#xff0c;操作数栈&#xff0c;动态链接&#xff0c;方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程&#xff1b;栈…

Photoshop如何使用蒙版之实例演示?

文章目录 0.引言1.给单调的天空添加蓝天白云2.清除头发边缘的杂色3.制作景深效果4.制作枯荣共存的树5.制作双重曝光肖像 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对PS进行了学习&#xff0c;本文通过《Photoshop2021入门教程》及其配套素材结合网上相关资料进行…