• 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去修改该类内变量就是合法的了。



  • 相关阅读:
    [loj3031]聚会
    [loj3146]路灯
    [loj2049]网络
    [luogu7599]雨林跳跃
    [loj3069]整点计数
    [loj3301]魔法商店
    [loj3333]混合物
    [cf1515I]Phoenix and Diamonds
    [cf1515H]Phoenix and Bits
    [atARC116F]Deque Game
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/14399047.html
Copyright © 2020-2023  润新知