天天看點

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

類的三大屬性:

private,public,protected

1,對于類的成員變量或者函數,預設即為私有

#include <iostream>
using namespace std;

class A
{
        int y;                      //私有成員    
        int x;                      //私有成員
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}
                void sety(int n)      {y = n;}
};

int main()
{
        A a(1,2);                                                                                                                               
}
           

2,對于類的繼承而言,預設也是私有繼承!!!!!

#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int y;
        int x;
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}
                void sety(int n)      {y = n;}
};

class B:A
{
        int x;
        public:
        B(int xx, int yy,int xxx):A(xx,yy)
        {       x = xxx;             }
};

int main()
{
        B b(1,2,3);
}
           

如上,B也是同樣的私有繼承A了。。。。

3,内聯函數

      内聯函數是一種特殊的函數,本身比較少,運作起來也特别快,,由于函數調用會産生一定的調用語句,參數的

      傳遞,入棧出棧,那麼當這個函數被反複調用的時候,就會産生很大的開銷。于是就産生了内聯函數。

      當我們的編譯器看到了關鍵字:inline的時候,那麼編譯器會把函數的實作全部插入到調用内聯函數出,這樣就

      不會産生額外的調用開銷了

      内聯成員函數:

      1,inline + 成員函數

      2,整個函數體出現在定義類的内部

#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}                    //為内聯函數,函數的定義在class中
                inline void sety(int n);                            //同樣的為内聯函數,通過inline聲明
};

void A::sety(int n)
{
        y = n;  
}

int main()
{
        A a(1,2);                                                                                                                               
}
           

          内聯函數,從代碼層看,有函數的結構,而編譯後卻沒有函數的性質,不是在調用時候發生控制轉移,而是

          在編譯的時候,将函數體嵌入到每一個調用之處。

3,成員函數的重載以及參數預設

      我們知道函數是可以重載的,不同的傳回值類型或者函數的個數是不相同的,那麼就是函數的重載,同樣:成員

      函數也是可以重載的。成員函數也是可以帶有預設的參數的。C++文法規定,函數在寫的時候可以有預設值,那麼

      在調用的時候,如果我們沒有給參數,那麼參數就是預設值。

#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int x = 0, int y = 0);          //預設參數
                void setx(int m)      {x = m;}           //下面這兩個函數為setx函數的重載
                int  setx()     {return x;}
};
                                                                                                                                                
int main()
{
        A a(1,2);
}
           

       這裡我們需要注意的就是使用:預設參數時和函數重載時的二異行,,,,,

       如下:程式

#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int xx = 0, int yy = 0);          //預設參數
                void setx(int m =0)      {x = m;}
                int  setx()     {return x;}                                                                                                     
};

int main()
{
        A a(1,2);
        a.setx();
}
           

如上的setx函數,a調用setx函數,就不知道調用的是哪一個,就會産生編譯出錯,,,,:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

5,構造函數

      一:成員函數的一種,名字與類名相同,可以有參數,不能有傳回值(任何類型都不行,包括void)

      二:作用是對類對象進行初始化,如:給成員變量賦初值

      三:如果定義類的時候沒有寫構造函數,則編譯器生成一個預設的無參構造函數(不做任何操作)

      四:如果我們定義了構造函數,那麼編譯器不會生成預設的無參構造函數

      五:對象生成時構造函數自動被調用(一定),對象一旦生成,就再也不能在其上執行構造函數

      六:一個類中可以有多個構造函數,構造函數的重載,那麼如果要調用那個構造函數,需要看我們的對象是如何

             調用的

      那麼為什麼需要構造函數呢????

      構造函數執行必要的初始化工作,有了構造函數就不必在專門寫必要的初始化函數了,也不用擔心忘記了寫

      初始化函數了,有時候對象沒有被初始化就使用,那麼就會導緻編譯錯誤了。。。。

#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int xx = 0, int yy = 0);          //預設參數
                void setx(int m =0)      {x = m;}
};
//編譯器自動的調用預設構造函數
int main()
{
        A a;            //預設的構造函數被調用
        A *pc = new A;  //預設的構造函數被調用
        pc->init(1,2);                                                                                                                          
}
           

如上,隻要有對象生成,就一定會調用構造函數

        構造函數的重載及其調用問題:

#include <iostream>
using namespace std;

