C语言基础–宏函数

1. 函数和数据类型

  1. 函数式宏(宏函数)和函数类比会更加灵活,我们通过两个例子来看一下。

    函数

    #include <stdio.h>
    int sqr_int(int x)
    {return x*x;
    }double sqr_double(double x)
    {return x*x;
    }int main(int argc , char *argv[])
    {int n;double x;printf("请输入一个整数:\n");scanf("%d",&n);printf("%d的平方是:%d\n",n,sqr_int(n));printf("请输入一个实数:\n");scanf("%lf",&n);printf("%lf的平方是:%lf\n",n,sqr_double(n));return 0;
    }
    
    • 当然了,如果计算long int,或者long long int型的数据类型的话我们还得编写该类型的函数。可以 遇见,如果这样下去代码中将会充斥着大量功能相近,名称相似的函数,看起来非常糟糕。
    • 下面来看一下函数式宏的实现:

    函数式宏

    #include <stdio.h>#define sqr(x) ((x)*(x))int main(int argc,char *argv[])
    {int n;double x;printf("请输入一个整数:\n");scanf("%d",&n);printf("%d的平方是:%d\n",n,sqr(n));printf("请输入一个实数:\n");scanf("%lf",&n);printf("%lf的平方是:%lf\n",n,sqr(n));return 0;
    }
    
    • 可以看到一个简单的宏函数就实现了几个函数才能实现的事情,当然了宏函数可不止这点儿能耐。
    • 本例中类似sqr(@) —>展开为((@)*(@))的形式就称为宏展开
    • 当调用该宏函数的时候,就会在调用位置将该宏函数展开为上述形式。
    • 值得注意的是宏定义只做替换不做计算,这一点很重要,后面会再提到。

