C++你可能不知道地方


c++中編譯器替我們完成了許多事情,我們可能不知道,但也可能習以為常。下面詳細介紹
 
一、初始化與初始賦值
首先說說類的初始化與初始賦值之前的區別,這也許里面可能有我們不知道的事情。  
其實類初始化與初始賦值還是有區別的。
 1         class People{
 2         public:
 3                 People(std::string name,int age,int height);
 4         private:
 5                 std::string m_sName;
 6                 int m_iAge;
 7                 int m_iHeight;
 8         }
 9         //賦值
10         People::People(std::string name,int age,int height)
11         {
12                 m_sName=name;
13                 m_iAge=age;
14                 m_iHeight=height;
15         }
16         //初始化列表
17         People::People(std::string name,int age,int height)
18         :m_sName(name),m_iAge(age),m_iHeight(height)
19         {}
 C++規定,對象的成員變量初始化動作發生在進入構造函數本體之前。在構造函數內成員變量賦值都不是初始化,而是賦值。
 賦值時首先調用默認構造函數為m_sName,m_iAge,m_iHeight賦初始值,然后在立刻調用賦值操作符進行賦新值。
 成員初始列表是將各個成員變量實參都作為復制構造函數的實參。
 所以看出賦值相對於初始化,多了一步就是使用賦值操作符進行賦值。所以初始化的效率比賦值的效率高多了。但是對於內置類型,它們效率是一樣的。
 
二、空類
  想想你如果聲明一個空類,C++編譯器會對它做什么呢?編譯器就會為它聲明一個復制構造函數,賦值操作符和一個析構函數,以及默認構造函數。所有這些函數都是public而且inline函數。
編譯器寫的賦值構造函數和賦值操作符,只是單純地將來源對象的每個non-static變量拷貝到目標對象,具體是進行位拷貝。
     如果聲明了一個構造函數,編譯器是不會創建默認構造函數。
  如果不希望類支持拷貝構造函數與賦值操作符怎么辦?不聲明?按照上面說明編譯器會自動幫你生成。那么可以將它們聲明為private,這樣阻止編譯器自動生成拷貝構造函數(public)。private成功阻止他人使用,但是這並不安全。因為類成員函數以及友元函數還是可以調用private的拷貝構造函數和賦值操作符。
     如果只在private下聲明拷貝函數和賦值操作符,在有人通過類成員函數去以及member函數去調用它,會獲得一個連接錯誤。那么這里能不能將錯誤在編譯的時候體現出來呢?這里只用將拷貝函數聲明為private,並且不在自身,就可以辦到了。顯然繼承一個拷貝函數和賦值操作符為private的基類就辦到了,基類如下:
1         class NonCopyable{
2         protected:
3                  NonCopyable (){}
4                 ~  NonCopyable (){}
5         private:
6              NonCopyable (const  NonCopyable &);
7              NonCopyable & operater=(const  NonCopyable &);
8         };
  原因是類成員函數或者友元函數嘗試拷貝對象,編譯器便會嘗試生成一個復制構造函數與賦值操作符,並會調用基類的對應函數,但是會被拒絕,因為基類這些函數是private。
 
3、++函數
  下面說說“*++"與"++*"中你不知道的事情,c++規定后綴形式自加函數有一個int類型參數,當函數被調用時,便其一傳遞一個0作為int參數的值傳遞給該函數,而前綴形式自己函數,類型參數沒有要求,所以這樣就能區分一個++函數是前綴形式與后綴形式了,具體代碼如下:
 1 class UPInt{
 2 public
 3     UPInt& operator++( ) ;                      //++ 前綴
 4     const UPInt operator++( int );          //++后綴
 5     UPInt& operator --( );                       // --前綴
 6     const UPInt operator --( int )           //--后綴
 7     UPInt& operator +=( int );               //
 8     ...
 9 };
10 
11 UPInt & UPInt::operator++( )
12 {
13     *this += 1;
14     return *this;
15 }
16 
17 const UPInt UPInt :: operator++( int )
18 {
19     UPInt oldValue = *this;
20     ++(*this);
21     return oldValue;
22 }
后綴函數使用返回參數類型const,是為了避免下面代碼生效
1 UPInt i;
2 i++++;
這個時候第一次調用++返回cosnt對象,並再次調用然后這個函數是non-const成員函數,所以const對象無法調用這個函數,那么i++++就無法生效了。
這里說說效率問題,我們可以看到后綴++函數建立一個臨時對象以作為它返回值,這個臨時對象經過構造並在最后被析構。而前綴++函數沒有這樣的臨時變量,並且沒有那樣的操作。所以如果我們在程序中使用前綴++效率會更加高一些,沒有了臨時變量的構造與析構的動作。
 
4.虛析構函數
帶有多態性質的base class應該聲明一個virtual析構函數。
為什么這么說呢?看下面例子
        class base
        { ... }
        class derived:public base
        {... }
        
        base * p= new derived;
     假設這里基類的析構函數不是virtual,當使用完p指針,我們刪除它的時候,想想會發生什么,因為基類的析構函數是non-virtual所以不會發生多態直接調用基類析構函數,僅僅刪除繼承類中基類那部分內容,那么繼承類對象其他內存沒有被銷毀,從而資源泄漏。
    如果將其聲明為virtual,那么就會發生多態,調用的是指向繼承類的指針,那么就會銷毀的是整個繼承類象。
 
5.傳遞方式用引用
 缺省情況下c++以值傳遞方式傳遞對象至函數。函數參數都是以實際實參的復件為初值,而調用端所獲得的是函數返回值的一個附件。這些復件都是由拷貝構造函數產出。看如下例子
 1         class Person{
 2         public:
 3             Person();
 4             virtual ~Person();
 5             ...
 6         private:
 7             std::string name;
 8             std::string address;
 9         }
10         
11         class Student:public Person{
12         public:
13             Student();
14             ~Student();
15             ...
16         private:
17             std::string schoolName;
18             std::string schoolAddress;
19         };
 那么如果有一個函數驗證是否為學生
1   bool validateStudent(Student s);
2   Student plato;
3   bool platoIsOK=validateStudent(plato);
  分析這3行代碼,編譯器到底做了什么?首先調用Student的copy構造函數,然后以plato為藍本將s初始化,當validateStudent返回被銷毀,所以成本為"一次Student copy構造函數調用,加上一次Student析構函數調用"。
      Student對象內部有兩個string對象,所以構造了兩個string對象。Student繼承自Person對象,里面又有兩個string對象。所以by value方式傳遞一個Student對象,總體成本是"六次構造函數和六次析構函數"!
      以by reference方式傳遞參數也可避免對象切割問題。當一個derived class對象以by value方式傳遞並被視為一個base class對象,base class的copy構造函數會被調用,造成像derived class對象全被切割掉了,僅僅留下base class對象。看如下代碼通過傳遞引用參數完成多態
        class Window{
        public:
            ...
            std::string name() const;
            virtual void display() const;
        };
        class WindowWithScrollBars:public Window{
        public:
            ...
            virtual void display() const;
        };
        
        //傳入Windos類型,調用其display函數
        //傳入WindowWithScrollBars類型,調用其display函數
        //體現多態
        void printNameAndDispaly(const Window& w)
        {
            std::cout<<w.name();
            w.display();
        }    
窺視c++編譯器的底層,reference往往以指針實現出來,因此pass by reference真正傳遞的是指針。如果對象屬於內置型,pass by value往往比pass by reference 效率高些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM