重读经典-《Effective C++》Item2:尽量以const,enum,inline替换#define

来源:互联网 发布:php 转为utf8 编辑:程序博客网 时间:2024/05/17 06:06

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

 

1. 宏定义

 

#define ASPECT_RATIO 1.653

该宏定义ASPECT_RATIO也许从未被编译器看见,也许在编译器开始处理源代码之前就被预处理器替换了。我们知道,宏定义在预处理阶段会进行简单地字符串替换,凡是遇到ASPECT_RATIO的地方都被替换为1.653。因此,ASPECT_RATIO是不会进入符号表(symbol table)的。

 

符号表复习

 

(1) 什么是符号表?符号表有哪些重要作用?

符号表是用来记录编译过程中的各种信息的表格。

符号表的作用:

  • 登记编译过程输入和输出信息
  • 在语义分析过程中用于语义检查和中间代码生成
  • 作为目标代码生成阶段地址分配的依据

 

(2) 符号表的表项常包括哪些部分?各描述什么?

符号表的表项包含两大栏,即名字栏和信息栏;

名字栏也叫主栏,存放名字的标示符,称为关键字;

信息栏包含许多子栏和标志位,用来记录相应名字的各种不同属性。

 

(3) 符号表的组织方式有哪些?它的组织取决于哪些因素?

符号表的组织形式分为直接组织方式和间接组织方式两大类。

直接组织方式中各项按固定长度顺序存放;

间接组织方式中,符号表的主栏存放标识符的一个指示器和一个整数(标识符的起始位置和长度),而标识符的字符串则存放在一个字符串数组中。

 

符号表的组织主要取决于以下几个因素:

  • 表项中的各栏所占的存储单元和长度是否固定
  • 语言中标识符的长度限制
  • 哪些项有哪些共同值
  • 对符号表操作和使用方式

 

(4) Win32平台和Linux平台上怎样查看可执行程序的符号表?

  • win32平台

dumpbin命令

如>dumpbin /SYMBOLS filename (其中>为命令行提示符)

  • Linux平台

objdump命令

如# objdump -s filename (其中#为命令行提示符)

 

因此,当1.653出现编译错误的时候,我们很难搞清楚到底是哪里的问题;另外,在调试阶段,也很难定位(我们通过visual Stiduo或者Linux平台上的gdb在调试的过程中无法查知定义的宏的值,因为符号表中没有该符号),因此不能够所见即所得,还要通过查阅代码才能知道该宏定义。

 

那么,如何解决呢?如下。

 

2. 使用const定义常量

 

例如,以上define定义的宏可以改为:

const double AspectRatio = 1.653;  //大写名称通常用于宏,因此这里改变写法

 

从以上的那个以可以看出,该常量有类型,为double,它作为一个语言常量,肯定会被编译器看到,当然就会进入符号表。在调试的过程中,也可以查知该常量的值。

 

3. class专属常量

 

如果将常量的作用域(scope)限制于class内,必须让它成为class的一个成员(member)。如果要确保此常量至多有一份实体,必须让它成为static成员。

 

例如,以下程序可以很好的说明class专属常量的定义方法。

[c-sharp] view plaincopy
  1. /** 
  2. * <Effective C++>, page 14 
  3. * const data of class 
  4. * platform: visual studio 2005, win32 
  5. * filename: item2.1.cpp 
  6. */  
  7. #include <iostream>  
  8. using namespace std;  
  9.    
  10. class MyTest  
  11. {  
  12.     //(1) error C2864: 'MyTest::MaxNumber1' : only static const integral data members can be initialized within a class  
  13.     //int MaxNumber1 = 5;  
  14.    
  15.     //(2) error C2864: 'MyTest::MaxNumber2' : only static const integral data members can be initialized within a class  
  16.     //const int MaxNumber2 = 5;  
  17.    
  18.     //(3) error C2864: 'MyTest::MaxNumber3' : only static const integral data members can be initialized within a class  
  19.     //static int MaxNumber3 = 5;  
  20.    
  21.     //(4) ok  
  22.     static const int MaxNumber4 = 5;  
  23.     static const char cconst4 = 'B';  
  24.    
  25.     //(5) error C2864: 'MyTest::dconst4' : only static const integral data members can be initialized within a class  
  26.     //static const double dconst4 = 200.00;  
  27.    
  28. public:  
  29.     //(6) error C2758: 'MyTest::MaxNumber2' : must be initialized in constructor base/member initializer list  
  30.     MyTest()  
  31.     {  
  32.         cout<<"MyTest constructor! "<<endl;  
  33.         cout<<"MaxNumber4 = "<<MaxNumber4<<endl;  
  34.         cout<<"cconst4 = "<<cconst4<<endl;  
  35.     }  
  36. };  
  37.    
  38. int main()  
  39. {  
  40.     MyTest obj;  
  41.   
  42.     return 0;  
  43. }  

 

 

