Effective C++

目录

规则2:尽量使用const、enum、inline替换#define

  1、使用const替换#define

  2、定义一个类的专属常量:在类内使用const和static

    需要在实现文件中再定义一次已经在类内声明了的static变量

  3、使用inline定义内联函数,替换#define

规则3:尽量可能使用const

  1、const用于指针中

  2、const用于STL中的迭代器中

  3、令函数返回一个const对象,往往可以避免客户因为错误使用而造成的意外,如下面的代码:

  4、const类内成员函数:在函数的参数列表后加上一个const---mutable引入

    使用const_cast为对象去掉const属性和使用static_cast为对象加上const属性的方法

规则2:尽量使用const、enum、inline替换#define

  1、使用const替换#define

1 #define ASPECT_RATIO 1.653  //大写常用语宏,但是每次出现ASPECT_RATIO的地方,预处理器都会盲目的将ASPECT_RATIO替换为1.653,导致目标代码出现多个1.653
2 const double AspectRatio = 1.653;  //此时在目标代码中只出现一个存储1.653的地方

  2、定义一个类的专属常量:在类内使用const和static

1 class GamePlayer{
2 private:
3     static const int NumTurns = 5;  //static确保了所有类对象共享NumTurns(NumTurns只有一份)、const确保了NumTurns不可以被修改
4     int scores[NumTurns];
5 };  //放在头文件中

需要在实现文件中再次定义在类中已经声明了的static变量的情况:

上面是NumTurns的声明式而非定义式。通常C++要求你给任何东西提供一个定义式,但如果它是一个类内static、const变量,只要不取它的地址,则可以声明并使用它而无需提供定义式。但是你需要取它的地址或编译器不明确的简直要看到一个定义式,那么需要提供下面的定义式:

1 class GamePlayer{
2 private:
3     static const int NumTurns;  
4     int scores[NumTurns];
5 };  //放在头文件中
6 const int GamePlayer::NumTurns = 5;  //NumTurns的定义式,放在实现文件而非头文件中,在定义式中获得初值

上述代码存在的问题:万一你的编译器错误的不允许类内static、const变量完成初值设定,而此时类内的scores需要使用该变量以确定数组内元素的个数,此时可以使用枚举:

1 class GamePlayer{
2 private:
3     enum {NumTurns = 5};    //注意此时NumTurns是一个枚举,此时对NumTurns取地址是不合法的
4     int scores[NumTurns];
5 };  //放在头文件中

 

  3、使用inline定义内联函数,替换#define

引入:

1 #define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))  //f是只带一个参数的函数
2 int a = 5, b = 0;
3 CALL_WITH_MAX(++a,b);    //在判断(++a) > (b)的时候a被累加一次,发现成立,然后再执行(++a),并将++a的结果做为f的参数。即a被累加了两次
4 CALL_WITH_MAX(++a,b+10); //在判断(++a) > (b)的时候a被累加一次,发现不成立,然后将b做为f的参数。即a被累加了一次

在调用函数f之前,a的递增此时居然取决于它被拿来和谁比较,可以使用内联函数和模板来代替:

1 template<typename T>
2 inline void callWithMax(const T & a, const T & b){
3     f(a > b ? a : b);
4 }

 

规则3:尽量可能使用const

  1、const用于指针中

1 char greeting1[] = "Hello";
2 char greeting2[] = "GoodMorning";
3 char* p = greeting1;              //non-const pointer(p可以改变指向,p=greeting2合法),non-const data(p[0]='G'合法)
4 const char* p = greeting1;        //non-const pointer(p可以改变指向,p=greeting2合法),const data(p[0]='G'不合法)
5 char* const p = greeting1;        //const pointer(p不可以改变指向,p=greeting2不合法),non-const data(p[0]='G'合法)
6 const char* const p = greeting1;  //const pointer(p不可以改变指向,p=greeting2不合法),non-const data(p[0]='G'不合法)

其中const char* p = greeting1; 也可以写成:

char const * p = greeting1;

 

  2、const用于STL中的迭代器中

1 const用于迭代器表示这个迭代器不可以指向不同的东西,但是它指向的东西是可以改变的,例如:
2 std::vector<int> vec;
3 ...
4 const std::vector<int>::iterator iter = vec.begin();  //表示iter只可以指向vec容器内的第一个元素,不可以指向其他的元素,但是iter指向的元素本身是可以被改变的
5 *iter = 10;  //合法                                   //等价于T* const (char* const)
6 ++iter;      //不合法

如果你希望迭代器所指向的东西不可以被改变,那么可以使用const_iterator:

1 std::vector<int> vec;
2 ...
3 const std::vector<int>::const_iterator iter = vec.begin();  //等价于const T* (const char*)
4 *iter = 10;  //不合法
5 ++iter;      //合法

 

  3、令函数返回一个const对象,往往可以避免客户因为错误使用而造成的意外,如下面的代码:

1 class Rational{...};
2 const Rational operator*(const Rational & lhs, const Rational & rhs);

返回一个const Rational类对象的原因是避免客户出现下面的错误:

1 int main(){
2      Rational a,b,c;
3      ...
4      (a*b)=c; 
5      (a*b)将调用operator*()函数,然后返回一个const类型的Rational类对象,然后将c赋给该const Rational类对象是不合法的!  
6 }

需要补充个的知识点:

1 const int num = 5;
2 num = 6;  //不合法!!

  4、const类内成员函数:在函数的参数列表后加上一个const

