C++繼承和多態(tài)的用法解讀
繼承
繼承的概念
繼承機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保 持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱派生類。
繼承呈現(xiàn)了面向?qū)ο?程序設(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類設(shè)計(jì)層次的復(fù)用。
繼承的定義
定義格式

繼承關(guān)系和訪問(wèn)限定符


繼承基類成員訪問(wèn)方式的變化

基類是 private ,這種情況下,無(wú)論以何種方式繼承,對(duì)于基類私有的部分在子類中都是不可見(jiàn)的,這里的不可見(jiàn)指的是無(wú)法訪問(wèn),但仍然繼承下來(lái)了,只是無(wú)法使用而已。但是可以通過(guò)父類提供的方法進(jìn)行間接的訪問(wèn)和使用。
如圖:(Print 方法在上圖父類中有寫(xiě))如圖:

對(duì)于父類私有成員,子類無(wú)論哪種繼承都無(wú)法訪問(wèn),對(duì)于父類其他成員,繼承后被哪種訪問(wèn)限定符修飾取決于父類成員訪問(wèn)限定符和繼承方式中權(quán)限小的那一個(gè)。
從繼承這里我們也可以看出 protect 的意義:正是因?yàn)橛辛死^承,protect 才有意義,對(duì)于父類不想讓外界訪問(wèn)也不想讓子類訪問(wèn)的成員,父類可以用 private 修飾,對(duì)于父類不想讓外界訪問(wèn),但想讓子類訪問(wèn)的成員可以用 protect 修飾。
使?關(guān)鍵字 class 時(shí)默認(rèn)的繼承?式是private,使? struct 時(shí)默認(rèn)的繼承?式是public
基類和派生類對(duì)象賦值轉(zhuǎn)換
派生類對(duì)象可以賦值給基類的對(duì)象 / 基類的指針 / 基類的引用。這里有個(gè)形象的說(shuō)法叫切片 或者切割。寓意把派生類中父類那部分切來(lái)賦值過(guò)去。
基類對(duì)象不能賦值給派生類對(duì)象。
基類的指針或者引用可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針或者引用。但是必須是基類 的指針是指向派生類對(duì)象時(shí)才是安全的。
子賦值給父:向上轉(zhuǎn)換(可以)
父賦值給子:向下轉(zhuǎn)換(不可以)
不會(huì)產(chǎn)生臨時(shí)變量,將子類中父親的切片出來(lái)拷貝給父類
不能向下轉(zhuǎn)換原因
根本問(wèn)題:基類指針/引用可能并不實(shí)際指向派生類對(duì)象
派生類通常比基類有更多成員,錯(cuò)誤向下轉(zhuǎn)型會(huì)導(dǎo)致訪問(wèn)不存在的數(shù)據(jù):

基本繼承示例代碼
#include<iostream>
using namespace std;
// 基類 Person
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年齡
};
// 公有繼承 Person
class Student : public Person
{
protected:
int _stuid; // 學(xué)號(hào)
};
// 公有繼承 Person
class Teacher : public Person
{
protected:
int _jobid; // 工號(hào)
};
int main()
{
Person p;
Student s;
// 賦值兼容轉(zhuǎn)換(切割/切片)
Person p1 = s; // 派生類對(duì)象賦值給基類對(duì)象
Person& rp = s; // 基類引用引用派生類對(duì)象
rp._name = "張三"; // 通過(guò)引用修改派生類中的基類部分
Person* ptrp = &s; // 基類指針指向派生類對(duì)象
ptrp->_name = "李四"; // 通過(guò)指針修改派生類中的基類部分
return 0;
}
學(xué)生和教師類繼承了人這個(gè)類,人這個(gè)類中所有的東西在學(xué)生和教師類里已經(jīng)都具有了,需要注意的是成員變量雖然繼承過(guò)來(lái)了,但各自的對(duì)象都獨(dú)立有這樣一份成員變量,使用起來(lái)互不影響,而對(duì)于繼承過(guò)來(lái)的成員方法都使用同一份(構(gòu)造函數(shù)除外)。
繼承中的作用域
1. 在繼承體系中基類和派生類都有獨(dú)立的作用域。
2. 子類和父類中有同名成員,子類成員將屏蔽父類對(duì)同名成員的直接訪問(wèn),這種情況叫隱藏, 也叫重定義。(在子類成員函數(shù)中,可以使用 基類::基類成員 顯示訪問(wèn))
3. 需要注意的是如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
4. 注意在實(shí)際中在繼承體系里面最好不要定義同名的成員。
- 隱藏/重定義:子類和父類有同名成員,子類隱藏父類成員(就近原則)
- 重載:同一個(gè)作用域
- 隱藏:父子類域中函數(shù)名相同
- 派生類不能直接在初始化列表中初始化基類的成員變量,必須通過(guò)基類的構(gòu)造函數(shù)來(lái)初始化基類成員
成員隱藏(重定義)示例
class Person
{
public:
void fun()
{
cout << "Person::func()" << endl;
}
protected:
string _name = "小李子"; // 姓名
int _num = 111; // 身份證號(hào)
};
// 派生類 Student
class Student : public Person
{
public:
// 隱藏了父類的 fun() 函數(shù)
void fun()
{
cout << "Student::func()" << endl;
}
void Print()
{
cout << "姓名:" << _name << endl;
cout << _num << endl; // 訪問(wèn)派生類的 _num
cout << Person::_num << endl; // 訪問(wèn)基類的 _num
}
protected:
int _num = 999; // 學(xué)號(hào) (隱藏了基類的 _num)
};
int main()
{
Student s;
s.Print();
s.fun(); // 調(diào)用派生類的 fun
s.Person::fun(); // 顯式調(diào)用基類的 fun
return 0;
}
派生類的默認(rèn)成員函數(shù)
6個(gè)默認(rèn)成員函數(shù),“默認(rèn)”的意思就是指我們不寫(xiě),編譯器會(huì)變我們自動(dòng)生成一個(gè),那么在派生類 中,這幾個(gè)成員函數(shù)是如何生成的呢?
1. 派生類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員。如果基類沒(méi)有默認(rèn) 的構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
class person
{
public:
person()
:_name("張三")
{}
void Print()
{
cout << "person" << _name << endl;
}
protected:
int _age;
string _name;
};
class student :public person
{
public:
student()
:person()
,_num(0)
{}
void Print()
{
cout << "student" << _name <<_num<< endl;
}
protected:
int _num;
};class Base {
protected:
int _x;
public:
Base(int x) : _x(x) {} // 基類構(gòu)造函數(shù)初始化 _x
};
class Derived : public Base {
public:
// 通過(guò) Base(x) 初始化基類成員 _x
Derived(int x) : Base(x) {}
// 錯(cuò)誤:不能在派生類初始化列表直接初始化 _x
// Derived(int x) : _x(x) {}
};2. 派生類的拷貝構(gòu)造函數(shù)必須調(diào)用基類的拷貝構(gòu)造完成基類的拷貝初始化。
3. 派生類的operator=必須要調(diào)用基類的operator=完成基類的復(fù)制。
4. 派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類成員。因?yàn)檫@樣才能保證派生類對(duì)象先清理派生類成員再清理基類成員的順序。
5. 派生類對(duì)象初始化先調(diào)用基類構(gòu)造再調(diào)派生類構(gòu)造。
6. 派生類對(duì)象析構(gòu)清理先調(diào)用派生類析構(gòu)再調(diào)基類的析構(gòu)。
7. 因?yàn)楹罄m(xù)一些場(chǎng)景析構(gòu)函數(shù)需要構(gòu)成重寫(xiě),重寫(xiě)的條件之一是函數(shù)名相同(這個(gè)我們后面會(huì)講 解)。那么編譯器會(huì)對(duì)析構(gòu)函數(shù)名進(jìn)行特殊處理,處理成destrutor(),所以父類析構(gòu)函數(shù)不加 virtual的情況下,子類析構(gòu)函數(shù)和父類析構(gòu)函數(shù)構(gòu)成隱藏關(guān)系。


