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

这篇具有很好参考价值的文章主要介绍了C语言中函数宏的三种封装方式详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

 

目录

​编辑

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),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。文章来源地址https://www.toymoban.com/news/detail-431715.html

到了这里,关于C语言中函数宏的三种封装方式详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言——字符串、打印字符串的三种方式

    字符串( character string )是一个或多个字符的序列,空字符( null character )\\0 标记字符串的结束 字符串以数组( array )存储,也就是以空字符(\\0)结尾的 char 类型数组 用 %s 转换说明来处理字符串的输入和输出 输入和输出必须给出字符串的首地址,可以 直接是字符串常量

    2024年02月11日
    浏览(43)
  • 计算字符串长度的三种方法(库函数 指针 )【详解】

    求字符串长度简单来说就是计算一个字符串(字符数组)中元素的个数即从数组头部计数,直到遇到字符串’\\0’结束符为止, 计数结果不包括’\\0’. C语言中的库函数strlen,它包含于string.h中,因此我们需要在使用前添加头文件 ,具体用法如下: strlen从数组头部计数,直到遇到字

    2024年02月06日
    浏览(60)
  • uni-app小程序引入iconfont的三种方式详解(无需下载文件到项目)

    官网iconfont的引入方式有三种分别为: Unicode 、 Font class 、 Symbol , 其中已明确说明 Unicode、Font class 这两种引入方式 不支持多色 。 单色如何理解呐?具体请看效果对比图(左图为UI上传的图标样式,右边为我们使用这两种方式引入后不加任何样式的效果) 是不是很丑? 另外

    2024年02月09日
    浏览(63)
  • 宏的使用(C语言详解)

    在写一个代码生成可执行文件的过程需要经过编译和链接,编译又要经过三部:预处理,编译,汇编。 #define定义的变量和宏就是在预处理阶段会处理的。 一个简单的宏定义: Max:宏名 a,b:宏参数 ab?a:b:宏体 宏定义有些 类似函数 ,Max(a,b)会被替换为 ab?a:b 比如这里printf

    2024年04月13日
    浏览(22)
  • 生成DLL的三种方式

    生成DLL的三种方式 1. 用命令行完成dll的新建与调用 2. 在VS中生成dll文件 首先需要cl编译器以及link连接器来完成后续操作。 新建一个dll_demo.cpp文件 ,此文件中的函数是我们想要封装成dll的函数。用写字板创建一个新的文件,文件名可自己选择(这里创建一个dll_demo),文件后

    2024年02月04日
    浏览(65)
  • 解决NPE的三种方式

    NullPointerException(空指针异常,NPE)是Java编程中常见的错误。解决NPE的方法可以从以下三个方面考虑: 明确处理空引用情况: 在某些情况下,无法避免使用可能为空的引用对象。此时,需要明确处理空引用情况,以避免抛出NPE。可以使用条件判断,例如使用if-else语句或者三

    2024年02月22日
    浏览(45)
  • selenium的三种等待方式

    设置固定休眠时间,单位为秒。 由python的time包提供, 导入 time 包后就可以使用。 缺点:不智能,使用太多的sleep会影响脚本运行速度。 使用方法:time.sleep(delay) 使用举例:打开百度,强制等待5秒 (无条件等待,在一个时间段内等待) 一次设置,全局生效。 不要当作固定等待

    2023年04月13日
    浏览(37)
  • Servlet的三种映射方式

    Servlet支持三种映射方式,以达到灵活配置的目的。 首先先创建Servlet(创建方式略),然后再web.xml中就行配置。 配置方式:         (1)、 指名道姓的方式         注:此种方式,只有和映射配置一模一样时,Servlet才会接收和响应来自客户端的请求。 示例:         (

    2024年02月06日
    浏览(37)
  • 集合的三种遍历方式

    目录 文章目录 一.迭代器遍历 二.增强for遍历 三. forEach方法 四. Lambda表达式  函数式接口: 函数式接口是指只有一个抽象方法的接口 为什么Lambda只能用于函数式接口 总结 前言 作者简介:最爱吃兽奶 座右铭:抱怨身处黑暗,不如提灯前行 内容介绍:今天给大家讲一下集合的遍历方

    2024年02月06日
    浏览(35)
  • 验证合约的三种方式

    使用truffle插件: https://github.com/rkalis/truffle-plugin-verify truffle run verify 合约名称@合约地址 --network 网络名称 --debug 注意:需要开启VPN,然后给CMD也设置代理,否则CMD无法访问外网会验证失败 (每次新开CMD都要执行) 在etherscan上手动上传 1、合约文件如果有导入的外部合约,需

    2023年04月22日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包