4.1两个成员函数如果只是常量性不同,则可以被重载,举例如下:

 1 class TextBlock{
 2 public:
 3     ....
 4     const char & operator[](std::size_t position) const{  //类内const成员函数
 5         return text[position];
 6     }
 7     const char & operator[](std::size_t position){             //类内non-const成员函数
 8         return text[position];
 9     }
10 private:    
11     std::string text;
12 };
13 
14 int main(){
15     TextBlock tb("Hello");
16     std::cout << tb[0];          //调用类内non-const成员函数
17     
18     const TextBlock ctb("Hello");
19     std::cout << ctb[0];         //调用类内const成员函数
20     
21     return 0;
22 }

上面的这个例子有点造作,下面的这个例子才是真实环境下的类内const成员函数使用方法:

1 void print(const TextBlock & ctb){
2     std::cout << ctb[0];        //调用类内const成员函数
3     ...
4 }

对于上面的代码,将私有变量原型的类型string改为char*,在类外使用一个指针指向了类内成员变量,那么就有可能修改该const对象中的变量,如下面的代码:

 1 class CTextBlock{
 2 public:
 3     ....
 4     const char & operator[](std::size_t position) const{  //类内const成员函数
 5         return text[position];
 6     }
 7     const char & operator[](std::size_t position){             //类内non-const成员函数
 8         return text[position];
 9     }
10 private:    
11     char* pText;
12 };
13 int main(){
14     const CTextBlock cctb("Hello");
15     char* pc = &cctb[0];
16     *pc = 'J';              //此时cctb类对象中的*pText=Jello
17     
18     return 0;
19 }

上面的程序表明,你创建了一个常量对象并设以初值,并对它只调用const成员函数,但是你最终还是改变了它的值。

写到这里,需要停下来思考一个问题:成员函数如果是const意味着什么?
bitwise constness学派认为:不可以更改对象中任何成员变量(static变量除外)才可以说是const成员函数;
logical constness学派认为:在编译器侦测不出来的情况下,可以修改对象内的某一位,使编译器侦测不来的方法是使用mutable

不符合bitwise constness观点的代码:

 1 class CTextBlock{
 2     
 3     std::size_t length() const;
 4 private:
 5     char* pText;
 6     std::size_t textLength;   //需要在length()类内修改,但是不符合bitwise constness观点
 7     bool lengthIValid;        //需要在length()类内修改,但是不符合bitwise constness观点
 8 };
 9 std::size_t CTextBlock::length() const{
10     if(!lengthIValid){
11         textLength = std::strlen(pText);   //在const成员函数内修改了类成员,不符合bitwise constness观点
12         lengthIValid = true;               //在const成员函数内修改了类成员,不符合bitwise constness观点
13     }
14     return textLength;
15 }

由于不符合bitwise constness观点,以上代码会出现编译错误,使用mutable关键字修改类内需要在const成员函数内修改的类成员变量,修改方法如下:

 1 class CTextBlock{
 2     
 3     std::size_t length() const;
 4 private:
 5     char* pText;
 6     mutable std::size_t textLength;   //需要在length()类内修改,故将textLength声明为mutable变量
 7     mutable bool lengthIValid;        //需要在length()类内修改,故将textLength声明为mutable变量
 8 };
 9 std::size_t CTextBlock::length() const{
10     if(!lengthIValid){
11         textLength = std::strlen(pText);   //在const成员函数内修改了mutable类成员textLength,是合法的
12         lengthIValid = true;               //在const成员函数内修改了mutable类成员lengthIValid,是合法的
13     }
14     return textLength;
15 }

4.2 const和non-const成员函数避免重复

如下面的代码,存在代码重复的问题:

 1 class TextBlock{
 2 public:
 3     ....
 4     const char & operator[](std::size_t position) const{       //类内const成员函数
 5         ...                     //边界检验
 6         ...                     //数据访问
 7         ...                        //数据完整性检验
 8         return text[position];
 9     }
10     const char & operator[](std::size_t position){             //类内non-const成员函数
11         ...                     //边界检验
12         ...                     //数据访问
13         ...                        //数据完整性检验
14         return text[position];
15     }
16 private:    
17     std::string text;
18 };

于是在const operator[]()中和non-const operator[]()成员函数中,存在着代码重复的问题,解决方法是在non-const operator[]()成员函数中调用const operator[](),那么就需要为该对象加上const属性,然后调用const operator[]()函数,最后再去掉const属性。其中为对象加上const属性方法是使用static_cast,为对象去掉const属性方法是使用const_cast,修改方法如下:

 1 class TextBlock{
 2 public:
 3     ....
 4     const char & operator[](std::size_t position) const{       //类内const成员函数
 5         ...                     //边界检验
 6         ...                     //数据访问
 7         ...                        //数据完整性检验
 8         return text[position];
 9     }
10     char & operator[](std::size_t position){             //类内non-const成员函数
11         return 
12             const_cast<char &>(                    //3、将const operator[]()函数返回值的const属性去掉
13                 static<const TextBlock &>(*this)   //1、由于调用的是non-const属性的operator[](),所以*this的原型是TextBlock &,无const属性,所以static<const TextBlock &>(*this)为*this加上const属性
14                     [position]                     //2、调用const operator[]()
15             );
16     }
17 private:    
18     std::string text;
19 };

为何不可以使用const成员函数调用non-const成员函数?
const成员函数承诺不修改类内变量,但non-const成员函数没有这样的承诺,如果使用const成员函数调用non-const成员函数就有可能发生在const成员函数中修改某一个类内变量是非法的,但是跑到non-const去修改该类内变量就是合法的了。



 

posted @ 2021-02-12 17:55  兵临城下的匹夫  阅读(428)  评论(0编辑  收藏  举报
TOP