成員函數(shù)示例
class Person
{
public:
Person(const char* name)
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
delete _pstr;
}
protected:
string _name; // 姓名
string* _pstr = new string("111111111");
};
class Student : public Person
{
public:
// 先調(diào)用基類構(gòu)造函數(shù),再初始化派生類成員
Student(const char* name = "張三", int id = 0)
:Person(name)
,_id(id)
{}
// 拷貝構(gòu)造
Student(const Student& s)
:Person(s) // 調(diào)用基類拷貝構(gòu)造
,_id(s._id)
{}
// 賦值運(yùn)算符
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s); // 調(diào)用基類賦值運(yùn)算符
_id = s._id;
}
return *this;
}
~Student()
{
// 子類析構(gòu)完成后會(huì)自動(dòng)調(diào)用父類析構(gòu)
cout << *_pstr << endl;
delete _ptr;
}
protected:
int _id;
int* _ptr = new int;
};
int main()
{
Student s1;
Student s2(s1); // 調(diào)用拷貝構(gòu)造
Student s3("李四", 1);
s1 = s3; // 調(diào)用賦值運(yùn)算符
return 0;
}- Student s2(s1); // 調(diào)用拷貝構(gòu)造,沒(méi)寫(xiě)拷貝構(gòu)造默認(rèn)掉默認(rèn)構(gòu)造
- 派生類只用析構(gòu)自己的就可以了
- 構(gòu)造子類后編譯器自動(dòng)析構(gòu)父類(子可以用父,父不可以用子)
- 由于后邊多態(tài)的問(wèn)題析構(gòu)函數(shù)函數(shù)名被特殊處理了,統(tǒng)一處理成destructer
繼承與友元
友元關(guān)系不能繼承,也就是說(shuō)基類友元不能訪問(wèn)子類私有和保護(hù)成員
友元函數(shù)示例
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum; // 學(xué)號(hào)
};
// 友元函數(shù)可以訪問(wèn)兩個(gè)類的私有和保護(hù)成員
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}

前置聲明
| 關(guān)鍵點(diǎn) | 說(shuō)明 |
|---|---|
| 前置聲明的作用 | 告訴編譯器某個(gè)名稱是類類型,具體定義稍后出現(xiàn)。 |
| 何時(shí)需要前置聲明 | 當(dāng)類的名稱在完整定義之前被使用(如友元聲明、函數(shù)參數(shù))。 |
| 何時(shí)需要完整定義 | 當(dāng)需要實(shí)例化對(duì)象、訪問(wèn)成員、繼承或計(jì)算類大小時(shí)。 |
| 友元函數(shù)的特殊性 | 友元聲明中用到未定義的類時(shí),必須前置聲明該類。 |
繼承與靜態(tài)成員
基類定義了static靜態(tài)成員,則整個(gè)繼承體系里面只有一個(gè)這樣的成員。無(wú)論派生出多少個(gè)子類,都只有一個(gè)static成員實(shí)例 。
靜態(tài)成員屬于父類和派生類,派生類中不會(huì)單獨(dú)拷貝一份,繼承的是使用權(quán)
- 靜態(tài)成員屬于類本身,不屬于對(duì)象
- 無(wú)論是否涉及繼承,靜態(tài)成員(static 變量/函數(shù))都是類的全局共享成員,不屬于任何一個(gè)對(duì)象。所有對(duì)象(包括基類和派生類的對(duì)象)訪問(wèn)的是同一份靜態(tài)成員。
- 派生類不會(huì)單獨(dú)拷貝靜態(tài)成員
- 靜態(tài)成員不會(huì)被派生類復(fù)制,而是直接繼承訪問(wèn)權(quán)。
- 基類和派生類共享同一個(gè)靜態(tài)成員。
靜態(tài)成員繼承示例
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 統(tǒng)計(jì)人的個(gè)數(shù)
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum; // 學(xué)號(hào)
};
class Graduate : public Student
{
protected:
string _seminarCourse; // 研究科目
};
int main()
{
Person p;
Student s1;
Student s2;
cout << Person::_count << endl; // 輸出3,因?yàn)閯?chuàng)建了3個(gè)對(duì)象
return 0;
}
復(fù)雜的菱形繼承與菱形虛擬繼承
單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承

多繼承:一個(gè)子類有兩個(gè)或以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承

菱形繼承:菱形繼承是多繼承的一種特殊情況。

菱形繼承的問(wèn)題:從下面的對(duì)象成員模型構(gòu)造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問(wèn)題。 在Assistant的對(duì)象中Person成員會(huì)有兩份。

菱形繼承代碼

主要問(wèn)題
1. 數(shù)據(jù)冗余問(wèn)題
Assistant對(duì)象會(huì)包含 兩個(gè)Person子對(duì)象:- 一個(gè)來(lái)自
Student繼承路徑 - 一個(gè)來(lái)自
Teacher繼承路徑 - 這意味著
_name和_age會(huì)被存儲(chǔ)兩次,造成內(nèi)存浪費(fèi)
2. 二義性問(wèn)題(編譯錯(cuò)誤)
當(dāng)嘗試直接訪問(wèn) as._name 時(shí),編譯器無(wú)法確定應(yīng)該使用哪個(gè)路徑的 _name:
as._name = "張三"; // 錯(cuò)誤:對(duì)成員'_name'的訪問(wèn)不明確
3. 必須明確指定訪問(wèn)路徑
要解決二義性問(wèn)題,必須指定訪問(wèn)路徑:
as.Student::_name = "張三"; // 通過(guò)Student路徑訪問(wèn) // 或 as.Teacher::_name = "張三"; // 通過(guò)Teacher路徑訪問(wèn)
虛繼承(解決菱形繼承問(wèn)題)
通過(guò)virtual關(guān)鍵字來(lái)實(shí)現(xiàn)虛繼承解決菱形繼承問(wèn)題
Person成為虛基類(Virtual Base Class)。Assistant對(duì)象只包含一份Person子對(duì)象,Student和Teacher共享它。_age和_name不再冗余,所有訪問(wèn)都指向同一個(gè)內(nèi)存位置。
virtual要加在第一個(gè)會(huì)引起數(shù)據(jù)冗余的類上
class Person
{
public:
string _name; // 姓名
int _age;
};
class Student : virtual public Person
{
protected:
int _num; //學(xué)號(hào)
};
class Teacher : virtual public Person
{
protected:
int _id; // 職工編號(hào)
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修課程
};
int main()
{
Assistant as;
as.Student::_age = 18;
as.Teacher::_age = 30;
as._age = 19;
return 0;
}虛擬繼承解決數(shù)據(jù)冗余和二義性的原理
菱形繼承

菱形繼承有數(shù)據(jù)冗余問(wèn)題
菱形虛擬繼承



虛繼承構(gòu)造函數(shù)調(diào)用順序
#include<iostream>
using namespace std;
class A {
public:
A(const char* s) {
cout << s << endl; // 打印構(gòu)造信息
}
~A() {}
};
class B : virtual public A // 虛繼承A
{
public:
B(const char* sa, const char* sb)
: A(sa) { // 初始化虛基類A
cout << sb << endl;
}
};
class C : virtual public A // 虛繼承A
{
public:
C(const char* sa, const char* sb)
: A(sa) { // 初始化虛基類A
cout << sb << endl;
}
};
class D : public B, public C
{
public:
// 注意:虛基類A的初始化由D直接負(fù)責(zé)
D(const char* sa, const char* sb, const char* sc, const char* sd)
: A(sa), // 顯式初始化虛基類(實(shí)際最先執(zhí)行)
B(sa, sb), // 初始化B(此時(shí)不會(huì)重復(fù)構(gòu)造A)
C(sa, sc) { // 初始化C(此時(shí)不會(huì)重復(fù)構(gòu)造A)
cout << sd << endl;
}
};
int main() {
// 場(chǎng)景1:構(gòu)造D對(duì)象(菱形繼承)
D* p = new D("class A", "class B", "class C", "class D");
/* 輸出順序:
class A (虛基類A的構(gòu)造)
class B (B的構(gòu)造)
class C (C的構(gòu)造)
class D (D的構(gòu)造)
*/
delete p;
// 場(chǎng)景2:?jiǎn)为?dú)構(gòu)造B對(duì)象(單繼承)
B b("class A", "class B");
/* 輸出順序:
class A (虛基類A的構(gòu)造)
class B (B的構(gòu)造)
*/
return 0;
}在虛繼承中,構(gòu)造順序遵循:
- 虛基類最先構(gòu)造(無(wú)論它在初始化列表中的位置)
- 非虛基類按聲明順序構(gòu)造(
class D : public B, public C則先B后C) - 最后構(gòu)造派生類自身
初始化列表順序僅決定參數(shù)傳遞
如果交換 B 和 C 的參數(shù)(如 C(sa, sc) 寫(xiě)在 B(sa, sb) 前面),參數(shù)會(huì)正常傳遞,但構(gòu)造順序不變
虛繼承內(nèi)存布局示例
class A
{
public:
int _a;
};
// 虛繼承 A
class B : virtual public A
{
public:
int _b;
};
// 虛繼承 A
class C : virtual public A
{
public:
int _c;
};
// 多重繼承
class D : public B, public C
{
public:
int _d;
};
int main()
{
/* D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;*/
D d;
d._a = 1; // 虛繼承后只有一個(gè)_a
B b;
b._a = 2;
b._b = 3;
B* ptr = &b;
ptr->_a++; // 通過(guò)基類指針訪問(wèn)
ptr = &d;
ptr->_a++; // 通過(guò)基類指針訪問(wèn)派生類對(duì)象
return 0;
}- 每個(gè)虛繼承類存儲(chǔ)一個(gè)虛基表指針,指向其虛基表。
- 虛基表存的是偏移量不是A的地址(用來(lái)動(dòng)態(tài)計(jì)算虛基類的位置)
- 偏移量在切割和切片中需要
- 虛基表中偏移量里第一個(gè)為其他值進(jìn)行了預(yù)留
- d1和d2都直接指向這個(gè)數(shù)據(jù),不需要在內(nèi)部開(kāi)額外空間存重復(fù)數(shù)據(jù)
- ptr->a不知道是B還是D的,B和D中間可能擱這好幾部分,先通過(guò)虛基表指針找到虛基表,再憑借偏移量可以找到A。

繼承和組合
public繼承是一種is-a的關(guān)系。也就是說(shuō)每個(gè)派生類對(duì)象都是一個(gè)基類對(duì)象。
組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個(gè)B對(duì)象中都有一個(gè)A對(duì)象。
繼承允許你根據(jù)基類的實(shí)現(xiàn)來(lái)定義派生類的實(shí)現(xiàn)。這種通過(guò)生成派生類的復(fù)用通常被稱 為白箱復(fù)用(white-box reuse)。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼承方式中,基類的內(nèi)部細(xì)節(jié)對(duì)子類可見(jiàn) 。繼承一定程度破壞了基類的封裝,基類的改變,對(duì)派生類有很 大的影響。派生類和基類間的依賴關(guān)系很強(qiáng),耦合度高。
對(duì)象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過(guò)組裝或組合對(duì)象來(lái)獲得。對(duì)象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為黑箱復(fù) 用(black-box reuse),因?yàn)閷?duì)象的內(nèi)部細(xì)節(jié)是不可見(jiàn)的。對(duì)象只以“黑箱”的形式出現(xiàn)。 組合類之間沒(méi)有很強(qiáng)的依賴關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類被封裝。
實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過(guò)繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類之間的關(guān)系可以用繼承,可以用組合,就用組合。
| 特性 | 繼承 | 組合 |
|---|---|---|
| 關(guān)系 | is-a | has-a |
| 耦合度 | 高 | 低 |
| 靈活性 | 低(編譯時(shí)確定) | 高(運(yùn)行時(shí)可替換) |
| 代碼復(fù)用 | 白盒復(fù)用(了解實(shí)現(xiàn)) | 黑盒復(fù)用(只使用接口) |
| 多態(tài)支持 | 支持 | 間接支持(通過(guò)接口) |
| 基類/組件修改影響 | 影響所有派生類 | 影響范圍有限 |
| 適合場(chǎng)景 | 需要多態(tài)/接口擴(kuò)展 | 代碼復(fù)用/功能組合 |
//繼承
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
//組合
class A
{
public:
int _a;
};
class B :
{
public:
A a();
int _b;
};
多態(tài)
注意:
- 只有成員函數(shù)才能+virtual
- 對(duì)象里不存成員函數(shù),只有成員變量
- 不同對(duì)象傳過(guò)去調(diào)用不同函數(shù)
- 多態(tài)調(diào)用看的指向的對(duì)象
- 普通對(duì)象看當(dāng)前類型
- 派生類的重寫(xiě)虛函數(shù)可以不+virtual
- 協(xié)變:返回值可以不同,但返回值必須是父子關(guān)系指針和引用
- 虛函數(shù)三同(函數(shù)名,參數(shù)列表,返回值類型)
多態(tài)的概念
多態(tài)是面向?qū)ο缶幊痰娜筇匦灾唬ǚ庋b、繼承、多態(tài)),它允許不同類的對(duì)象對(duì)同一消息做出不同的響應(yīng)
多態(tài)分為:
- 編譯時(shí)多態(tài)(靜態(tài)多態(tài)):通過(guò)函數(shù)重載和模板實(shí)現(xiàn)
- 運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)多態(tài)):通過(guò)虛函數(shù)和繼承實(shí)現(xiàn)
我們主要了解運(yùn)?時(shí)多態(tài)。要實(shí)現(xiàn)運(yùn)行時(shí)多態(tài),必須滿足以下條件:
- 繼承關(guān)系:存在基類和派生類
- 虛函數(shù):基類中使用
virtual聲明函數(shù) - 指針/引用:通過(guò)基類指針或引用調(diào)用虛函數(shù)
多態(tài)的定義和實(shí)現(xiàn)
多態(tài)的構(gòu)成條件
多態(tài)是在不同繼承關(guān)系的類對(duì)象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。比如Student繼承了 Person。Person對(duì)象買票全價(jià),Student對(duì)象買票半價(jià)。
- 1.調(diào)用函數(shù)是重寫(xiě)的虛函數(shù)
- 2.基類指針或引用調(diào)用(子類指針只能指向子類,虛表只有子類虛函數(shù),父類沒(méi)有子類成員變量或方法會(huì)引發(fā)內(nèi)存錯(cuò)誤)
基礎(chǔ)多態(tài)代碼:
#include <iostream>
using namespace std;
// 基類
class Person {
public:
virtual void BuyTicket() {
cout << "買票-全價(jià)" << endl;
}
};
// 派生類
class Student : public Person {
public:
virtual void BuyTicket() override {
cout << "買票-半價(jià)" << endl;
}
};
// 多態(tài)調(diào)用函數(shù)
void Func(Person& p) {
p.BuyTicket(); // 多態(tài)調(diào)用
}
int main() {
cout << "----- 基礎(chǔ)多態(tài)演示 -----" << endl;
Person Mike;
Student Johnson;
Func(Mike); // 輸出: 買票-全價(jià)
Func(Johnson); // 輸出: 買票-半價(jià)
return 0;
}“調(diào)用函數(shù)是重寫(xiě)的虛函數(shù)”
- 體現(xiàn)在
Student類中virtual void BuyTicket() override,它重寫(xiě)了基類Person的虛函數(shù)BuyTicket()。 - 運(yùn)行時(shí)通過(guò)
Person& p調(diào)用p.BuyTicket()時(shí),會(huì)根據(jù)實(shí)際對(duì)象類型(Person或Student)決定調(diào)用哪個(gè)版本(多態(tài))。
“基類指針或引用調(diào)用”
- 體現(xiàn)在
Func(Person& p)的參數(shù)是基類引用,且p.BuyTicket()通過(guò)該引用調(diào)用虛函數(shù)。 - 如果改為
Func(Person p)(傳值而非引用),則失去多態(tài),始終調(diào)用Person::BuyTicket()。
虛函數(shù)
虛函數(shù):即被virtual修飾的類成員函數(shù)稱為虛函數(shù)。
virtual void BuyTicket() {
cout << "買票-全價(jià)" << endl;
}虛函數(shù)的重寫(xiě)(覆蓋)
派生類中有一個(gè)跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的 返回值類型、函數(shù)名字、參數(shù)列表完全相同),稱子類的虛函數(shù)重寫(xiě)了基類的虛函數(shù)。(派生類中virtual可以不寫(xiě),但是父類必須寫(xiě),因?yàn)槿绻割惒粚?xiě)這個(gè)函數(shù)就不是虛函數(shù)了)
// 派生類
class Student : public Person {
public:
virtual void BuyTicket() override {
cout << "買票-半價(jià)" << endl;
}
};虛函數(shù)重寫(xiě)的兩個(gè)例外
協(xié)變
基類與派生類虛函數(shù)返回值類型不同
派生類重寫(xiě)基類虛函數(shù)時(shí),與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對(duì)象的指 針或者引用,派生類虛函數(shù)返回派生類對(duì)象的指針或者引用時(shí),稱為協(xié)變。
協(xié)變僅適用于指針或引用類型
Student::f() 返回 int*不合法,int* 與 A* 無(wú)繼承關(guān)系,不滿足協(xié)變規(guī)則。
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
};
析構(gòu)函數(shù)的重寫(xiě)
- 基類與派生類析構(gòu)函數(shù)的名字不同
- 如果基類的析構(gòu)函數(shù)為虛函數(shù),此時(shí)派生類析構(gòu)函數(shù)只要定義,無(wú)論是否加virtual關(guān)鍵字, 都與基類的析構(gòu)函數(shù)構(gòu)成重寫(xiě),雖然基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同, 看起來(lái)違背了重寫(xiě)的規(guī)則,其實(shí)不然,這里可以理解為編譯器對(duì)析構(gòu)函數(shù)的名稱做了特殊處 理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor。
確保通過(guò)基類指針刪除派生類對(duì)象時(shí)正確調(diào)用派生類析構(gòu)
內(nèi)存釋放過(guò)程:
通過(guò)虛表找到 Student::~Student()
執(zhí)行派生類析構(gòu):
- 輸出
~Student() - 釋放
ptr指向的數(shù)組
自動(dòng)調(diào)用基類析構(gòu) Person::~Person()
若不使用虛析構(gòu)函數(shù):
delete p只會(huì)調(diào)用Person::~Person()- 導(dǎo)致
ptr內(nèi)存泄漏(約40字節(jié))
防止基類new派生類,析構(gòu)基類


class Person {
public:
virtual void BuyTicket() { cout << "買票-全價(jià)" << endl; }
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "買票-半價(jià)" << endl; }
~Student() {
cout << "~Student()" << endl;
delete[] ptr;
}
protected:
int* ptr = new int[10];
};
int main()
{
//Person p;
//Student s;
Person* p = new Person;
p->BuyTicket();
delete p;
p = new Student;
p->BuyTicket();
delete p; // p->destructor() + operator delete(p)
// 這里我們期望p->destructor()是一個(gè)多態(tài)調(diào)用,而不是普通調(diào)用
return 0;
}
禁止在派生類中釋放基類資源
- 基類析構(gòu)函數(shù)已經(jīng)負(fù)責(zé)其自身資源的釋放,派生類不應(yīng)干涉。
層級(jí)化資源管理
- 基類管理基類的資源
- 派生類管理派生類新增的資源
- 像堆疊的俄羅斯套娃,各層管好自己的部分
C++11 override 和 ?nal
C++對(duì)函數(shù)重寫(xiě)的要求比較嚴(yán)格,但是有些情況下由于疏忽,可能會(huì)導(dǎo)致函數(shù) 名字母次序?qū)懛炊鵁o(wú)法構(gòu)成重載,而這種錯(cuò)誤在編譯期間是不會(huì)報(bào)出的,只有在程序運(yùn)行時(shí)沒(méi)有 得到預(yù)期結(jié)果才來(lái)debug會(huì)得不償失,因此:C++11提供了override和?nal兩個(gè)關(guān)鍵字,可以幫 助用戶檢測(cè)是否重寫(xiě)。
?nal:修飾虛函數(shù),表示該虛函數(shù)不能再被重寫(xiě)
final 的作用:
當(dāng)用于虛函數(shù)時(shí)(如 virtual void Drive() final),表示禁止派生類重寫(xiě)該函數(shù)。
當(dāng)用于類時(shí)(如 class Benz final),表示禁止其他類繼承 Benz。
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒適" << endl;}
};
override: 檢查派生類虛函數(shù)是否重寫(xiě)了基類某個(gè)虛函數(shù),如果沒(méi)有重寫(xiě)編譯報(bào)錯(cuò)。
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒適" << endl;}
};設(shè)計(jì)不希望被繼承類
1.基類構(gòu)造函數(shù)私有 (C++98)
1)通過(guò)將構(gòu)造函數(shù)設(shè)為私有,阻止派生類實(shí)例化(因?yàn)榕缮悩?gòu)造時(shí)需要調(diào)用基類構(gòu)造函數(shù))。

2)派生類析構(gòu)時(shí)需要調(diào)用基類析構(gòu)函數(shù),私有化析構(gòu)函數(shù)可阻止繼承。

2.基類加一個(gè)final (C++11)

重載、覆蓋(重寫(xiě))、隱藏(重定義)的對(duì)比

抽象類
概念
在虛函數(shù)的后面寫(xiě)上 =0 ,則這個(gè)函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類),抽象類不能實(shí)例化出對(duì)象。派生類繼承后也不能實(shí)例化出對(duì)象,只有重寫(xiě)純虛函數(shù),派生類才能實(shí)例化出對(duì)象。純虛函數(shù)規(guī)范了派生類必須重寫(xiě),另外純虛函數(shù)更體現(xiàn)出了接口繼承。
純虛函數(shù)和抽象類
func 為純虛函數(shù),所以 A 為抽象類。抽象類不允許實(shí)例化對(duì)象。
class A
{
public:
virtual void fun() = 0;
};雖然抽象類不可以實(shí)例化出對(duì)象,但是我們可以寫(xiě)一個(gè)類來(lái)繼承它,并重寫(xiě)里面的純虛函數(shù),這時(shí)這個(gè)子類是可以實(shí)例化出對(duì)象并可以調(diào)用重寫(xiě)后的方法的。
class A
{
public:
virtual void fun() = 0;
};
class B:public A
{
public:
virtual void fun()
{
cout << " " << endl;
}
};多態(tài)的原理
練習(xí)
class A
{
public:
virtual void func(int val = 1) { cout << "A->" << val << endl; }
virtual void test() { func(); }//隱含this指針
};
class B : public A
{
public:
virtual void func(int val = 0) { cout << "B->" << val << endl; }
};
int main()
{
B* p = new B;
p->test();
return 0;
}- p 指針調(diào) test 方法的時(shí)候會(huì)去 A 類中調(diào)用,A 類的 test 方法中又調(diào)用了 func 方法,類的成員函數(shù)是有一個(gè)隱含的 this 指針的,這個(gè) func 方法就是通過(guò)這個(gè) this 指針調(diào)用的,這個(gè) this 指針類型是 A*(基類指針)func 方法是重寫(xiě)虛函數(shù),滿足了多態(tài)的條件。
test()是從A繼承的,調(diào)用func()時(shí)使用A的默認(rèn)參數(shù)1- 但實(shí)際調(diào)用的是B類的
func()實(shí)現(xiàn),因?yàn)閜指向B對(duì)象

多態(tài)的條件
- 父類的指針和引用
- 虛函數(shù)的重寫(xiě)
- 為什么不能是子類指針或引用,為什么不能是父類對(duì)象:
- 子類賦值給父類對(duì)象切片,不會(huì)拷貝虛表,如果拷貝虛表那么父類對(duì)象虛表中時(shí)父類虛函數(shù)還是子類就不確定了
- 派生類虛表先將父類拷貝一份再將修改的進(jìn)行覆蓋
多態(tài)的底層

虛函數(shù)表(vtable):
Student 對(duì)象會(huì)包含一個(gè)虛函數(shù)表,包含:
- 重寫(xiě)的
BuyTicket()(半價(jià)版本) - 繼承的
Func1()、Func2() - 新增的
Func3()(應(yīng)該有的,編譯器優(yōu)化了)
訪問(wèn)控制:
Func3()是private,無(wú)法通過(guò)基類指針調(diào)用,但仍在虛表中存在。(vs優(yōu)化了)_a在基類中是public(建議改為protected,避免直接暴露)。
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}1.派生類對(duì)象d中也有一個(gè)虛表指針,d對(duì)象由兩部分構(gòu)成,一部分是父類繼承下來(lái)的成員,虛表指針也就是存在部分的另一部分是自己的成員。
2. 基類b對(duì)象和派生類d對(duì)象虛表是不一樣的,這里我們發(fā)現(xiàn)Func1完成了重寫(xiě),所以d的虛表中存的是重寫(xiě)的Derive::Func1,所以虛函數(shù)的重寫(xiě)也叫作覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫(xiě)是語(yǔ)法的叫法,覆蓋是原理層的叫法。
3. 另外Func2繼承下來(lái)后是虛函數(shù),所以放進(jìn)了虛表,F(xiàn)unc3也繼承下來(lái)了,但是不是虛函 數(shù),所以不會(huì)放進(jìn)虛表。
4. 虛函數(shù)表本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,一般情況這個(gè)數(shù)組最后面放了一個(gè)nullptr。
5. 總結(jié)一下派生類的虛表生成:
- a.先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中
- b.如果派生類重寫(xiě)了基類中某個(gè)虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù)
- c.派生類自己新增加的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的最后。
6. 虛函數(shù)存在哪的?虛表存在哪的? 答:虛函數(shù)存在虛表,虛表存在對(duì)象中。
注意上面的回答的錯(cuò)的。虛表存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。另外對(duì)象中存的不是虛表,存的是虛表指針。vs下虛表存在代碼段
內(nèi)存分布
內(nèi)存問(wèn)題(虛函數(shù)表指針)
- 虛函數(shù)表也簡(jiǎn)稱虛表
- 虛函數(shù)本質(zhì)放在代碼段
- 虛表里存的是虛函數(shù)的地址

