Effective C++学习笔记(一)

来源:互联网 发布:ubuntu nagios 安装 编辑:程序博客网 时间:2024/05/29 10:53
1.条款02  尽量以const ,enum,inline代替#define

     (1)  尽量用const或者enum代替#define。原因是:#define不做类型检查,并且#define不在编译器登记,所以在调试的时候不方便。用const可以达到同样的效果,并且还能            做 类型检 查。而且#define不能有private这样的限定,不能进行封装。同样也可以使用enum。如:

   class Person
{
       enum { NUM = 5};
       int a[NUM];
    };

            最好是加上inline代替#define,因为这样的话可以提高效率。

     (2)  类中的static能在类中声明,但是定义一般要在类外面进行定义(除了是static const int 类型的),否则会有link2001这样的错误。还有一点,static数据成员的类           型可以是该成员所属的类类型,非static数据成员被限定为其自生类对象的指针或引用。如:

   class Person
{

static const int age=20;
static string address;
static Person person1;
Person
*person2;
Person person3;
    };

      是错误的,错误的原因是Person person3;是不正确的。

                说完了static成员后,我们再来看看static成员函数,static成员是类的组成部分并不是任何对象的组成部分,因此,static成员函数没有this指针。我们知道,一般而                言,类中的成员函数具有一个附加的隐含实参,即指向该类对象的一个指针。这个隐含实参命名为this。因为static成员函数不是任何对象的组成部分,所以static成员函数就没有this形参了。由于成员函数声明为const说明该成员函数不会修改该成员函数所属的对象,所以static成员函数不能声明为const。为什么呢?因为static成员函数不是任何对象的组成部分。static成员函数可以直接访问所属类的static成员,但是不能直接使用非static成员函数!也不能访问static const 类型的成员!

2.条款03尽量使用const

   (1)const Widget *pw 或者Widget const*pw 指的是所指的内容不能变。而Widget *  const pw指的是指针不能变。

    (2)STL迭代器中的iterator和const_iterator区别是:前者指向的是可以改变值元素的指针,后者指向的是内容不能改变的元素的指针。

    (3) const iterator表示的是指针的值不能改变。

    (4)const成员函数不能改变当前类的成员变量。

    (5)为了提高效率,函数参数传递可以采用passed by reference-to-const 或者passed by pointer-to-const。

3. 条款04  确定对象被使用前已被初始化过

      (1)区分一下初始化和赋值。在构造函数中的赋值操作不是初始化,用参数列表的构造函数才是真正的初始化。参数列表是在进入构造函数之前进行的,效率高,因为在进          入构造函数函数体之前,会为成员变量初始化,如果在此时就为成员变量初始化了正确的值,那么就不需要对其进行赋值操作。如果错过了这个机会,那么只能在构造函            数内要对其进行赋值操作才能完成值设定,效率就会降低。

       (2)在构造函数的初始化列表里,初始化顺序尽量是按照类里面属性的顺序,要不容易产生一些错误。比如:先在类里定义了一个数组的长度,然后定义了一个数组,如          果先初始化数组然后定义数组长度,那么这个逻辑是不正确的。

4.  条款05 了解C++默认构造函数,赋值函数,析构函数,复制构造函数

       (1) 如果c++没有定义构造函数,析构函数,复制构造函数,赋值函数,编译器会自动为你构造这些函数。

        (2)如果类成员变量里面有引用类型或者const类型,则使用系统给你生成的复制构造函数会出现问题,如:

            class A
           {
              public:
                     int num;
                     string &name;
                     A( string &strname, int Num ):name( strname ),num( Num )
                    {}
             };


            void main()
           {
               string str1( "dog" );
               string str2( "cat" );
     
               string & str11 = str1;
               string & str22 = str2;
               A A1( str11 , 10 );
               A A2( str22 , 20 );
               A2 = A1;
            }

    则会报这样的错误error C2582: 'A' : 'operator =' function is unavailable,因为类中含有string &name。解决问题的办法是定义自己的复制运算符。

5.条款06 若不想使用编译器自动生成的函数,就该明确拒绝

      (1)第一种拒绝方法:声明复制构造函数和赋值函数,并将其声明为private类型,并且不实现它。优点和缺点:程序在使用类的复制构造函数或者复制运算符的时候编译时          就会报错,因为是private类型的所以拒绝调用。但是如果是类中的成员函数或者友元类或者函数调用的话,就会报找不到函数实现的错误,有点让人感觉莫名其妙。

       (2)第二种拒绝方法:

            class uncopy          

            {           

                   private:         

                           uncopy(const uncopy&);        

                           uncopy& operator=(const uncopy&);         

               }          

            class A:private uncopy        

             {           

                    public:                

                             string name;                

                              int age;                

                              A()                {                 }                 

                              void print();          

                      private:       

                               A( const A&);     

                               A& operator=(const A&);         

                 };        

              void main()     

              {  

                        A a;   

                        a.age= 20;   s

                       tring str("hello");   

                       a.name = str;     

                }

      这样的话,在调用类A的复制运算符的时候在编译的时候就会报错,不会出现在运行的时候提示找不到函数实现的问题了。

