C++编程规范 编程风格 学习 (2) -- 避免使用宏
2012-12-31 01:17
323 查看
避免使用宏
实不相瞒:宏是C和C++语言的抽象设施中最生硬的工具,它是披着函数外衣的饥饿的狼,很难驯服,它会我行我素地游走于各处。要避免使用宏。由于几方面的原因,宏已经成为讨厌、恶心、杂乱的混合体,其中最主要的原因在于它们被吹捧为一种文本替换设施,其效果在预处理阶段就产生了,而此时C++的语法和语义规则还都没起到作用。
在C++中几乎从不需要宏。可以用const或者enum定义易于理解的常量,用inline避免函数调用的开销,用template制定函数系列和类型系列,用namespace避免名字冲突。
关于宏的第一规则就是:不要使用它,除非不得不用,几乎每个宏都说明程序设计语言、程序或者程序存在缺陷。
宏和模板不同,template是C++类型的一部分,宏和语言本身是割裂开来的,template可以借助编译器,而宏不行,template的错误可在编译的时候就发现,编译器也能更好地处理它,而宏里的错误只有在宏展开的时候才能被发现,而不是定义宏的时候。
但是,
宏还是必不可少的,比如#include保护,条件编译中的#ifdef和#if defined,以及assert的实现。
在条件编译中,要避免在代码中杂乱的插入#ifdef,相反,应该对该代码进行组织,利用宏来驱动一个公共接口的多个实现,然后始终使用该接口。
如果不想到处复制和粘贴代码段,可以使用宏,但是要非常小心。
关于预处理:
预处理可能是C++翻译过程中最危险的阶段,预处理器注重构成C++源码的”word“,但是它会忽略C++的细节,包括语法和语义,实际上,预处理器并不知道它的能力有多大,其实它的破坏力是巨大的。
使用预处理器最好是,需要强大的力量,但是不需要过多的了解C++,并避免需要技巧的工作。
#define 字面值
C++中使用#define定义字面值会导致错误或潜在的问题,所以C++程序员并不使用#define来定义字面值,考虑C中的#define的标准用法。
#define MAX 1<<16
使用预处理器符号的基本问题在于,预处理器在C++编译器检查这些符号之前,就展开了这些符号,预处理器对C++作用域或者类型规则一无所知。
void f( int ); void f( long ); //... f(MAX); //这里改用哪个f呢?
这里编译器进行重载分析时,MAX这个符号的值就是整数1<<16,而1<<16可能是int也可能是long,这就要取决于编译目标的平台,不同平台下编译上述代码,会调用不同的函数。
#define 指令不会关心作用域。现在多处C++工具都封装在namespace中,这有不少优点,而不仅仅是降低了不同工具互相干扰的可能性,而#define的作用域不止限于某个namespace内。
namespace A { #define MAX 1<<16 //.... } namespace B { const int max = 512; //.... } int arr[MAX];如果在此忘记了导入名称max,而误写为MAX。预处理器会把1<<16替换为MAX, 所以可以编译代码。这个预处理器就跨越了C++的作用域。
如果这么用问题就会更大
int arr2[MAX*2];
这个数组大小就会超出自己当初的预料,它实际上是arr2[1<<16*2];
这就是不正确地构造#define而引起的。如果使用初始化好的常量就不会出这种语义问题。
const int max = 1<<16;
int arr3[max*3];
这才是我想要的。
#define 伪函数
在C中,#define 通常用于定义伪函数,这种情况就要更多地考虑避免函数调用的代价,而不是安全性。
#define MULTIPLE( a, b ) (a * b)
这么写就有问题,如果是MULTIPLE(2+3,4)
其实你想要(2+3)*4,但是实际是2+3*4,这差了很远,不如用inline来实现岂不更好,
int inline multiple(int a, int b){return a*b;};
这绝不会产生歧义。
当然你给带上括号,给好约束就好了#define MULTIPLE( a, b ) ((a) * (b)),是不是觉得OK了?其实不然,看下面
#define SQUARE( a ) ((a) * (a)) 这个看似没啥问题。
但是还会有可能有问题
#define SQUARE( a ) ((a) * (a)) int nValue = 10; int nSquare = SQUARE(nValue++); // nSquare=110, nValue=12想想为啥是这个结果?
这就是宏在展开时,对其参数的多次取值替换所带来的副作用。为了避免这种副作用,禁止在使用宏的时候,参数有变化。
这就是伪函数的恐怖之处。
另外,有很多宏是很有用的,比如说断言ASSERT,但是它也是伪函数,是伪函数就有它致命的地方,所以需要正确的使用它才能达到目的。
相关文章推荐
- C++编程规范 编程风格 学习 (3) -- 避免使用“魔数”
- C++编程规范 编程风格 学习 (1) -- 积极使用const
- C++编程规范 编程风格 学习 (4) -- 总是初始化变量
- C++编程规范 编程风格 学习 (5) --总是编写内部#include保护符,绝不要编写外部#include保护符
- koltin学习教程 androidstudio 使用koltin 语言编程
- 并发编程学习总结(四) :java 显式锁ReentrantLock使用详解之lock()\unlock() 加锁与释放锁
- Google C++ 编程风格学习
- STL学习笔记之使用reserve来避免不必要的重新分配
- 【VS2010学习笔记】【编程实例】 (在Visual Studio中使用C++创建和使用DLL)
- Web编程学习一: 使用JSF来创建Web应用
- Java编程风格学习(一)
- 从零开始,学习windows编程(7)--不使用CRT库的程序
- JavaScript 学习笔记十二 函数式编程风格
- Unix学习笔记-----编程练习实例------使用vfork&&execv()在程序中加载可执行程序
- 并发编程学习总结(七) :java中synchronized关键字使用详解(1)
- C++学习——第8章 使用函数编程
- 从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片
- Effective C# 学习笔记(九) 在你的API中避免使用类型转换运算
- 编程时避免使用 if 语句的五种模式
- shell脚本学习-为什么使用 shell 编程