建構函式體賦值和初始化列表的區別
阿新 • • 發佈:2021-01-02
技術標籤: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, 在於我們宣告的順序.
所以在寫初始化列表時, 儘量讓初始化順序和宣告順序保持一致, 避免造成錯誤