6.条款07  为多态基类声明virtual析构函数

     1.带有多态性质的基类应该声明一个virtual析构函数,如果类中带有任何virtual函数,它都应该拥有一个virtual析构函数。个人理解:带有virtual函数,那么子类就有可能覆盖         这个virtual函数,如果在子类覆盖这个virtual中出现了像new之类的申请内存空间的清空,那么就需要子类的析构函数进行析构。此时将子类的析构函数设置成virtual,那么          以后用父类的指针保存子类的对象的话,就可以在delete的时候调用子类的析构函数,析构在子类中new的内存空间。

     2.如果不需要虚函数的类尽量不要使用虚函数,因为使用虚函数就会在该类中偷偷的添加一个虚函数表指针,这个虚函数表指针在32bit操作系统中中是32位,64位操作系统         下是64位,所以会使类变得臃肿。

      3. C++里面标准的类如果它们的析构函数不是virtual的话,继承它是一个不明智的选择。比如:string类的析构函数不是一个virtual,所以如果用户自定义一个类继承string,          将是一个不明智的选择。

 7 条款08 别让异常逃离析构函数

        尽量不让在析构函数中抛出异常,因为是在析构函数中抛出异常,虽然可以用try...catch进行处理,但是比如说这个类如果在一个array中,频繁的抛出异常会带来不确定的       情况。为了解决这个问题就是尽量不要再析构函数中抛出异常。

      (1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。

          

   DBConn::~DBConn()
   {
      try{db.close();}
      catch(...)
       {
          制作运转记录,记下对close的调用失败;
          std::absort();
       }
    }
    或者
   DBConn::~DBConn()
   {
      try{db.close();}
      catch(...)
       {
          制作运转记录,记下对close的调用失败;
       }
    }
   }                                                                                                                        

    (2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么应该提供一个普通函数执行该操作。如:

   bool closed;
   // 普通close函数,供客户调用,在这个函数里面可以在进入析构函数之前处理一些异常
   void close
   {
       db.close();
       closed=true;
   }
   DBConn::~DBConn()
   { 
     if(!closed)
     {
         try{db.close();}
         catch(...)
         {
            制作运转记录,记下对close的调用失败;
         }
      }
   }  

8.  条款9 绝不在构造和析构过程中调用virtual

   (1)在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived  class。个人理解:如果在父类的构造函数中使用了一个virtual函数,并且集成它的子类重写了          这个virtual函数,那么如果以后实例化一个子类的话,首先会调用父类的构造函数,然而在父类的构造函数中调用了virtual函数,这个virtual函数本来应该是调用子类的,   

         但是现在子类还没有初始化,所以呢编译器现在不把virtual函数当做virtual函数,它将调用父类的这个函数,会导致错误。同理析构函数是先析构子类,然后再析构父类的           时候有一个virtual函数,本来该调用子类的,但此时子类已经被析构过了,就会出现问题。

9. 条款10  令operator=返回一个reference to *this

     (1)关于赋值,可以这样连续赋值 x=y=z=15;  采用右结合律上述赋值被解析为x=(y=(z=15)) ;为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实                 参。

     Widget& operator=(const Widget& rhs)
      {
          ....
          return *this
      }
     同样的operator+=等也应该返回reference to *this

10 条款11 在operator=中处理“自我赋值”

       (1)为了保障在赋值运算符不出现Widget=Widget的情况,在重载赋值运算符函数里要做一些判断,最传统的判断如下所示:

     Widget& operator=(const Widget& rhs)
      {
         if(this == &rhs) return *this;
          ....
      }
11 条款12 复制对象时勿忘其每一个成分
   (1)复制构造函数和赋值运算符在重载的时候一定要确保复制对象内的所有成员变量以及所有基类的成员变量。
     class A     {      public:           string name;           int age;           A( const string & strname, const int & intage )          {       name = strname;       age = intage;           }  A& operator=(const A& a)  {name = a.name;age = a.age;return *this;  }     };    class AA :public A    {       public :  string sex;AA(string strname, int intage , string strsex):A(strname,intage),sex(strsex){}AA ( const AA &aa ):A(aa),sex(aa.sex)//调用了A的拷贝构造函数,注意穿进去的是AA类型的,它会把AA中的A的部分赋值给A{}AA& operator=( const AA &aa){A::operator =(aa);//对A成分进行了赋值操作sex = aa.sex;return *this;}void print(){cout<< this->name << "  " << this->age << " " << this->sex <<endl;}      };     void main()      {string strname("lily");string strsex("male");        AA aa(strname,20,strsex);        aa.print();string strname1("lilei");string strsex1("male");        AA aa1(strname1,20,strsex1);aa1.print();aa1=aa;aa1.print();      }
原创粉丝点击