C++中構(gòu)造函數(shù)的初始化順序說明
關(guān)鍵規(guī)則
- 如果派生類有基類(單繼承或多繼承),基類的構(gòu)造函數(shù)會(huì)首先被調(diào)用。
對基類進(jìn)行處理
- 多繼承時(shí),按照派生類繼承列表中聲明的順序(從左到右)依次調(diào)用基類的構(gòu)造函數(shù)。
- 如果有虛繼承,虛基類的構(gòu)造函數(shù)優(yōu)先于非虛基類調(diào)用,且只調(diào)用一次。
- 虛基類只在最遠(yuǎn)派生類中進(jìn)行處理,并且只有最遠(yuǎn)派生類調(diào)用,其他虛繼承的派生類調(diào)用被忽略,并且只執(zhí)行一次
對成員對象進(jìn)行處理
- 在基類構(gòu)造函數(shù)調(diào)用完成后,派生類中聲明的成員對象的構(gòu)造函數(shù)會(huì)被調(diào)用。
- 成員對象的構(gòu)造順序遵循它們在類中聲明的順序(而不是初始化列表中的順序)。
- 如果成員對象有自己的構(gòu)造函數(shù),C++ 會(huì)根據(jù)初始化列表(如果提供)或默認(rèn)構(gòu)造函數(shù)來調(diào)用。
派生類自己的構(gòu)造函數(shù)
- 最后,派生類自己的構(gòu)造函數(shù)體被執(zhí)行。
特殊情況
如果父類構(gòu)造函數(shù)含有虛函數(shù)調(diào)用
- 在父類的構(gòu)造函數(shù)中調(diào)用虛函數(shù),還是會(huì)執(zhí)行父類的構(gòu)造函數(shù),不會(huì)跑到子類中去,即使有vitual,因?yàn)榇藭r(shí)父類都還沒有構(gòu)造完成,子類也就還沒有構(gòu)造。
#include <iostream>
using namespace std;
class A{
public:
A ():m_iVal(0){test();}//這里的test會(huì)調(diào)用父類的virtual void func()
virtual void func() { std::cout<<m_iVal<<' ';}
void test(){func();}
public:
int m_iVal;
};
class B : public A{
public:
B(){test();}//這里的test會(huì)調(diào)用父類的test()進(jìn)而通過指針匹配
virtual void func(){
++m_iVal;
std::cout << m_iVal << ' ';
}
};
int main(int argc ,char* argv[]){
A*p = new B;
p->test();
return 0;
}
輸出結(jié)果
0 1 2
虛函數(shù)調(diào)用規(guī)則:
- 構(gòu)造函數(shù)中:綁定到當(dāng)前構(gòu)造的類版本。
- 構(gòu)造完成后:動(dòng)態(tài)綁定到實(shí)際對象類型。
執(zhí)行流程:
- A::A() → test() → A::func() → 輸出0(vtable指向A)。
- B::B() → test() → B::func() → m_iVal++,輸出1(vtable指向B)。
- p->test() → B::func() → m_iVal++,輸出2。
vtable切換:
- 基類構(gòu)造:指向A。
- 基類完成后,進(jìn)入B::B()前:指向B。
輸出:
0 1 2。
核心結(jié)論
- 構(gòu)造函數(shù)中虛函數(shù)調(diào)用取決于當(dāng)前類類型,非最終類型。
- vtable在基類構(gòu)造后、派生類構(gòu)造函數(shù)體前更新。
- 注意:在執(zhí)行構(gòu)造函數(shù)體之前,可以認(rèn)為構(gòu)造函數(shù)**已經(jīng)完全完成了相應(yīng)對象的初始化工作,**在C++的實(shí)現(xiàn)中,虛函數(shù)表的切換發(fā)生在進(jìn)入派生類構(gòu)造函數(shù)體之前,而不是等到整個(gè)構(gòu)造函數(shù)結(jié)束。
關(guān)于拷貝構(gòu)造函數(shù)的初始化問題
- 在初始化時(shí)如果對象不存在,且沒有聲明explict禁用賦值操作
- 編譯器默認(rèn)調(diào)用拷貝構(gòu)造函數(shù)
#include<iostream>
using namespace std;
class MyClass {
public:
MyClass(int i = 0) { // 構(gòu)造函數(shù)
cout << i;
}
MyClass(const MyClass &x) { // 拷貝構(gòu)造函數(shù)
cout << 2;
}
MyClass &operator=(const MyClass &x) { // 賦值運(yùn)算符
cout << 3;
return *this;
}
~MyClass() { // 析構(gòu)函數(shù)
cout << 4;
}
};
int main() {
MyClass obj1(1), obj2(2); // 創(chuàng)建 obj1 和 obj2
MyClass obj3 = obj1; // 創(chuàng)建 obj3 并初始化
return 0;
}
MyClass obj3 = obj1;
由于obj3為被創(chuàng)建,那么調(diào)用拷貝構(gòu)造函數(shù),稱為復(fù)制初始化
MyClass obj3; // 先默認(rèn)構(gòu)造 obj3 = obj1; // 再賦值
這時(shí)會(huì)導(dǎo)致調(diào)用賦值運(yùn)算符
拷貝構(gòu)造函數(shù)的什么時(shí)候被調(diào)用呢?
| 場景 | 示例代碼 | 說明 |
|---|---|---|
| 對象初始化 | MyClass b = a;或者M(jìn)yClass b(a); | 用已有對象初始化新對象 |
| 按值傳遞參數(shù) | void func(MyClass x); | 函數(shù)參數(shù)創(chuàng)建副本 |
| 按值返回對象 | MyClass func() { … } | 返回局部對象(可能被優(yōu)化,現(xiàn)如今的C++編譯器普遍采用了RVO返回值優(yōu)化導(dǎo)致返回時(shí)候不會(huì)進(jìn)行拷貝) |
| 容器操作 | vec.push_back(a); | 插入對象到容器 |
| 顯式調(diào)用 | new MyClass(a); | 動(dòng)態(tài)分配時(shí)拷貝 |
關(guān)于子類和父類的虛函數(shù)問題
如果父類函數(shù)不是 virtual,子類將其聲明為 virtual:
- 對基類無影響,基類調(diào)用仍是靜態(tài)綁定。
- 從子類開始,函數(shù)成為虛函數(shù),后續(xù)派生類可以實(shí)現(xiàn)多態(tài),(即便是后續(xù)沒有加virtual關(guān)鍵字也是多態(tài))
#include <iostream>
class Base {
public:
void foo() { // 非虛函數(shù)
std::cout << "Base::foo()" << std::endl;
}
};
class Derived : public Base {
public:
virtual void foo() { // 子類聲明為虛函數(shù)
std::cout << "Derived::foo()" << std::endl;
}
};
class GrandDerived : public Derived {
public:
void foo() { // 重寫 Derived 中的虛函數(shù)
std::cout << "GrandDerived::foo()" << std::endl;
}
};
int main() {
Base* b1 = new Derived();
b1->foo(); // 輸出 "Base::foo()"
Derived* d1 = new Derived();
d1->foo(); // 輸出 "Derived::foo()"
Base* b2 = new GrandDerived();
b2->foo(); // 輸出 "Base::foo()"
Derived* d2 = new GrandDerived();
d2->foo(); // 輸出 "GrandDerived::foo()"
delete b1; delete d1; delete b2; delete d2;
return 0;
}
父類函數(shù)非虛,沒有多態(tài)
- 因?yàn)?Base::foo() 不是虛函數(shù),通過 Base* 指針調(diào)用 foo() 時(shí)**,總是執(zhí)行 Base::foo()**,不會(huì)發(fā)生運(yùn)行時(shí)多態(tài)。
- 即使 Derived::foo() 被聲明為 virtual,它對 Base 的函數(shù)沒有影響,因?yàn)槎鄳B(tài)性需要從基類開始啟用。
子類聲明為虛,影響后續(xù)繼承
- 在 Derived 中將 foo() 聲明為 virtual,意味著從 Derived 開始,這個(gè)函數(shù)變成了虛函數(shù)。
- 后續(xù)的派生類(如 GrandDerived)可以重寫 Derived::foo(),并通過 Derived* 或 Derived& 調(diào)用時(shí)實(shí)現(xiàn)多態(tài)。
- 但這種多態(tài)僅限于 Derived 及其子類,不追溯到 Base。
隱藏而非重寫
- Derived::foo() 只是隱藏了 Base::foo(),而不是重寫它。
- 當(dāng)通過 Base* 調(diào)用時(shí),調(diào)用的仍然是 Base::foo(),因?yàn)?Base::foo() 不是虛函數(shù)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++?中的?JSON?序列化和反序列化及結(jié)構(gòu)體與枚舉類型的處理方法
在?C++?編程中,處理?JSON?數(shù)據(jù)是一項(xiàng)常見任務(wù),特別是在需要與其他系統(tǒng)或前端進(jìn)行數(shù)據(jù)交換時(shí),本文將詳細(xì)介紹如何使用?nlohmann::json?庫對結(jié)構(gòu)體和枚舉類型進(jìn)行序列化和反序列化,感興趣的朋友一起看看吧2024-11-11
C++數(shù)據(jù)結(jié)構(gòu)AVL樹全面分析
今天的這一篇博客,我要跟大家介紹一顆樹——AVL樹,它也是一顆二叉搜索樹,它就是在二叉搜索樹中加了一個(gè)平衡因子的概念在里面,下面我就來和大家聊一聊這棵樹是個(gè)怎么樣的樹2021-10-10
Qt實(shí)現(xiàn)模糊匹配功能的實(shí)例詳解
對于瀏覽器的使用,我想大家一定不會(huì)陌生吧,輸入要搜索的內(nèi)容時(shí),會(huì)出現(xiàn)相應(yīng)的匹配信息。本文就來用Qt實(shí)現(xiàn)模糊匹配功能,感興趣的可以了解一下2022-10-10
C語言 數(shù)據(jù)結(jié)構(gòu)之連續(xù)存儲(chǔ)數(shù)組的算法
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)之連續(xù)存儲(chǔ)數(shù)組的算法的相關(guān)資料,需要的朋友可以參考下2017-01-01

