從初始化列表和建構函式談C 的初始化機制

2021-08-02 19:30:00 字數 4583 閱讀 8597

綜合而言,c++中類的初始化操作有四個部分組成:

1.初始化列表:所有類非靜態資料成員都可以在這裡初始化,

所有類靜態資料成員都不能在這裡初始化
2.建構函式體:對於類非靜態資料成員:

const型成員不能在這裡初始化

引用型成員不能在這裡初始化

沒有預設建構函式的成員不能在這裡初始化

對於類靜態資料成員:

可以在這裡修改可修改的靜態成員,但靜態成員必須已經在類外部初始化

3.類外初始化:除乙個特例外,所有類static資料成員必須在這裡初始化,

特例是類static const int資料成員可以在這裡初始化,也可以在成員的宣告處初始化
4.類中宣告時直接賦值:類static const int資料成員可以選在這裡初始化。

直接羅列這樣的規則,是我國大多數教科書的展開方式,記得經典的三部曲嗎?

(1)定義

(2)定理

(3)例題

至於來龍去脈就只能靠我們這些學子的悟性了。何其苦載!事實證明需要理清

一些定理和思想的來龍去脈往往需要比這個定理更加廣闊的知識和視野,讓學生拿

著空洞的課本靠領悟?(不要意思,又吐槽了)

讓我們從一段簡單的**開始:

[cpp] view plain copy

01.class a

07.};

對很多人而言,這是什麼直觀寫法,為什麼就錯了呢?其實這本質上相當於寫:

[cpp] view plain copy

01.const int x;

02.x = 1;

所以我們只能按如下方式宣告其初始化:

[cpp] view plain copy

01.class a

06.};

再來看一段簡單的**:

[html] view plain copy

01.class a

07.};

同理這這本質上相當於寫:

[cpp] view plain copy

01.int &x;

02.x = k;

所以我們只能按如下方式宣告其初始化:

[cpp] view plain copy

01.class a

06.};

有了上面兩個簡單例子作為引子,我們開始進一步討論c++初始化的全過程。
其實我相信很多人還是懷著這樣一些疑問「寫在初始化列表裡就相當於int &x=k;嗎?」

且讓我們來看看c++類的初始化的全過程:

(1)靜態成員初始化階段:所有類的靜態成員應該都是在這個階段初始化的。

注意初始化的順序,就是操作語句的順序,例如你有乙個test類:
[cpp] view plain copy

01.int test::x = 2;

02.int test::y = 3;

需要注意的是2點,一是初始化語句不再需要static關鍵字,二是執行順序就是

語句的順序,這裡是先初始化t1,再初始化t2。執行順序的問題在靜態成員是類的時候

就關係到建構函式的呼叫順序了。另外需要注意的是,這些靜態成員的初始化在任何具

體例項被建立前就已經完成了。

(2)例項初始化列表工作階段:

需要說的是,在使用者使用new或者其他方法開始構建例項的時候,第一步首先是向

作業系統申請記憶體,初始化列表是在申請成功後才開始工作的。

然後,根據非靜態成員的宣告順序開始執行如下操作:

1.如果該成員沒有出現在初始化列表中:

1)如果是內建非const且非引用型別,不設定初值

2)如果是const型別,報錯,必須在這裡給定初值

3)如果是引用型別,報錯,必須在這裡給定初值

4)如果是class型別,就呼叫預設建構函式,進行初始化操作

2.如果該成員出現在初始化列表中:

1)如果是內建型別,就按初始化列表指定的值設定初值

2)如果是const型別,就按初始化列表指定的值設定初值

3)如果是引用型別,就按初始化列表指定的值設定初值

4)如果是class型別,就呼叫初始化列表指定的建構函式進行初始化操作

(3)計算階段:

根據建構函式的函式體進行賦值操作,或者修改操作,

在這裡,靜態和非靜態資料都可以賦值和修改

下面用一段**來測試這個過程:

[cpp] view plain copy

01.class test1 /這就取消了test1的預設建構函式/

08.

09.};

10.

11.

12.

13.class test2

80.

81.};

82.

