天天看點

Effective C++學習筆記(一)

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();
      }