代码注释中的(1),(2),(3)表示step编号。

从(1),(2),(3)中,我们可以看出,只有static const integral data member(静态整型常量数据成员)才能在类内初始化。从(4),(5)中也可以得到证明。其中,char型相当于整型。

 

运行结果如下。

MyTest constructor!

MaxNumber = 5

cconst1 = A

cconst2 = B

dconst1 = 100

 

再如。

[c-sharp] view plaincopy
  1. /** 
  2. * <Effective C++>, page 14 
  3. * const data of class 
  4. * platform: visual studio 2005, win32 
  5. * filename: item2.2.cpp 
  6. */  
  7. #include <iostream>  
  8. using namespace std;  
  9.    
  10. class MyTest  
  11. {  
  12.     int MaxNumber1;  
  13.     const int MaxNumber2;  
  14.     static int MaxNumber3;  
  15.    
  16.     static const int MaxNumber4 = 5;  
  17.     static const char cconst4 = 'B';  
  18.    
  19.     static const int MaxNumber5;  
  20.    
  21. public:  
  22.     //(1) error C2758: 'MyTest::MaxNumber2' : must be initialized in constructor base/member initializer list  
  23.     //(4) error C2438: 'MaxNumber3' : cannot initialize static class data via constructor  
  24.     //(7) error C2438: 'MaxNumber5' : cannot initialize static class data via constructor  
  25.     MyTest():MaxNumber1(5), MaxNumber2(5)//, MaxNumber5(5)//, MaxNumber3(5)  
  26.     {  
  27.         //(2) error C2166: l-value specifies const object  
  28.         //MaxNumber2 = 5;  
  29.    
  30.         //(3) error LNK2001: unresolved external symbol "private: static int MyTest::MaxNumber3" (?MaxNumber3@MyTest@@0HA)  
  31.         //MaxNumber3 = 5;  
  32.    
  33.         //(6) error C3892: 'MaxNumber5' : you cannot assign to a variable that is const  
  34.         //MaxNumber5 = 5;  
  35.    
  36.         cout<<"MyTest constructor! "<<endl;  
  37.         cout<<"MaxNumber1 = "<<MaxNumber1<<endl;  
  38.         cout<<"MaxNumber2 = "<<MaxNumber2<<endl;  
  39.         cout<<"MaxNumber3 = "<<MaxNumber3<<endl;  
  40.         cout<<"MaxNumber4 = "<<MaxNumber4<<endl;  
  41.         cout<<"MaxNumber5 = "<<MaxNumber5<<endl;  
  42.         cout<<"cconst4 = "<<cconst4<<endl;  
  43.     }  
  44. };  
  45.    
  46. //(5) ok  
  47. int MyTest::MaxNumber3 = 5;  
  48.    
  49. //(8) ok  
  50. const int MyTest::MaxNumber5 = 5;  
  51.    
  52. //(9) error C2761: 'int MyTest::MaxNumber1' : member function redeclaration not allowed  
  53. //int MyTest::MaxNumber1 = 5;  
  54.    
  55. int main()  
  56. {  
  57.     MyTest obj;  
  58.    
  59.     return 0;  
  60. }  

 

 

运行结果如下。

MyTest constructor!

MaxNumber1 = 5

MaxNumber2 = 5

MaxNumber3 = 5

MaxNumber4 = 5

MaxNumber5 = 5

cconst4 = B

 

代码注释中的(1),(2),(3)表示step编号。

