编写高质量代码:改善C++程序的150个建议(连载13)

发表于:2012-4-19 09:42

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:李健    来源:51Testing软件测试网采编

  建议25:尽量用const、enum、inline替换#define

  在建议4中,我们已经详细说明了在使用宏时应注意的一些问题。“表面似和善、背后一长串”绝对是对宏的形象表述。宏的使用具有一些优点:能减少代码量(比如简单字符替换重复的代码),在某种程度上提供可阅读性(比如MFC的消息映射),提高运行效率(比如没有函数调用开销)。

  然而谈到宏,绝对绕不开预处理器。把C/C++源码从源文件的形式变成可执行的二进制文件通常需要三个主要步骤:预处理→编译→链接。在预处理阶段,预处理器会完成宏替换。因为此过程并不在编译过程中进行,所以难以发现潜在的错误及其他代码维护问题,这会使代码变得难以分析,繁于调试。所以,宏—这个C语言中的“大明星”在C++的世界里却变成了程序员深恶痛绝的东西。因为#define 的内容不属于语言自身的范畴,所以C++设计者为我们提供了替代宏的几大利器,建议我们尽量使用编译器管制下的const、enum、inline来实现#define的几大功能。如此看来,本建议的名称换做“尽量把工作交给编译器而非预处理器”或许更合适。

  接下来分析一下#define的弊端,请看下面的代码片段:

#define PI 3.1415926

  在预处理阶段,预处理器就完成了代码中符号PI的全部替换,因为这个过程发生在源代码编译以前,所以编译器根本接触不到PI这个符号名,这个符号名更不会被编译器列入到符号表中。如果因为在代码中使用了这个常量PI而引起问题,那这个错误将可能变得不易察觉,难以找到问题,出错信息只会涉及3.1415926,对PI则只字未提。

  如果PI是在某个大家并不熟悉的或出自别人之手的头文件中定义的,那么寻找数值3.1415926的出处就如同大海捞针,费时费力。不过这一切也并非是不可避免的,解决的办法很简单,就是“使用常量来代替宏定义”:

const double PI = 3.1415926;

  作为语言层面的常量,PI肯定会被编译器看到,并且会确保其进入符号表中,也就不会出现类似“3.1415926有错误”这样模糊不清的错误信息了。当出现问题时,我们也会有章可循,可以通过符号名顺藤摸瓜,消灭错误。另外,使用常量可以避免目标码的多份复制,也就是说生成的目标代码会更小。这是由于预处理器会对目标代码中出现的所有宏 PI复制出一份3.1415926,而使用常量时只会为其分配一块内存。

  在使用普通常量时,有一种特殊情形会让我们感觉棘手,那就是常量指针。用const去修饰指针的方式有多种,诸如:

  1. const char* bookName = "150 C++ Tips";  
  2. char* const bookName = "150 C++ Tips";  
  3. const char* const bookName = "150 C++ Tips";

  应该使用哪一种方式确实是一个需要明确的问题。const修饰指针的规则可以简单地描述为:如果const出现在*左边,表示所指数据为常量;如果出现在*右边,表示指针自身是常量。需要注意的是,在头文件中定义常量指针时,是将指针声明为const了,而不是指针指向的数据。所以,如果定义一个指向常量字符串的常量指针,我们选择的就是最后一种,需要用两个const进行修饰。然而在定义指向常量字符串的常量指针时,用两个const修饰并不是我们推荐的形式。我们推荐使用更加安全、更加高级的const string形式:

const string bookName("150 C++ Tips");

  作为C++中最重要的概念,class与很多其他的关键字都产生了联系,const也肯定不会放过纠缠这个C++主角的机会,所以就有了常量数据成员。定义常量数据成员的主要目的就是为了将常量的作用域限制在一个特定的类里,为了让限制常量最多只有一份,还必须将该常量用static进行修饰,例如:

  1. class CStudent  
  2. {  
  3. private:  
  4.      static const int NUM_LESSONS = 5; //声明常量  
  5.      int scores[NUM_LESSONS];          //使用常量  
  6. };

  注意,上述注释中说的是“声明常量”,而非“定义常量”,并且在声明的同时,完成了“特殊形式”的初始化。之所以谓之“特殊形式”,是因为我们熟悉的一般形式的初始化是不允许放在声明里的。这种“特殊形式”的初始化在C++中被称为“类内初始化”。还有一点需要明确的是,在不同的编译器中对类内初始化的支持情况也不尽相同。在VC++ 2010中,并不是所有的内置类型都可以实现类内初始化,它只对整数类型(比如int、char、bool)的静态成员常量才有效。如果静态成员变量是上述类型之外的其他类型,如double型,那么需要将该类的初始化放到其实现文件该变量的定义处,如下所示:

  1. /* VC++ 2010 */  
  2. // CMathConstants声明文件(.h)  
  3. class CMathConstants  
  4. {  
  5. private:  
  6.      static const double PI;  
  7. };  
  8. // CMathConstants实现文件(.cpp)  
  9. const double CMathConstants::PI = 3.1415926;

41/41234>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号