??常量宏定义出现问题的原因并不来自于宏,而是来自常量本身不规范的使用。在 if(-1 > 2)这种简单的判断中,-1与2都是具有数据类型的常量,很多时候我们都会忽略-1与2本身的数据类型,在这个例子中两个常量被系统默认为int数据类型,因此我们得到了正确的判断结果,不过总有例外存在。当数据变成if(-1 > 2147483649)时,2147483649默认为long long型,而-1默认依旧为int型,这时候因为运算数据的类型不匹配,会导致导致编译不能通过,还有些编译器比较傻,虽然能编译通过,但是其内在隐患并没有解决掉。
以上是在常量使用中比较显式的一类问题,另一类问题比较隐式,是在不同数据类型间的赋值中可能产生的。当一个int类型常量给long long进行赋值,可以得到正确的结果,而当以上的赋值顺序交换,就有可能造成数据被截断。由于数据复制过程中得到的的结果有可能是对的,所以这种问题往往被人忽略。
??总之,一般由程序员主动定义的变量在使用过程中都会留意,不过当数据是通过宏定义出现在式子中,就要谨慎了,因为一种数据的表达形式可能有不止一种的含义,比如说1可以是int型,也可以是long long,因此在编译的过程中,系统本身对数据类型的默认选择并不一定符合程序员的本意,也就导致了代码运行过程产生了歧意。其它的一些数据类型的宏替换,比如字符,字符串就没有类似的问题,对它们来说,一种表现形式往往有且只有一种意义。
??对于这种由于宏定义导致的数据产生的歧意,可以通过在宏定义过程中添加后缀来解决。经过对宏添加后缀,我们可以对宏定义的常量数据类型进行限定,而不是由系统对数据类型进行控制,从而降低代码的相关风险。
#define CECOND_PER_YEAR (60*60*24*365UL)
上面这个例子中如果不加后缀而是以(60*60*24*365)
来表示,会产生数据截断,加上了UL后,该数据的存储方式会以无符号整型来存储,在对常量进行宏定义时要有加上后缀的意识,很多时候程序出现BUG都是因为编写者日常没有养成良好的编程习惯带来的,下面是数据类型与后缀的对应表项。
F(f) |
float(浮点) |
U(u) |
unsigned int(无符号整型) |
L (l) |
signed long(符号长整型) |
LL(ll) |
signed long long(符号长长整型) |
UL(ul) |
unsigned int(无符号整型) |
ULL(ull) |
unsigned long long(无符号长长整型) |
替换方案
??小小的常量替换,大大的编程作用。不过在编程替换中只有宏定义一家独大吗?答案是否定的,除了宏定义还有const
关键字修饰的变量与 enum
可以担此大任。与其把被const修饰的变量称做常量,或许只读的变量才更符合它的真实情况,但是最终达成的作用却是类似的,都可以看成常量替换。相对而言,const 本身就具有类型检测功能,因为在定义时,我们必须给const 修饰的常量指定类型,这就避免了使用宏定义常量而存在的潜在问题,不过编者在平时编程中对于常量定义依旧是以宏定义为主,因为宏定义看起来更有美感,可怜的强迫症患者就是我了。
整体
??什么是整体呢?一把伞由伞柄、伞骨和伞面组成。其中伞柄是握住伞的部分;伞骨是支撑伞面的部分;伞面是遮雨的部分,这几个部分在挡雨时缺一不可,如果缺少某个部分则就失去了伞的功能,就不能称之为整体。我理解的编程整体也是这样,它的功能具有单一性与唯一性,该整体不能有缺少,也不能画蛇添足,通过宏定义可以帮助我们封装一个编程整体。
??一个宏定义的整体可以分为简单宏整体,复合宏整体两类。简单宏整体就是利用一些运算符结合起来的宏整体,比如下面这个比较数字大小的宏定义
#define MAX(x, y) ((x)>(y)?(x):(y))
当然这类宏整体并不都是这么短,下面是一个遍历数组的宏定义
#define FOREACH(item, array) \ // 定义一个遍历数组的宏
for(int keep=1, \
count=0, \
size=sizeof (array)/sizeof *(array); \
keep && count != size; \
keep = !keep, count++) \
for(item = (array)+count; keep; keep = !keep)
了解了简单宏定义后再来看一下复合宏整体,不过为什么称之为复合宏定义呢?所谓复合就是宏定义内不仅包含了一些运算符这些,还有了函数的参与
#define ECHO(s) (get(s), put(s))
ECHO(str);
以上这个例子中,用宏将get()
与put()
包裹起来,实现输入输出的一条龙服务,通过将宏定义用于函数的结合,使我们的操作更加灵活,也一定程度提高了代码的可读性。
在上面对两种宏整体的讲解例子中,都不同程度在宏定义中使用了参数,不过宏定义中的参数也可以不是固定的,这类宏定义被称为参数可变宏,它可以根据不同情况传递不同类型和数量的参数。参数可变宏的定义方法是在宏定义后面的参数列表中的最后一个参数为省略号(…)
,表示可以接受任意个数和类型的参数。例如:
#define PRINTF(...) printf(__VA_ARGS__) // 定义一个可以接受任意个数和类型的参数的宏
在使用参数可变宏时,需要用一个特殊的标识符 __VA_ARGS__
来表示所有传递给宏的可变参数。
PRINTF("Hello, world!\n&