宏的定义在程序中是非常有用的,但是使用不当,就会给自身造成很大的困扰。通常这种困扰为:宏使用在计算方面。

本例子主要是在宏的计算方面,很多时候,大家都知道定义一个计算的宏,对于编译和编程是多么的有用。现在定义有以下一个计算 “乘法” 的宏。

  1. #include <stdio.h>
  2. #define MUL(a) ((a)*(a)*(a))
  3. int main(int argc,char *argv[])
  4. {
  5. int i = 10;
  6. int sum = MUL(i);
  7. printf("MUL(%d) = %d\n",i,sum);
  8. return 0;
  9. }

  上面程序的这种做法对于非负数而言那就是没有问题的,比如,程序中的 变量 i=10,这个时候,调用宏得到的数据如下:

  但是如何变量的数值是自加或者自减的操作的话,结果就不一样了。

  假如我们将上面的程序变为下面这样的

  1. #include <stdio.h>
  2. #define MUL(a) ((a)*(a)*(a))
  3. int main(int argc,char *argv[])
  4. {
  5. int i = 10;
  6. int sum = MUL(++i);
  7. printf("MUL(%d) = %d\n",i,sum);
  8. return 0;
  9. }

  得到的结果并不是 11 * 11 *11 = 1331这个数据,而是 1872,这时候有人会问为什么?

      

  得到宏的朋友或者了解过宏在计算方面的朋友就会知道,这除了是宏的问题,还是本身程序员编写这段代码的问题。当使用了 ++i 和 i++ 的时候,

