C 默認構造函數 拷貝構造函數

1、默認構造函數

如果沒有爲類提供任何構造函數,那麼編譯器將自動成一個默認的無參構造函數。一旦用戶爲類定義了構造函數,哪怕只是一個,那麼編譯器將不再生成默認的構造函數。即當且僅當沒有定義任何構造函數時,編譯器纔會提供默認構造函數。

1.1自定義默認構造函數的方式

1、給已有的構造函數的所有參數提供默認值;(推薦此種)

class People{
    public:
        People(string n="", int a= -1): name(n), age(a){} // 提供了一個默認 構造函數
    private:
        string name;
        int age;
};

2、通過函數重載來定義另一個構造函數,先提供一個沒有參數的構造函數,再在其基礎上進行重載;

class People{
    public:
        People(){} // 提供了一個默認 構造函數
        People(string n, int a): name(n), age(a){} // 普通構造函數重載
    private:
        string name;
        int age;
};

注:由於只有一個默認構造函數,因此不能同時使用上述兩種方式定義默認構造函數;比如People p1;將出現錯誤

若沒有提供默認函數,則定義People p2;將出現錯誤;

3、在C++11中使用=defalut來要求編譯器生成默認構造函數,=default既可以和聲明一起出現在類的內部也可以出現在類外部的函數的定義上

class A{
    private:
        int a;
        char b;
    public:
        A() = default;
        A(int a1, char b1) : a(a1), b(b1){};//定義默認構造函數請對其進行重載;
};

1.2爲什麼需要默認構造函數呢?

1、只有當類沒有聲明任何構造函數時,編譯器纔會自動地生成默認構造函數。

2、對於某些類來說,合成的默認構造函數可能執行錯誤的操作。。含有內置類型或複合類型成員的類應該在類的內部初始化這些成員,或者定義一個自己的默認構造函數。否則,用戶在創建類的對象時就可能得到未定義的值。

3、編譯器不能爲某些類合成默認的構造函數。。如果類中包含一個其他類類型的成員且這個成員的類型沒有默認構造函數,那麼編譯器將無法初始化該成員。

請看ANSYS公司經典例題

一下代碼若存在bug請改正,改正之後在給出輸出值?
class A{
    public:
        A(int data):data_(data){    
            cout << "Constructor:" << data_ << endl;
        };
        void func(int i){
            data_ += i;
            cout << "func:" << data_ <<endl;
        }
        void func(int i) const{
            data_ = i + 1;   
            cout << "func const:" << data_ <<endl;
        }
    private:
        int data_;    
};
class B : public A{
};

int main(){
    // 不產生臨時變量,直接調用構造函數!!!
    // 用一個臨時對象來初始化一個新對象時,編譯器一般會優化爲直接使用臨時對象的參數來創建新對象,
    // 即const A aa(3); 實際上不會生成臨時對象。
    const A aa = A(3);// 不產生臨時變量,直接調用構造函數!!!
    //A aa(10);
    aa.func( 5 );
    
    B bb;
    bb.func(5); 
    return 0;   
}

解析:

// 錯誤1:const修飾 不能改變數據成員
// 錯誤2: bb創建時先調用基類構造, 發現 A 缺少默認構造函數
// 正確輸出如下
Constructor:3
func const:3
func:5

 

2、拷貝構造函數

2.1拷貝構造函數的定義

若構造函數的第一個參數是自身類類型的引用,且任何額外參數都有默認值,則此構造函數是拷貝構造函數。

如果不顯示定義的話,編譯器默認爲我們自動定義拷貝函數(淺拷貝);

1、如果類帶有指針成員變量,並有動態內存分配,則它必須有一個拷貝構造函數;

2、拷貝構造函數的首個形參是T&、const T&、volatile T&、const volatile T&且無其他參數,或任何額外的參數都必須帶有默認實參;(注:const 爲可選項,但在Dev C++5.11中必須加上)

class Foo()
{
    Foo(); 
    Foo(const Foo& obj);
    //...
}

 

2.2拷貝構造函數類型

拷貝構造函數分爲淺拷貝和深拷貝。

淺拷貝:指的是在對象(賦值)時,僅僅對對象中的數據成員的值簡單的賦值給另一個同一類對象應的數據成員,編譯器提供的默認拷貝構造函數默認賦值運算符重載函數( operator=() ) 執行的是淺拷貝。

深拷貝:對於對象中動態成員(申請了動態內存),就不能僅僅簡單地賦值了,而應該重新動態分配空間

#include<iostream>
#include<cstring>
using namespace std;