class A
{
        double y;          
        double x;
        public:
                A(double xx, double yy)
                {    x = xx; y = yy;}
                A(double xxx)
                {    x = xxx;}
                void setx(int m =0)      {x = m;}
};
//構造函數的重載
int main()
{
        A a(2,3);           //調用的是第一個構造函數,整形被轉化成了double類型
        A b(2);             //調用的是第二個構造函數,同樣的被轉化成了double                                                                    
}
           

            構造函數在數組(對象數組)中的使用:

#include <iostream>
using namespace std;

class A
{
        double x;
        public:
                A()
                {     cout<<"Construction 1"<<endl; }
                A(double xx)
                {     cout<<"Construction 2"<<endl; x = xx;}
                void setx(int m =0)      {x = m;}
};

int main()
{
        A a1[2];     //調用無參的構造函數
        cout<<"step1"<<endl;
        A a2[2] = {4,5};  //調用有參的構造函數初始化
        cout<<"step2"<<endl;    
        A a3[2] = {3};    //一個調用有參的構造函數,一個調用無參的構造函數
        A *p = new A[2];  //調用無參的構造函數                                                                                                  

        delete[] p;
}
           

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

           如上:這裡還是有很多不同的,,,,,,生成一個對象就一定要調用構造函數

還有一種特殊的例子,我們來看一下:

#include <iostream>
using namespace std;

class A
{
        double x;
        double y;
        public:
                A()                                            //無參構造函數
                {     cout<<"Construction 1"<<endl; }
                A(double xxx)                        //一個參數構造函數
                {     cout<<"Construction 2"<<endl; x = xxx;}
                A(double xx, double yy)      //兩個參數構造函數
                {     cout<<"Construction 3"<<endl; x = xx; y = yy;}
                void setx(int m =0)      {x = m;}
};

int main()
{
        A a[3] = {1, A(2,3)};           //定義了一個對象數組,第一個一個元素初始化,第二個兩個元素,第三個沒有初始化
        A *p[3] = {new A(3), new A, new A(4,5)};
 }
           

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

從上面可以看出,我們的對象數組是這個樣子進行初始化的。。。。。。。。。。。并且也是這個樣子調用new的。

其實總的來說:

對于對象數組的初始化:嗯嗯,好像隻能用這種方式:

A a[3] = {A(1), A(2,3)};
A *p[3] = {new A(3), new A, new A(4,5)};
           

嗯嗯,就是,這種方式:上面我們實作的方式是不一樣的,也就是說:當隻有一個參數的時候

A(1)和1是一樣的,,,,,,,,,,,

即就是:

A a = {1},A a(1),A a = {A(1)};      //三者是一樣的!!!!!
           

6,複制構造函數(Copy  Constructor)

              我們經常使用函數,傳遞各種各樣的參數,而我們今天要說的複制構造函數:就是把對象(注意是對象,不是

       對象的指針或者對象的引用)作為一個參數,并且傳遞給函數

              我們也知道,把參數傳遞給函數有三種方法:值傳遞,位址傳遞,傳引用。

              值傳遞時,傳送的是作為實際參數的對象的副本而不是實際對象本身。而且這個副本的内容是按位從原始參數

       那裡複制過來的,内容是相同的。

       當傳遞過來的時候,如果是類的對象呢???會不會觸發類的構造函數呢,,調用結束,會不會觸發析構呢???

        從程式看:

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //構造函數的實作
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析構函數的實作    
                void print()
                {     cout<<x<<endl;                        }
};

void f(A a)
{
        a.print();      
}

int main()
{
        A a(10);
        f(a);
        a.print();

        return 0;
}
           

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

從運作結果可以看到:A類的構造函數隻被調用了一次,發生在建立對象a的時候,而A類的析構函數卻被調用了兩次