虛函數(shù)表指針 (vptr)
- 當(dāng)類包含虛函數(shù)時(shí),編譯器會(huì)自動(dòng)添加一個(gè) 虛函數(shù)表指針 (vptr),指向該類的虛函數(shù)表 (vtable)。
- 在 64 位系統(tǒng) 下,指針的大小是 8 字節(jié)。
- 因此,
Base類至少占用 8 字節(jié)(用于存儲(chǔ)vptr)。
成員變量 _b
_b是一個(gè)char類型,占用 1 字節(jié)。- 但由于 內(nèi)存對(duì)齊(alignment),編譯器會(huì)在
_b后面填充 7 字節(jié),使得vptr和_b整體對(duì)齊到 8 字節(jié) 邊界(優(yōu)化訪問(wèn)速度)。
| 組成部分 | 大?。?4 位) | 大小(32 位) |
|---|---|---|
| 虛函數(shù)表指針 | 8 字節(jié) | 4 字節(jié) |
| char _b | 1 字節(jié) | 1 字節(jié) |
| 填充字節(jié) | 7 字節(jié) | 3 字節(jié) |
| 總大小 | 16 字節(jié) | 8 字節(jié) |
為什么不是9字節(jié)
- 如果
sizeof(Base)是 9 字節(jié)(vptr8 +_b1),那么當(dāng)Base對(duì)象存儲(chǔ)在數(shù)組中時(shí),第二個(gè)對(duì)象的vptr會(huì)錯(cuò)位(起始地址不是 8 的倍數(shù)),導(dǎo)致 性能下降 或 崩潰(某些 CPU 架構(gòu)要求指針地址對(duì)齊)。 - 因此,編譯器會(huì)自動(dòng)填充字節(jié),使類的大小是 最大對(duì)齊單位(8 字節(jié))的整數(shù)倍。

多態(tài)實(shí)現(xiàn)指向父類調(diào)父類指向子類調(diào)子類 :
指向父類在父類虛函數(shù)表中找到父類地址,找父類虛表
指向子類在子類虛函數(shù)表中找到子類地址(切片后看到的還是父類對(duì)象,但是是子類里的父類,他的虛表已經(jīng)被覆蓋了,找到的是子類地址)
- 函數(shù)調(diào)用棧幀才會(huì)去開(kāi)空間
- 同類型對(duì)象共用虛表,虛表不在棧上
- 沒(méi)有獨(dú)立函數(shù)不建立自己的虛表
C++ 的多態(tài)機(jī)制、對(duì)象內(nèi)存布局和繼承關(guān)系
class Person {
public:
virtual void BuyTicket() { cout << "買票-全價(jià)" << endl; }
virtual void Func1()
{
cout << "Person::Func1()" << endl;
}
virtual void Func2()
{
cout << "Person::Func2()" << endl;
}
//protected:
int _a = 0;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "買票-半價(jià)" << endl; }
private:
virtual void Func3()
{
//_b++;
cout << "Student::Func3()" << endl;
}
protected:
int _b = 1;
};
void Func(Person& p)
{
p.BuyTicket();
}
void test()
{
Person ps1;
Student st1;
}
int main()
{
Person ps;
Student st;
st._a = 10;
ps = st;
Person* ptr = &st;
Person& ref = st;
test();
return 0;
}


由表可推斷虛表是儲(chǔ)存在常量區(qū)的。
x86環(huán)境運(yùn)行



多繼承
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main()
{
Derive d;
Base1* ptr1 = &d;
ptr1->func1();
Base2* ptr2 = &d;
ptr2->func1();
Derive* ptr3 = &d;
ptr3->func1();
return 0;
}對(duì)象內(nèi)存分布和虛函數(shù)表布局