从(1),(2)可以看出,非静态的常量数据成员必须在构造函数的初始化列表中初始化;如果在构造函数中初始化,会出现error c2166的错误,即常量对象是只读(read only)的,不能对其赋值。

 

从(3),(4),(5)可知,静态非常量数据成员只能在类外(类的实现文件)初始化。

从(6),(7),(8)可知,静态常量数据成员也可以在类外(类的实现文件)初始化。

结论:

  • 静态常量数据成员可以在类内初始化(即类内声明的同时初始化),也可以在类外,即类的实现文件中初始化,不能在构造函数中初始化,也不能在构造函数的初始化列表中初始化;
  • 静态非常量数据成员只能在类外,即类的实现文件中初始化,也不能在构造函数中初始化,不能在构造函数的初始化列表中初始化;
  • 非静态的常量数据成员不能在类内初始化,也不能在构造函数中初始化,而只能且必须在构造函数的初始化列表中初始化;
  • 非静态的非常量数据成员不能在类内初始化,可以在构造函数中初始化,也可以在构造函数的初始化列表中初始化;

 

总结如下表:

类型 初始化方式

类内(声明)

类外(类实现文件)

构造函数中

构造函数的初始化列表

非静态非常量数据成员

N

N

Y

Y

非静态常量数据成员

N

N

N

Y (must)

静态非常量数据成员

N

Y (must)

N

N

静态常量数据成员

Y

Y

N

N

 

4. enum类型的class专属常量

 

上述4中类型的数据成员,都有各自的初始化方法,唯一列外就是在class编译期间要一个class常量,除了采用静态常量数据成员外,还可以使用enum类型的数据,即改用所谓的"the enum hack"补偿做法,其理论基础是“一个属于枚举类型(enumerated type)的数值可权充int被使用”。例如,

 

class MyTest

{

private:

    enum {MaxNumber = 5}; //"the enum hack"使MaxNumber成为5的一个记号名称

    int score[MaxNumber];

 

};

 

enum hack的行为某方面较像#define而不像const,如可以取一个const的地址,而不能取一个enum的地址,也不能取一个#define的地址;

 

5. 使用inline函数代替宏函数

 

(template) inline函数的好处:

  • 获得宏带来的效率(宏没有函数调用带来的额外开销)
  • 一般函数的所有可预料行为和类型安全性(type safety)

 

 

remember

对于单纯常量,最好以const对象或enum替换#defines;

对于形似函数的宏(macros),最好改用inline函数替换#defines

 

注:该文程序亦可在Linux平台上运行。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 给青竹蛇咬了怎么办 被青竹蛇咬了怎么办 孕早期吃了桂皮怎么办? 怀孕后吃了八角怎么办 孕妇吃了点八角怎么办 煮粥老是溢出来怎么办 6个月的小孩咳嗽怎么办 10个月婴儿咳嗽怎么办 6个月婴儿感冒了怎么办 六个月宝宝有痰怎么办 9月婴儿牛奶过敏怎么办 一用粉底就过敏怎么办 7个月婴儿过敏怎么办 9个月宝宝腹泻怎么办 9个月宝宝拉肚子怎么办 5个月宝宝拉肚子怎么办 九个月大宝宝拉肚子怎么办 九个半月的宝宝拉肚子怎么办 9个月婴儿拉稀怎么办 九个月的宝宝拉肚子怎么办 9个月宝宝拉稀水怎么办 2岁宝宝腹泻拉水怎么办 18个月宝宝拉水怎么办 2个月宝宝拉稀水怎么办 2个月的宝宝腹泻怎么办 2个月的宝宝拉稀怎么办 宝宝不喝补液盐怎么办 4个月宝宝腹泻怎么办 6个月宝宝腹泻怎么办 四个月的宝宝拉肚子怎么办 7个月婴儿便秘怎么办 宝宝9个月拉肚子怎么办 2个月宝宝拉肚子怎么办 4个月宝宝没奶怎么办 宝宝又吐又拉怎么办 冬季车放在外面怎么办 冬天车放在外面怎么办 新车被拖走了要怎么办 门钥匙拔不出来怎么办 婴儿换尿布就哭怎么办 芥末吃多了胃疼怎么办