這是為什麼呢???

        查閱書籍後發現:如同我們上面所提到的,把一個對象作為參數傳遞給函數時,同時建立了該對象的副本(這

        個副本将作為函數的參數)。也就是說:建立了一個新的對象。當函數結束時,作為函數的實際參數的副本将被

        銷毀,這産生了兩個有趣的問題。。。。。。。

        1,在建立對象的副本時有沒有調用構造函數

        2,在銷毀對象的副本時有沒有調用了析構函數

        首先,在調用函數時,程式建立了一個對象的副本作為形式參數,此時普通的構造函數并沒有被調用,而是調用

        了複制構造函數,複制構造函數定義了如何建立了一個對象的副本。。。。。如果一個類中沒有顯示的地定義類

        的複制構造函數,那麼C++将提供一個預設的複制構造函數。預設的複制構造函數将以按位複制的形式建立一個

        對象的副本,即建立一個與原對象一樣的副本。

        由于普通的構造函數通常用于初始化對象的某些成員,是以就不能調用普通的構造函數建立對象的副本,因為

        這樣産生的對象可能與現有的對象的狀态屬性不完全相同。很簡單,隻是因為當把一個對象傳遞給函數時,需要

        使用的是對象的目前狀态,而不是初始狀态。。。。

        其次,當函數結束時,由于作為參數的對象副本超出了作用域,是以它将被銷毀,進而調用了析構函數,這就是

        為什麼前面的程式中調用了兩次析構函數的原因,第一次函數f()參數超出了作用域,第二次是因為main函數

        中的對象a在程式結束時被銷毀。。。。

        綜上所述:當建立一個對象的副本作為函數參數時,普通的構造函數沒有被調用,所調用的構造函數是按位複制

        的預設複制構造函數。但是,當對象的副本被銷毀時(通常因為,函數傳回而超出了作用域),析構函數被調用

        ,如果析構函數是空的話,通常不會發生什麼問題,但是一般情況下:析構函數都要完成一些清理工作(釋放

        記憶體。。。。)

        假如,我們真的在複制構造函數裡面為一個指針變量配置設定了記憶體,在析構函數裡釋放了配置設定給這個指針所指向的

        空間,那麼我們可以想想,在把對象傳遞給函數到整個main函數傳回的時候,發生了什麼???

        首先,有一個對象的副本産生了,這個副本也有一個指針,,,它和原始對象的指針指向同塊記憶體空間,,函數

        傳回時,副本對象的析構函數被執行了,,,釋放了對象副本裡指針所執行的記憶體空間,但是這個記憶體空間對于

        原始對象來說,還是有用的啊,,,,(幹嘛就直接給釋放了呢???),就程式本身而言,這是一個錯誤,

        但是錯誤還是沒有停止,,,等到main函數結束的時候,原始對象的析構函數繼續被執行,,,對同一塊記憶體

        釋放兩次,這有可能加深錯誤!!!

        那麼,如何解決上面的問題呢???

        嗯嗯,可以用傳位址,傳引用,這樣卻是可以合理的解決這樣一個問題,但是,有的時候,我們不需要直接的改

        變外部變量啊,,,那如何處理呢????

        其實,這個時候,我們可以用複制構造函數來解決這個問題,複制構造函數是在産生對象副本的時候執行的,

        我們可以調用自己的複制構造函數。在自己的複制構造函數裡,我們可以申請一個新的記憶體空間來儲存那個

        構造函數所指向的内容。。。。這樣,在執行對象副本的時候,執行的就是剛剛申請的那個空間了。。。

       複制構造函數(拷貝構造函數)

X&
const X&
volatile X&
const volatile X&
//後面也可以跟其他的參數。。。。。。。。。。。。。
           

       如上,就是複制構造函數的幾種類型。。。。。。。。。

1, 當一個對象副本被作為參數傳遞給函數時              

display(y);
           

2, 當一個對象被另一個對象顯式的初始化時

my_string x = y;
           

3, 當建立一個臨時對象時(最常見的是:作為函數的傳回值)

y = func2();
           

          前面2中情況,對象y的引用将被傳遞給複制構造函數;在第三種情況下,函數func2()傳回的對象引用将被傳遞給

        y的複制構造函數。。。。

        關于對象之間的顯示初始化。。。

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //構造函數的實作
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析構函數的實作    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b = a; //  可以這樣初始化
        A c(a);   //  也可以這樣初始化
        return 0;
} 
           

        運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

         其實後面兩個的初始化調用的是複制構造函數,隻是在目前類中我們沒寫,調用的是預設,析構調用了 ,,,,

程式:

#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //構造函數的實作
                A(A & a)                                            //自定義的複制構造函數
                {     x = a.x;   cout<<"自定義的 Constructor"<<endl;}
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析構函數的實作    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b = a;
        A c(a); 
        return 0; 
}
           

         程式如上:我們寫了自定義的複制構造函數

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

