1. 程式人生 > 其它 >建構函式體賦值和初始化列表的區別

建構函式體賦值和初始化列表的區別

技術標籤:c++

再談建構函式

1 建構函式體賦值

在建立物件時,編譯器通過呼叫建構函式,給物件中各個成員變數一個合適的初始值。

include<iostream>
using namespace std;
class Date
{
public:
	Date(int year_, int month_, int day_)
    {
	// 這只是一個賦值的操作, 並不算初始化 , 因為其可以執行很多次
		year = year_;
		month = month_;
		day = day_;
	// 後面還可以寫很多次
		year = 4;
		year = 4;
	}
    int get_year()
    {
        return year;
    }
private:
	int year;
	int month;
	int day;
};
int main()
{
    Date a(1,2,3);
    cout<<",year:"<<a.get_year()<<endl;
}
  • 雖然上述建構函式呼叫之後,物件中已經有了一個初始值,但是不能將其稱作為類物件成員的初始化,建構函式體中的語句只能將其稱作為賦初值,而不能稱作初始化。因為初始化只能初始化一次,而建構函式體內可以多次賦值

2 初始化列表

初始化列表:以一個冒號開始,接著是一個以逗號分隔的成員變數列表,每個"成員變數"後面跟一個放在括號中的初始值或表示式。 不寫的話也行, 相當於初始化的是一隨機值, 通過{}裡進行賦值;

  • 初始化列表只能寫在建構函式中(包括拷貝構造)
  • class Date{
    public:
    	//初始化列表: 成員變數初始化的地方,也是成員定義的地方
    	//           每一個成員變數只能出現一次
    	//   :成員變數(初始值或者初始化表示式), 後續的成員變數 
    	Date(int year, int month, int day)
    		: _year(year)
    		// , _year(y)  // 這個不行, 只能初始化一次
    		, _month(month)
    		// , _month(month + d) // 這就算是給了一個初始化的表示式
    		, _day(day){	
    
    		// 可以在括號裡來改變_year的值
    		_year = y;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    

1.每個成員變數在初始化列表中只能出現一次(初始化只能初始化一次)
2.類中包含以下成員,必須放在初始化列表位置進行初始化:

  • 引用成員變數
  • const成員變數
  • 自定義型別成員(該類沒有預設建構函式)
    如果不顯示初始化, 我們編譯器不知道該怎樣去初始化自定義型別成員

其他的成員變數不進行初始化 都是可以的, 但是上面這三個必須在初始化列表中進行初始化

#include <string>
#include<iostream>
using namespace std;
class A{
public:
	A(int a)
		:_a(a)
	{}
    int get_a_resut()
    {
        return _a;
    }
private:
	int _a;
};

class B{
public:
	B(int a, int ref)
	:_aobj(a) // 如果不初始化自定義型別的成員會報錯, 因為B中沒有自定義型別成員的預設構造
	,_ref(ref) // 引用型別必須在初始化列表中初始化,
	,_n(10) // const成員必須在初始化列表中初始化
	{}
    int get_a()
    {
       return _aobj.get_a_resut();
    }
	
private:
	// 這裡相當於是對成員變數的宣告, 而成員變數屬於物件, 物件只有在調建構函式時才初始化建立
	// 相當於我們的成員變數也是在物件初始化時被構造出來, 真正定義成員變數的地方就是我們的初始化列表
	A _aobj; // 這就是我們的自定義型別成員 沒有預設建構函式
	int& _ref; // 引用 必須在定義的時候初始化, 即可以在建構函式的初始化列表實現初始化
	const int _n; // const成員也必須在定義時(初始化列表中)初始化
};
int main()
{
    B b = B(2,3);
    cout<<b.get_a()<<endl;
    
}

3.儘量使用初始化列表初始化,因為不管你是否使用初始化列表,對於自定義型別成員變數 ,一定會先使用初始化列表初始化。

通過下面的函式呼叫發現, 結果列印顯示調了兩次Time類中的建構函式,

#include<iostream>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include <algorithm>
using namespace std;

class data
{
private:
	int year;
	int month;
	int day;
public:
    data(int yy, int mm, int dd)
    {
        year = yy;
        month = mm;
        day = dd;
    }

    void display()
    {
        cout<<"birthday:"<<year<<"-"<<month<<"-"<<day<<endl;

    }
};
class student
{
private:
    string name;
    data birthday;
public:
    student(string nn, int yy, int mm, int dd):birthday(yy, mm, dd)
    {
        name = nn;
    }
    void output()
    {
        cout<<"name:"<<name<<endl;
        birthday.display();
    }
};

int main()
{
    student s("張三", 2000, 12, 03);
    s.output();
    return 0;
}

可以發現我們在函式中只顯式寫了一個構造, 就是在Date類的建構函式體內部, 但是卻執行了兩次構造, 原因就在於自定義成員首先在初始化列表呼叫了一次預設構造, 獲得預設值(1-1-1), 然後又在Date的建構函式體中先呼叫Time的建構函式獲得初值, 再執行了一次賦值操作, 將值賦給了_t.

4.成員變數在類中宣告次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先後次序無關,

可以猜一下下面這個test函式指向完後, a和b分別是什麼值???

include<iostream>
using namespace std;
class Test
{
    public:
        //成員的初始化順序和宣告順序一致,與其在初始化列表中的順序沒有關係
        //所以在寫初始化列表時, 儘量讓初始化順序和宣告順序保持一致, 避免造成錯誤
        Test(int b):_b(b),_a(2*_b)
        {

        }
        int get_a()
        {
                cout<<"_a:"<<_a<<endl;
                return _a;
        }
        int get_b()
        {
                cout<<"_b:"<<_b<<endl;
                return _b;
        }
     private:
        int _a;
        int _b;
};

int  main()
{
    Test t(100);
    cout<<""<<t.get_a()<<endl;
    cout<<""<<t.get_b()<<endl;
}

最終上面函式執行完 _a是隨機值, _b = 100; 按理_a應該是2倍的_b, 是為什麼呢, _a為什麼沒有被初始化呢??

  • 原因在於先初始化的並不是_b, 而是_a的值, 雖然_a是2倍的_b, 但是在初始化_a之前, _b的值並不知道, _b是一隨機值, 導致_a的值就是隨機值. 當完成了_a的初始化後, 才走的_b的初始化, _b給的是100, 才導致_b才變成了正常值(100),
  • 這裡不管先初始化_b還是先初始化_a, 最終都預設先初始化_a再初始化_b, 在於我們宣告的順序.

所以在寫初始化列表時, 儘量讓初始化順序和宣告順序保持一致, 避免造成錯誤