class C_person{
    public:
        C_person(char *p, int age = -1, int height = -1)//構造函數
        {
            cout << "調用構造函數" << endl;
            this->height = height;
            
            p_age = new int;//--------------------存在分配內存!!!
            *p_age = age;

            p_name = new char[strlen(p) + 1];//注意此處和使用sizeof是不同
            strcpy(p_name, p);
        }     
        C_person(const C_person& obj)//拷貝構造函數
        {
            cout << "調用--拷貝構造--函數!!!" << endl;
            this->height = obj.height;

            //淺拷貝!!!
            p_age = new int;
            *p_age = *obj.p_age;

            //深拷貝 !!!
            if(p_name != NULL)
                delete p_name;//斷開原有的連接
            p_name = new char[strlen( obj.p_name ) + 1];//重新分配一樣的大小
            strcpy(p_name, obj.p_name);//將原來內存中的內容複製一份到新開闢的內存中去
        }
         
        void print_info()
        {
            cout <<" 姓名:"<< p_name
                 <<" 年齡:"<< (*p_age)
                 <<" 身高:"<< height << endl;
        }
        ~C_person()//析構函數
        {
            cout << "析構函數釋放\n\n";
            delete p_age;
            delete []p_name;    
        }
    private:
        char *p_name; //------------------------------存在指針!!!        
        int *p_age;
        int height;
};

int main()
{
    C_person *p_person = new C_person("xufangakjgksdgh",10 , 799);
    p_person->print_info();    
    delete p_person;//此形式只有使用了delete才執行析構函數  
    
    C_person P1("xufang",20, 188);//使用直接初始化
    P1.print_info();
    
    C_person P2 = P1;//使用拷貝初始化
    P2.print_info();

    return 0;
}

2.3拷貝構造函數發生的情況

若存在類 A,則有:

1、一個對象需要通過另外一個對象進行初始化。

A a;
A b(a);或 A b = a; // 調用拷貝構造函數

A a;
A b(1,2,3);
a = b; // 這是賦值操作啊!!調用默認賦值運算重載函數

2、一個對象以值傳遞(指針和引用不行)的方式傳入函數體

void func( A x );
int main(){
    A a;
    func( a ); // 
}

3、一個對象以值傳遞的方式從函數返回

A func( A x );
int main(){
    A a;
    A b = func( a );
}

拷貝構造函數經常會和 賦值構造函數混淆,,下面是一個綜合例子,,可以加深大家對這兩這着的區別,,特別要注意對象的定義、聲明、初始化與賦值等等一系列名詞的區別。

#include <iostream>
using namespace std;


class Data {
public:
    Data(){
        cout << "default constructor run...\n";
    }
    Data(int _years, int _months, int _days):years(_years),months(_months),days(_days){
        cout << "user constructor run..." << endl;
    }
    Data(const Data& d) //拷貝構造函數
    {
        cout << "copy constructor run..." << endl;
        this->years = d.years;
        this->months = d.months;
        this->days = d.days;
    }        
    Data& operator = (const Data& d) // = 運算符重載
    {
        cout << "operator=() run..." << endl;
        this->years = d.years;
        this->months = d.months;
        this->days = d.days;
        return *this;
    }        
    
    int get_years(){return this->years;}
    int get_months(){return this->months;}
    int get_days(){return this->days;}
    void print()const{
        cout << years<< "年"<< months<< "月"<< days<< "日"<<endl<<endl;
    }
private:
    int years;
    int months;
    int days;
};


void print_class(Data d){
    cout << d.get_years()<<"-"<<d.get_months()<<"-"<< d.get_days()<<endl<<endl;
}

Data test(Data d){
    Data res = d;
    return res;
}


int main()
{
    // 初始化
    Data data1(2019,9,22);
    data1.print();
    
    Data a(data1);  // 一、調用拷貝構造函數
    a.print();    
    Data b = data1; // 一、調用拷貝構造函數
    b.print();
    
    Data c = Data(2020,2020,2020);// 由編譯器優化爲Data c(2020,2020,2020);不會產生臨時變量
    c.print();
          
    // 賦值操作
    Data d;            // 定義
    d = data1;      // 賦值:調用 = 運算符重載函數 (a 爲調用者,data1爲參數,相當於a.operator=(data1);)
    d.print();
  
    Data data2(20,20,20);
    print_class(data2);         // 二、調用拷貝函數


    Data data3 = test(data2);    // 三、調用拷貝函數2次
    print_class(data3);         // 調用拷貝函數1次
    return 0;
}

接下來再看一道題,,請問輸出是什麼?,,歡迎評論區留言。。一起學習進步!!

struct C{
    C(){cout << "1 ";}// 構造函數
    C(const C& other){cout << "2 ";}// 拷貝構造函數
    C& operator=(const C& other){cout << "3 "; return *this;}// 賦值運算符重載
};
void main_5()
{    
    C c1;
    C c2 = c1;
    cout << endl;
    C c3;
    c3 = c2;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

歡迎大家加C/C++ Linux 技術棧開發羣:786177639,一起交流學習

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章