要特别注意在宏中是全部使用 ++i或者i++的,变成的格式如下

  1. MULi++) ((i++)*(i++)*(i++))
  2. MUL(++i) ((++i)*(++i)*(++i))

  上述的做法显然不是我们想要的计算结果,可能在我们程序中看到的是MUL(++i) 或者 MUL(i++),认为实际上是如下情况:

  1. //当i的初始化数值为10的时候,进行i++的 MUL(i++)宏计算,即是:
    int i = 10;
  2. //MUL(i ++)的数值计算结果相比是 10 * 11 * 12的,这是没有问题的,但是 i的值呢??是11吗??显然不是。
  3. MUL(i++) = 10 * 11 *12;
  4. i = ??;

  i的数值如下图所示

    

  诚然,i的数值变成了 13,这是为什么呢??

  那就是因为这个MUL(a)这个宏和程序员的 “自加自减” 操作所造成的。这里先普及一下 C/C++语言的 “自加自减” 操作:

  1. //自加自减的操作
  2. i++ ++i ----> 这里的操作属于++后操作,可以替换成 i = i+1 的结果。
  3. 但是,当它赋值给一个变量的时候,表示的内容和含义就有不同: (假设i = 10
  4. 1. sum1 = i++;
  5. 2. sum2 = ++i;
  6. 1中的sum1的数值就是 10 i 11
  7. 2中的sum2的数值就是 11 i 11
  8. 这是因为:
  9. i++ 操作是 先赋值给 sum1后,自己在执行 i = i+1的操作
  10. ++i 操作是 先进行 i = i+ 1的操作,然后再赋值给sum2
  11. 这样得到的结果当然不同了,但是i最终的结果是要加1的,只不过是赋值给变量的时候会有不同

  

  通过对自加自减的操作进行说明,不知道大家是否明白为什么了吗??

  1. i = 10的时候,MUL(i++)就是为 (i++)*(i++)*(i++)的计算结果,考虑到C/C++的运算符结合性,
  2.  
  3. 先计算第一个 i++,这是一个先计算后赋值的自加方式,那么这是后第一个 (i++)的数值待定为 10 ,那么第
  4.  
  5. 二个的i是因为第一个数据的 (i++)起了作用而变化的,这时候第二个(i++)的数值为11,然后加1,这时候
  6.  
  7. 据结合性,先计算前面两个数据,就是(i++) * (i++)的数值了,即为:10 * 11了,这时候的i数值是 12
  8.  
  9. 然后计算第三个 i++的数值,这时候第三个i++中的i数值为 12,计算后再加1,也就是说,10 * 11 * 12之后,
  10.  
  11. i= 12 的数值在进行i++变为 13了。所以 MUL(i++) = 10 * 11 * 12 = 1320

   

  另外,在进行++i的操作和上述的情况差不多,只不过是先做自加的运算,在进行赋值。

  

  1. i = 10的时候,MUL(++i)实际上也为 (++i)*(++i)*(++i)的方式,这时候先计算第一个 (++i),这是一
  2.  
  3. 个先计算后赋值的结合方式,那么 i = i+1 = 11;这时候准备计算第二个(++i)的时候,因为需要先计算后赋值,
  4.  
  5. 所以 第二个 ++i 之后的数值为12,但是因为i属于同一个变量和属性,那么第一个i也会变成 12了,这时候结合性
  6.  
  7. 考虑应该是计算前两个(++i)的结果,再与第三个(++i)计算,即(++i)*(++i) = 12 * 12;然后,我们计算第三个
  8.  
  9. (++i)的数值,由于前面第二个++ii值,所以第三个++i即为 13,此时,12 * 12 * 13
  10.  
  11.   有人可能顾虑,为什么最后不是13 * 13 * 13的呢?那不是最后都是13吗?? ------》其实这种想法是错误的,
  12.  
  13. 这必须先理解运算符的结合性。我们知道,当计算中遇到了括号的时候,我们先计算括号的内容,这是我们在数学中
  14.  
  15. 的惯性思维。但是对于计算机而言,计算机必须 有计算的优先级,也就是运算符的优先级问题。首先我们计算前面两
  16.  
  17. 个括号的内容,以为两个括号之间有乘号(*),所以计算前面两个(++i)之后,必须进行乘法计算,这就是优先级中的
  18.  
  19. 乘法计算,自左向右计算。所以结果变为了 12 * 12的最终结果在和第三个括号的(++i)计算,
  20.  
  21. 就是144 * (++ i) = 144 * 13;

  所以MUL(++i)的结果如下:

    

总结:

  慎用宏在计算方面的,但是宏的有点还是很多的,对于C语言来说,宏可以减少运行的时间。在C++中,宏由于不会对类型进行检查,安全性不够,所以建议使用const来

进行使用,这样可以保证类型一致。这是C/C++对宏的严谨性进行优化的结果。更多的宏的知识或者如何定义宏,大家可以上网查查资料。

C/C++中慎用宏(#define)的更多相关文章

  1. c++中的宏 #define _CLASSDEF(name) class name

    #include <iostream> using namespace std; #define _CLASSDEF(name) class name; \ typedef name * ...

  2. 20个C语言中常用宏定义总结

    01: 防止一个头文件被重复包含 #ifndef COMDEF_H#define COMDEF_H//头文件内容#endif 02: 重新定义一些类型防止由于各种平台和编译器的不同,而产生的类型字节数 ...

  3. Android.mk中添加宏定义

    在Boardconfig.mk 中添加一个 IS_FLAG := true 由于Boardconfig.mk和各目录的Android.mk是相互关联的 所以我们可以在Android.mk 中添加 一个 ...

  4. 头文件中的#ifndef/#define/#endif 的作用

    在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时,就会出现大量重定义的错误.在头文件中实用#ifndef #define #endif能避免头文件的重定 ...

  5. 26.怎样在Swift中定义宏?

    Swift 中没有宏定义,苹果建议使用let 或者 get 属性来替代宏定义值.虽然没有#define,但我们仍然可以使用 #if 并配合编译的配置来完成条件编译.下面会列出Swift项目开发中的一些 ...

  6. IOS中利用宏将RGB值转换为UIColor(转)

    可以在pch文件中定义宏,这样整个项目就都可以用了! #define UIColorFromRGBValue(rgbValue) [UIColor colorWithRed:((float)((rgb ...

  7. Linux中_ALIGN宏背后的原理——内存对齐

    转载自: http://englishman2008.blog.163.com/blog/static/2801290720114210254690/ 1. 原理    int a;     int ...

  8. iOS 静态库中使用宏定义区分iPhone模拟器与真机---备用

    问题描述 一般项目中,可以使用宏定义来判断模拟器还是真机,这无疑是有效的. #if TARGET_IPHONE_SIMULATOR #define SIMULATOR 1 #elif TARGET_O ...

  9. C语言中的宏

    写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性 等等.下面列举一些成熟软件中常用得宏定义...... 1,防止一个头文件被重复包含 #ifndef COMDEF_ ...

随机推荐

  1. DDR工作原理(转)

    源:DDR工作原理 DDR SDRAM全称为Double Data Rate SDRAM,中文名为“双倍数据流SDRAM”.DDR SDRAM在原有的SDRAM的基础上改进而来.也正因为如此,DDR能 ...

  2. 火狐上的一个post提交工具(主要用于测试接口时候)

    添加的过程 安装完后,就可以在下图上,看到一个poster 点击poster就可以看到下图 图中红线圈好的,是必须要填写的 Url是访问路径 Name是参数名称 Value是参数值 需要注意一点的是: ...

  3. DP题目列表/弟屁专题

    声明: 1.这份列表不是我原创的,放到这里便于自己浏览和查找题目. ※最近更新:Poj斜率优化题目 1180,2018,3709 列表一:经典题目题号:容易: 1018, 1050, 1083, 10 ...

  4. tools_list

    http://files.cnblogs.com/files/yansc/ExportQingtaoImage.rar

  5. iOS开发之监听键盘高度的变化 分类: ios技术 2015-04-21 12:04 233人阅读 评论(0) 收藏

    最近做的项目中,有一个类似微博中的评论转发功能,屏幕底端有一个输入框用textView来做,当textView成为第一响应者的时候它的Y值随着键盘高度的改变而改变,保证textView紧贴着键盘,但又 ...

  6. sqlserver 设置外键

    CREATE TABLE Orders ( O_Id int NOT NULL, OrderNo int NOT NULL, Id_P int, PRIMARY KEY (O_Id), FOREIGN ...

  7. object-c中的BOOL类型

    object-c中的布尔类型比C语言中的bool类型早了10年,它具有YES和NO两种值.在object-c中的布尔类型BOOL实际上是一种带符号的字符类型(signed char),它使用的空间是1 ...

  8. 创建iwatch 程序选项

    include complication :包含自定义表盘事件 include glance scene:包含缩略图事件

  9. NGINX location 配置

    location表达式类型 ~ 表示执行一个正则匹配,区分大小写 ~* 表示执行一个正则匹配,不区分大小写 ^~ 表示普通字符匹配.使用前缀匹配.如果匹配成功,则不再匹配其他location. = 进 ...

  10. iOS 之 工厂模式

    参考:http://www.jikexueyuan.com/course/2054_2.html?ss=2 1. 简单工厂 简单工厂类是一个实体类.用于几种相似类的统一创建,简化流程,隔离细节. 下面 ...