2. 函数和函数式宏

  • 从上面可以看到函数式宏在某些时候可以替代函数的作用,它们的区别如下:
  1. 函数式宏是在编译时展开并填入程序的

  2. 而函数定义则需要为每个形参都定义各自的数据类型,返回值类型也只能为一种。函数更为严格。

  3. 函数默默的为我们进行一些复杂的操作,比如:

    1. 参数传递(将实参值赋值给形参)
    2. 函数调用和函数返回操作
    3. 返回值的传递

    而函数式宏只是做宏展开,并不做上述处理。

  4. 函数式宏能是程序的运行速度稍微提高一点儿,但是当函数中有大量的宏替换的时候,又会使得程序变得臃肿。(原理是宏函数只做替换,并没有类似函数调用的跳转、参数出入栈等操作,自然会提高函数的运行速度。这种运行速度加快体验在大量使用的时候尤为明显)

  5. 宏在使用的时候必须小心谨慎,避免出现问题。这一点是有宏的本身特性决定,即只做替换不做计算。举例来说明:

    宏的副作用

    情况一:

    #define sqr(a) ((a)*(a))
    

    若调用该函数式宏计算sqr(a++),展开后就变为:((a++) * (a++)),可以发现a执行了两次自增操作。这就会造成隐形的错误,比如我只是想将a自增1后在求其平方,但是结果却并非我们所想。

    情况二:

    宏定义与宏函数

    假如我们在sqr 和’('之间多敲了一个空格,如下

    #define sqr (a) ((a)*(a))
    

    那么此时函数式宏就变成了,宏定义了,也成对象式宏。即sqr会被编译器替换成(x) (x)*(x)

    在定义宏函数的时候注意宏函数名和’('之间不能有空格。

    情况三:

    #define sum(x,y) x + y  //注意:不规范的函数式宏的定义//调用
    z = sum(a,b) * sum(c,d);
    //编译器将其展开后就变为:
    z = a + b * c + d;   //这样是不是偏离了我们的本意
    

    因此,我们在定义函数式宏的时候与一定要每个参数以及整个表达式都用()括起来,就不会出错了。上面的就可以改为

    #define sum(x,y) ((x) + (y))  //正确的定义方法
    //调用
    z = sum(a,b) * sum(c,d);
    /编译器将其展开后就变为:
    z = ((a) + (b)) * ((c) + (d));
    

    总结,在定义和使用函数式宏的时候要注意避免其产生副作用

3. 不带参数的函数式宏

  • 函数式宏也可以像函数那样不带参数

    #define my_print() (printf("你好啊!\n"))
    

4. 函数式宏和逗号表达式

  • 下面将说明函数式宏使用时的一个重要技巧
#include <stdio.h>#define puts_alert(str)  {putchar('\a');puts(str);}int main(int argc,char *argv[])
{int n;printf("请输入一个整数:");scanf("%d",&n);if(n)puts_alert("这个数不是0.");elseputs_alert("这个数是0.");return 0;
}
  • 当我们运行这个程序的时候会报错,无法运行。提示else 缺少if

    因为将该函数式宏展开后就变为:

#include <stdio.h>#define puts_alert(str)  {putchar('\a');puts(str);}//当然了,如果一行定义不下,有时候为了美观我们也这样写,'\'表示下一行还有内容
#define puts_alert(str)  {putchar('\a');  \puts(str);}int main(int argc,char *argv[])
{int n;printf("请输入一个整数:");scanf("%d",&n);if(n){putchar('\a');puts(str);};else{putchar('\a');puts(str);};return 0;
}
  • 很明显,if下的复合语句’}‘后的’;‘会被认为是空语句,那么此时else再去找它上面的那个if的时候就找不到了,因此就会报错。当然了函数式宏中的’{}'也不能少,若少了又会报其他的错误。怎么解决呢?可以思考一下。当然了,也可以参见下面一节

5. 函数式宏和逗号表达式

  • 上面一节我们看到了当函数式宏中有多条需要执行的语句的时候我么遇到了麻烦,下面各处解决方法:
#include <stdio.h>#define puts_alert(str)  (putchar('\a'),puts(str))int main(int argc,char *argv[])
{int n;printf("请输入一个整数:");scanf("%d",&n);if(n)puts_alert("这个数不是0.");elseputs_alert("这个数是0.");return 0;
}//在if处展开if(n)(putchar('\a'),puts(str));else(putchar('\a'),puts(str));
  • 这里有个知识点,逗号表达式。逗号运算符:

    表达式a,表达式b —> 按顺序判断a和b,整个表达式最终生成表达式b的判断结果。当然了,若有多个表达式(整个逗号表达式的值为最后一个表达式的值),可以以此类推。

    举个例子:

  int a = 3,b = 4,z = 0;z = ++a,++b;
//执行时a的值会自增1,b的值也会自增1,最终将b自增后的值赋给变量z
//此时z的值为5,也就是最终整个逗号表达式的结果。

可以看到:整个逗号表达式中每个语句都会被计算到

如果想要了解更多的逗号表达式的知识可以参考下我的另一博文:
逗号运算符与逗号表达式

6. 总结

  • 函数式宏在使用时很方便,正确使用的话不仅可以使得我们的程序变得简洁,而且可以提高我们程序的运行速度。
  • 但是这种方式有时也很容易出错,所以我们在使用的时候一定要非常小心,避免出错。

C语言基础--宏函数相关推荐

  1. fig r函数_R语言基础绘图函数散点图~跟着Nature Communications学画图~Figure1

    今天继续 跟着Nature Communications学画图系列第二篇.学习R语言基础绘图函数画散点图. 对应的 Nature Communications 的论文是 Fecal pollution ...

  2. c语言函数调用数组_第七讲:C语言基础之函数,第二节,实现汉诺塔

    本文约2200字,主要讲了C语言基础之函数,递归,数组作为函数参数以及案例.练习题等. 带你进入C语言的世界,入门C语言,后边将持续更新.可以收藏学习.想了解C语言基础之函数,函数的基本概述,函数的调 ...

  3. plot函数_R语言基础绘图函数散点图~跟着Nature Communications学画图~Figure1

    今天继续 跟着Nature Communications学画图系列第二篇.学习R语言基础绘图函数画散点图. 对应的 Nature Communications 的论文是 Fecal pollution ...

  4. BSP学习Day11 C语言基础 宏定义和宏函数 函数调用 类型转换 数组

    目录 1.宏函数和宏定义 2.条件编译 3.类型转换 4.数组 1.宏函数和宏定义 (1)宏定义 宏函数的本质是对源文件进行替换 先声明一个宏,然后再main函数中使用该宏 #define MAX 1 ...

  5. C语言重载宏函数的小技巧

    在写C/C++语言时我们经常会用到宏定义,宏函数就是带参数的宏定义(blablabla--省去背景介绍). 有时候我们会需要一个可以有多种参数版本的宏定义,例如: 1 2 #define MACRO_ ...

  6. C语言基础09 函数

    函数 函数是什么? C语言中的函数,维基百科中给出的定义是:子程序 在计算机科学中,子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成.它负责完成某项特定任务,而且相较于其他代码,具备相对的 ...

  7. C语言_宏函数_换行符

    在写代码时,发现很多代码重复,为了缩减代码及利于代码维护,将重复的代码通过宏函数的方式进行定义. 简单地将重复代码拷贝出来,通过宏定义封装了一下:因为多行代码,通过换行符进行了规范.使用了宏函数之后发 ...

  8. c语言函数定义时涉及的基本要素是什么,C语言基础:函数的定义与调用

    在前面内容中我们调用了一个标准C的库函数,叫printf,那么如果我们想自己定义函数应该如何来编写程序呢?定义函数又有什么好处呢?因为我们在教材中提及到的例子主要目的是为了让读者对程序的原理有一定的了 ...

  9. C语言 用宏函数实现 swap() 交换函数

    函数功能: 交换任意两个同类型的数 函数一: #include <stdio.h> /** 运行环境:MinGW 和 MSVC 都可用* *//** 这里添加 do-while 循环是为了 ...

最新文章

  1. XenApp Farm:修改密码、退域、更改Farm
  2. 深度好文:2018 年 NLP 应用和商业化调查报告
  3. 进程、地址空间、文件、I/O、保护、虚拟内存
  4. Flume之介绍 核心组件 可靠性 恢复性
  5. 8、第六 -面向对象高级语法-异常处理
  6. MySQL单元三试题与答案_2016年3月三级MySQL数据库试题及答案
  7. 程序员面试逻辑题解析
  8. 使用短信接口进行通知
  9. MySQL数据库插入中文时出现Incorrect string value: '\xE6\x97\xB7\xE5\x85\xA8' for column 'sz_name' at row 1...
  10. ant-design vue上传 多文件 、单文件上传
  11. unity Color和Hex转化
  12. HSI彩色图像空间滤波
  13. 北邮计算机基础题目,北邮网络教育学院计算机基础题目.doc
  14. FPGA接口实现----增量式编码器
  15. 中兴zxr10路由器重启命令_ZXR10路由器基本操作和配置.doc
  16. 理性、抽丝剥茧地解决 Easypoi、poi 版本冲突问题,而不是无头苍蝇一样闷头百度、google 一顿搜
  17. 功能中进行频繁查询、提高查询效率的方法
  18. 解决ERROR Failed to compile with 3 errors These dependencies were not found: * pdfjs-dist/es5/buil问题
  19. Hexo-Theme-Buer主题修改介绍
  20. LOG的含义 : Mysql 之 binlog介绍

热门文章

  1. OSChina 周一乱弹 ——周日晚上,高跟鞋坏了。
  2. 什么是onehot编码
  3. uniapp | 安卓手机无线真机调试教程
  4. Java实现将十六进制转换成十进制
  5. 域名品相分析 - 分析域名是否 数字 字母 拼音 几拼 杂米
  6. android密码开闭眼睛,见与不见,你说了算:Android之设置密码是否可见
  7. xvfb运行java脚本_java – 如何在Xvfb上启动mvn测试阶段?
  8. VS2013写window服务,实现定时短信功能
  9. 冶金物理化学复习【5】--- 正规溶液
  10. Log4j2简单配置