base1
vfptr: 指向Derive類為Base1部分維護(hù)的虛表(地址0x00007ff7179bbd30)
[0]: 重寫(xiě)的Derive::func1()(地址0x00007ff7179b1348)[1]: 繼承的Base1::func2()(地址0x00007ff7179b1334)
b1: 未初始化的int成員(值-858993460是Debug模式的填充值0xCCCCCCCC)
base2
vfptr: 指向Derive類為Base2部分維護(hù)的虛表(地址0x00007ff7179bbd38)
[0]: Thunk函數(shù)(地址0x00007ff7179b1230),用于調(diào)整this指針并跳轉(zhuǎn)到Derive::func1()[1]: 繼承的Base2::func2()(地址0x00007ff7179b10eb)
b2: 同樣未初始化的int成員
總結(jié):
多重繼承的虛表:
- 每個(gè)基類(
Base1/Base2)有獨(dú)立的虛表指針 Derive重寫(xiě)的func1()在Base1虛表中直接替換,在Base2虛表中通過(guò)Thunk調(diào)用
Thunk函數(shù):
- 當(dāng)通過(guò)
Base2*調(diào)用func1()時(shí),需要調(diào)整this指針(指向Base2子對(duì)象的起始位置) - Thunk會(huì)先修正
this指針(減去Base2在Derive中的偏移量),再跳轉(zhuǎn)到Derive::func1()
未初始化值:
-858993460(即0xCCCCCCCC)是Debug模式的填充值,用于標(biāo)記未初始化的棧內(nèi)存
兩種多態(tài)
靜態(tài)(編譯時(shí))的多態(tài),函數(shù)重載
動(dòng)態(tài)(運(yùn)行時(shí))的多態(tài),繼承,虛函數(shù)重寫(xiě),實(shí)現(xiàn)的多態(tài)
int main()
{
//靜態(tài)多態(tài)(編譯時(shí)多態(tài))
int i = 1;
double d = 1.1;
cout << i << endl;
cout << d << endl;
//動(dòng)態(tài)多態(tài)(運(yùn)行時(shí)多態(tài))
Person ps;
Person* ptr = &ps;
ps.BuyTicket();
ptr->BuyTicket();
return 0;
}
靜態(tài)
實(shí)現(xiàn)方式:函數(shù)重載
operator<<針對(duì)不同的參數(shù)類型(int和double)有不同的實(shí)現(xiàn)- 在編譯時(shí)就能確定調(diào)用哪個(gè)版本的函數(shù)
特點(diǎn):
- 通過(guò)函數(shù)重載實(shí)現(xiàn)
- 編譯時(shí)確定具體調(diào)用哪個(gè)函數(shù)
- 不需要虛函數(shù)或繼承關(guān)系
- 效率高(無(wú)運(yùn)行時(shí)開(kāi)銷)
動(dòng)態(tài)
實(shí)現(xiàn)方式:
- 繼承關(guān)系
- 虛函數(shù)重寫(xiě)
- 通過(guò)基類指針或引用調(diào)用
特點(diǎn):
- 通過(guò)虛函數(shù)表(vtable)實(shí)現(xiàn)
- 運(yùn)行時(shí)確定調(diào)用哪個(gè)函數(shù)
- 需要繼承和虛函數(shù)
- 有一定的運(yùn)行時(shí)開(kāi)銷(查虛函數(shù)表)
兩種多態(tài)的關(guān)鍵區(qū)別
| 特性 | 靜態(tài)多態(tài) | 動(dòng)態(tài)多態(tài) |
|---|---|---|
| 實(shí)現(xiàn)方式 | 函數(shù)重載、模板 | 虛函數(shù)、繼承 |
| 確定時(shí)機(jī) | 編譯時(shí) | 運(yùn)行時(shí) |
| 性能 | 高效(無(wú)額外開(kāi)銷) | 有一定開(kāi)銷(查虛函數(shù)表) |
| 靈活性 | 較低(編譯時(shí)確定) | 高(運(yùn)行時(shí)可改變行為) |
| 典型應(yīng)用 | 運(yùn)算符重載、函數(shù)重載 | 接口設(shè)計(jì)、多態(tài)對(duì)象處理 |
虛基表和虛函數(shù)表區(qū)別
| 特性 | 虛基表 (Virtual Base Table) | 虛函數(shù)表 (Virtual Function Table, vtable) |
|---|---|---|
| 用途 | 解決虛繼承中的共享基類偏移問(wèn)題 | 實(shí)現(xiàn)運(yùn)行時(shí)多態(tài),管理虛函數(shù)調(diào)用 |
| 觸發(fā)條件 | 當(dāng)類使用virtual繼承時(shí)(如class D : virtual public B) | 當(dāng)類包含virtual成員函數(shù)時(shí) |
| 存儲(chǔ)內(nèi)容 | 存儲(chǔ)虛基類相對(duì)于當(dāng)前對(duì)象的偏移量 | 存儲(chǔ)虛函數(shù)的地址(指向?qū)嶋H實(shí)現(xiàn)的函數(shù)指針) |
| 指針名稱 | vbptr(虛基表指針) | vfptr(虛函數(shù)表指針) |
| 內(nèi)存位置 | 位于對(duì)象內(nèi)存布局的起始或相關(guān)位置 | 通常位于對(duì)象內(nèi)存布局的起始位置 |
| 編譯器生成邏輯 | 確保多個(gè)派生類共享同一虛基類實(shí)例時(shí)能正確訪問(wèn)基類成員 | 確保通過(guò)基類指針/引用調(diào)用時(shí)能正確跳轉(zhuǎn)到派生類實(shí)現(xiàn) |
| 是否依賴運(yùn)行時(shí) | 是(運(yùn)行時(shí)計(jì)算偏移量) | 是(運(yùn)行時(shí)查表確定函數(shù)地址) |
| 典型場(chǎng)景 | 菱形繼承(如B ← D1 ← D和B ← D2 ← D,B為虛基類) | 基類定義虛函數(shù),派生類重寫(xiě)(如Shape::draw()) |
| 訪問(wèn)開(kāi)銷 | 額外間接尋址(通過(guò)vbptr找到偏移量再訪問(wèn)基類) | 一次指針解引用(通過(guò)vfptr跳轉(zhuǎn)到函數(shù)地址) |
| 調(diào)試查看方式 | 在調(diào)試器中觀察vbptr和偏移量 | 在調(diào)試器中觀察vfptr和函數(shù)地址列表 |
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
c語(yǔ)言實(shí)現(xiàn)數(shù)組循環(huán)左移m位
這篇文章主要介紹了c語(yǔ)言實(shí)現(xiàn)數(shù)組循環(huán)左移m位,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
C語(yǔ)言使用DP動(dòng)態(tài)規(guī)劃思想解最大K乘積與乘積最大問(wèn)題
Dynamic Programming動(dòng)態(tài)規(guī)劃方法采用最優(yōu)原則來(lái)建立用于計(jì)算最優(yōu)解的遞歸式,并且考察每個(gè)最優(yōu)決策序列中是否包含一個(gè)最優(yōu)子序列,這里我們就來(lái)展示C語(yǔ)言使用DP動(dòng)態(tài)規(guī)劃思想解最大K乘積與乘積最大問(wèn)題2016-06-06
QML中動(dòng)態(tài)與靜態(tài)模型應(yīng)用詳解
QML是一種描述性的腳本語(yǔ)言,文件格式以.qml結(jié)尾。語(yǔ)法格式非常像CSS(參考后文具體例子),但又支持javascript形式的編程控制。QtDesigner可以設(shè)計(jì)出·ui界面文件,但是不支持和Qt原生C++代碼的交互2022-08-08
C++在多線程中使用condition_variable實(shí)現(xiàn)wait
這篇文章主要介紹了C++中的condition_variable中在多線程中的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09
C語(yǔ)言實(shí)現(xiàn)打印星號(hào)圖案
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)打印星號(hào)圖案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
C++簡(jiǎn)單又輕松建立鏈?zhǔn)蕉鏄?shù)流程
二叉樹(shù)的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)是指,用鏈表來(lái)表示一棵二叉樹(shù),即用鏈來(lái)指示元素的邏輯關(guān)系。通常的方法是鏈表中每個(gè)結(jié)點(diǎn)由三個(gè)域組成,數(shù)據(jù)域和左右指針域,左右指針?lè)謩e用來(lái)給出該結(jié)點(diǎn)左孩子和右孩子所在的鏈結(jié)點(diǎn)的存儲(chǔ)地址2022-06-06
Qt+FFMPEG實(shí)現(xiàn)循環(huán)解碼詳解
這篇文章主要為大家詳細(xì)介紹了如何利用Qt+FFMPEG實(shí)現(xiàn)循環(huán)解碼功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Qt有一定幫助,需要的可以參考一下2022-08-08