為了明顯的區分開跟指派的差別,我們重載了一個指派運算符:

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //構造函數的實作
                A(A & a)                                            //自定義的複制構造函數
                {     x = a.x;   cout<<"自定義的 Constructor"<<endl;}
                
                A &operator = (A & a)                               //重載了一個指派運算符                                                      
                {     this->x = a.x;  cout<<"指派運算符的重載"<<endl;}
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析構函數的實作    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b(2); 
        b = a;
        return 0;
}
           

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

           1,上面我們知道了一些關于複制構造函數裡面的一些東西。。。。那麼我們有一些疑惑???

               複制構造函數可以重載嗎????

               可以的,因為上面我們提到了,複制構造函數的類型;;;;;;;;

#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //構造函數的實作
                A(A & a)                                            //自定義的複制構造函數
                {     x = a.x;   cout<<"自定義的 Constructor"<<endl;}
                A(A & a, int b)
                {     a.x = b;   cout<<"重載的複制構造函數"<<endl;  }
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析構函數的實作    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{ 
        A a(2); 
        A b(a, 3);
        return 0; 
}
           

          如上:我們重載了複制構造函數....

          運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

          2,拷貝構造函數中可以調用private成員變量嗎???

                從上面的程式中可以看到,這裡的調用無壓力,,,,可以直接調用,,,因為是一種特殊的構造函數

                操作的是自己的成員變量,不用考慮private。。。。。

          關于深拷貝,淺拷貝

          其實說白了:編譯器預設的拷貝構造函數其實就是一個淺拷貝,如同我們上面所提到的那樣,如果有指針變量的

話,那麼就會出錯,因為拷貝後兩個指針指向了同一塊空間,釋放兩次自然就會出錯。。。。而深拷貝,不但對指針

進行拷貝,也對指向的内容進行拷貝。。。。。。。拷貝後,是兩個指向不同位址的指針。。。。。。

1,那麼淺拷貝會出現什麼問題呢????

int main()
{
        A a(2);
        A b = a;   
//      A c(a);   上面這兩種情況都是可以的                                                                                                      
        return 0; 
}
           

       假設類有一個成員變量指針:char *p;

       1,淺拷貝隻是拷貝了指針,使得兩個指針指向同一個位址,這樣在對象塊結束,調用函數析構的時,會造成同一

              份資源析構2次,即delete同一塊記憶體2次,造成程式崩潰。

       2,淺拷貝使得b.p和a.p指向同一塊記憶體,任何一方的變動都會影響到另一方

       3,結果是會調用兩次析構函數,造成同一塊記憶體釋放兩次,明顯的錯誤

如下:

#include <string.h>
#include <iostream>
using namespace std;

class A
{
        char *p;
        public:
                A(char *s)             //構造函數
                {
                        cout<<"Constructor"<<endl;      
                        p = new char[strlen(s) + 1];
                        strcpy(p, s);
                }
                ~A()                   //析構函數
                {
                        if(p != NULL)
                        {
                                delete p;
                                p = NULL;       
                                cout<<"析構函數"<<endl; 
                        }
                }                                                                                                                               
};

int main()
{
        char p[] = "abc";
        A a(p);
        return 0;
}
           

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

一塊空間的多次釋放,自然而然的造成了錯誤。 ,,,,,,,,,,,,,,

深拷貝采用了在堆記憶體中申請新的空間來存儲資料,在我們重新書寫的複制構造函數裡我們重新為這個新的指針

        配置設定了一塊存儲空間了,,,,,,,,,,

#include <string.h>
#include <iostream>
using namespace std;

class A
{
        char *p;
        public:
                A(char *s)             //構造函數
                {
                        cout<<"Constructor"<<endl;      
                        p = new char[strlen(s) + 1];
                        strcpy(p, s);
                }
                A(A & a)
                {
                        cout<<"複制Constructor"<<endl;
                        int length = strlen(a.p);
                        p = new char[length + 1];
                        strcpy(p, a.p); 
                }
                ~A()                   //析構函數
                {
                        if(p != NULL)
                        {
                                delete p;
                                p = NULL;       
                                cout<<"析構函數"<<endl; 
                        }
                }
};

int main()
{
        char p[] = "abc";
        A a(p);
        A b = a;
        return 0;
}
           

可以看到:我們重新寫了複制構造函數,并且在其中重新配置設定了記憶體空間了。。。。

運作結果:

C++的簡單總結(複制構造函數,深拷貝,前拷貝,預設屬性)

繼續閱讀