83.//int test2::a = 1; //error:非靜態資料成員不能在其類的外部定義

84.

85.//int test2::b = 2; //error:非靜態資料成員不能在其類的外部定義

86.

87.int test2::c = 3; //如果沒有這句,會出現無法解析的外部符號public:static int a::c

88.

89.//int test2::d = 4; //error: int與宣告const int不相容

90.

91.//int const test2::d = 4; //和在類宣告裡面直接寫賦值等價

92.

93.int x_g = 5; /這個全域性變數主要使用者後續的靜態成員賦值/

94.

95.int x_h = 6; /這個全域性變數主要使用者後續的靜態成員賦值/

96.

97.test1 x_t3(7);/這個全域性變數主要使用者後續的靜態成員賦值/

98.

99.test1 x_t4(8);/這個全域性變數主要使用者後續的靜態成員賦值/

100.

101.

102.int& test2::g = x_g;

103.

104.const int& test2::h = x_h;

105.

106.test1 test2::t3 = x_t3;

107.

108.const test1 test2::t4 = x_t4;

前面講了這麼多具體的細節,我個人建議按如下簡化規則來記憶:

(1)所有static成員變數在類外初始化(不管它是const,是引用,還是沒預設建構函式的物件)

(2)普通成員變數,是const,是引用,是沒預設建構函式的,必須在初始化列表初始化

(3)普通成員變數,需要複雜運算的初始化變數,應該在建構函式內初始化,否則盡量在

初始化列表中初始化。
另外補充2個小點:

(1)初始化列表的使用可能提高效能

[cpp] view plain copy

01.class test3

14.

15. test3(test3 &t3)

22.

23. test3& operator=(test3 &t)

32.

33.

34.

35. ~test3()

36.

37.};

38.

39.

40.

41.class test4

50.

51.

52. test4( test3 &t3) ;

57.

58.};

(2)成員是按照他們在類中出現的順序進行初始化的,而不是按照他們在初始化列表出現的順序初始化的

參考如下**
[cpp] view plain copy

01.struct foo

02.; // ok, 先初始化i,後初始化j

06.};

再看下面的**
[cpp] view plain copy

01.struct foo

02. // i值未定義

06.};

這裡i的值是未定義的因為雖然j在初始化列表裡面出現在i前面,但是i先於j定義,所以先初始化i,但i由j初始化,此時j尚未初始化,所以導致i的值未定義。所以,乙個好的習慣是,按照成員定義的順序進行初始化。也就是說相當於實際執行順序是:

i(j);

j(x);

所以會出現錯誤。

其中為什麼要使用初始化列表是因為:

1.常量成員,因為常量只能初始化不能賦值,所以必須放在初始化列表裡面;

2.引用型別,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裡面;

沒有預設建構函式的類型別,因為使用初始化列表可以不必呼叫預設建構函式來初始化,而是直接呼叫拷貝建構函式初始化。

建構函式初始化列表和初始化函式

其實並沒有所謂的初始化函式的概念,本文中的初始化函式只是說明在函式體內進行賦值。而初始化列表才是真正意義上的物件初始化。使用初始化列表效率會高一點。c 規定,物件的成員變數的初始化動作發生在進入建構函式本體之前。在建構函式體內只是賦值,並不是初始化。請看下面這個栗子 class base publi...

C 建構函式 ,初始化列表

c 中的類的建構函式 1.如果類中沒有定義建構函式,編譯器將生成乙個預設建構函式,這個預設建構函式會呼叫類中所有成員的預設建構函式,但不會對如int,double的基本資料型別做初始化 2.類中可以定義多個建構函式,但每個建構函式應該有不同的引數實現 3.預設建構函式必須定義的情況,當需要定義乙個物...

C 建構函式初始化列表

從概念上講,可以認為建構函式分兩個階段進行 1.初始化階段 2.普通的計算階段。計算階段由建構函式函式體中的所有語句組成 不管成員是否在建構函式初始化列表中顯示初始化,類型別的資料成員總是在初始化階段初始化。初始化發生在計算階段的開始之前。建議 使用建構函式初始化列表 注 必須對任何const或引用...