[toc]
繼承
? 繼承,可以讓子類擁有父類的所有成員(變量\函數(shù))

對(duì)象的內(nèi)存布局
struct Person {
};
struct Student : Person {
int m_no;
};
struct GoodStudent : Student {
int m_money;
};
? 父類的成員變量在前,子類的成員變量在后

成員訪問權(quán)限
? 成員訪問權(quán)限、繼承方式有3種
public:公共的,任何地方都可以訪問(struct默認(rèn))
protected:子類內(nèi)部、當(dāng)前類內(nèi)部可以訪問
private:私有的,只有當(dāng)前類內(nèi)部可以訪問(class默認(rèn))
? 子類內(nèi)部訪問父類成員的權(quán)限,是以下2項(xiàng)中權(quán)限最小的那個(gè)
成員本身的訪問權(quán)限
上一級(jí)父類的繼承方式
? 開發(fā)中用的最多的繼承方式是public,這樣能保留父類原來的成員訪問權(quán)限
? 訪問權(quán)限不影響對(duì)象的內(nèi)存布局
struct Person {
private:
int m_age;
public:
void setAge(int age) {
m_age = age;
}
int getAge() {
return m_age;
}
};
struct Student : public Person {
};
struct GoodStudent : public Student {
void work() {
}
};
class Person {
private:
int m_age;
public:
void run() {
}
};
struct Student : public Person {
};
class GoodStudent : public Student {
};
初始化列表
? 特點(diǎn)
一種便捷的初始化成員變量的方式
只能用在構(gòu)造函數(shù)中
初始化順序只跟成員變量的聲明順序有關(guān)
struct Person {
int m_age;
int m_height;
/*Person(int age, int height) {
m_age = age;
m_height = height;
}*/
// 語法糖
Person(int age = 0, int height = 0) :m_age(age), m_height(height) {}
};
int myAge() {
cout << "myAge()" << endl;
return 10;
}
int myHeight() {
cout << "myHeight()" << endl;
return 10;
}
struct Person {
int m_age;
int m_height;
Person(int age , int height):m_age(myAge()), m_height(myHeight()){
}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
}
myAge()
myHeight()
10
10
- 初始化順序只跟成員變量的聲明順序有關(guān)


說明輸出和定義在類中成員的順序有關(guān),和代碼Person(int age , int height):m_age(myAge()), m_height(myHeight())定義的順序無關(guān)。
初始化列表與默認(rèn)參數(shù)配合使用
? 如果函數(shù)聲明和實(shí)現(xiàn)是分離的
初始化列表只能寫在函數(shù)的實(shí)現(xiàn)中
默認(rèn)參數(shù)只能寫在函數(shù)的聲明中
struct Person {
int m_age;
int m_height;
Person(int age = 0, int height = 0);
};
Person::Person(int age, int height) :m_age(age), m_height(height) {
}
int main() {
Person person1;
Person person2(17);
Person person(18, 180);
}
構(gòu)造函數(shù)的互相調(diào)用

struct Person {
int m_age;
int m_height;
/*Person() :Person(10, 20) {
}*/
Person() :Person(10, 20) {
// 創(chuàng)建了一個(gè)臨時(shí)的Person對(duì)象
// Person(10, 20);
/*Person person;
person.m_age = 10;
person.m_height = 20;*/
}
Person(int age, int height) {
this->m_age = age;
this->m_height = height;
}
};
int main() {
Person person;
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
父類的構(gòu)造函數(shù)
? 子類的構(gòu)造函數(shù)默認(rèn)會(huì)調(diào)用父類的無參構(gòu)造函數(shù)
? 如果子類的構(gòu)造函數(shù)顯式地調(diào)用了父類的有參構(gòu)造函數(shù),就不會(huì)再去默認(rèn)調(diào)用父類的無參構(gòu)造函數(shù)
? 如果父類缺少無參構(gòu)造函數(shù),子類的構(gòu)造函數(shù)必須顯式調(diào)用父類的有參構(gòu)造函數(shù)
struct Person {
Person() {
cout << "Person::Person()" << endl;
}
~Person() {
cout << "Person::~Person()" << endl;
}
};
struct Student : Person {
Student() {
// call Person::Person
cout << "Student::Student()" << endl;
}
~Student() {
cout << "Student::~Student()" << endl;
// call Person::~Person
}
};
int main() {
{
Student student;
}
// Student student(18, 34);
return 0;
}
/*
Person::Person()
Student::Student()
Student::~Student()
Person::~Person()
*/
- 子類的構(gòu)造函數(shù)默認(rèn)會(huì)調(diào)用父類的無參構(gòu)造函數(shù)

構(gòu)造、析構(gòu)順序

多態(tài)
? 默認(rèn)情況下,編譯器只會(huì)根據(jù)指針類型調(diào)用對(duì)應(yīng)的函數(shù),不存在多態(tài)
? 多態(tài)是面向?qū)ο蠓浅V匾囊粋€(gè)特性
同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果
在運(yùn)行時(shí),可以識(shí)別出真正的對(duì)象類型,調(diào)用對(duì)應(yīng)子類中的函數(shù)
? 多態(tài)的要素
子類重寫父類的成員函數(shù)(override)
父類指針指向子類對(duì)象
利用父類指針調(diào)用重寫的成員函數(shù)
父類指針、子類指針

struct Person {
int m_age;
};
struct Student : public Person {
int m_score;
};
void test() {
// 父類指針 指向 子類對(duì)象
// Person *p = new Student();
// p->m_age = 10;
Student *p = (Student *) new Person();
p->m_age = 10;
p->m_score = 100;//危險(xiǎn),動(dòng)了age后面的4個(gè)字節(jié),修改了非自己內(nèi)存的數(shù)據(jù)
}
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
struct Dog : Animal {
// 重寫(覆寫、覆蓋、override)
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat : Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Pig : Animal {
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
- 同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果
//同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果
void liu(Animal *p) {
p->speak();
p->run();
}
int main() {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
/*
Dog::speak()
Dog::run()
Cat::speak()
Cat::run()
Pig::speak()
Pig::run()
*/
- 默認(rèn)情況下,編譯器只會(huì)根據(jù)指針類型調(diào)用對(duì)應(yīng)的函數(shù),不存在多態(tài)
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
/*
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
*/

虛函數(shù)
? C++中的多態(tài)通過虛函數(shù)(virtual function)來實(shí)現(xiàn)
? 虛函數(shù):被virtual修飾的成員函數(shù)
? 只要在父類中聲明為虛函數(shù),子類中重寫的函數(shù)也自動(dòng)變成虛函數(shù)(也就是說子類中可以省略virtual關(guān)鍵字)
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
int main() {
Animal *p = new Pig();
p->speak();
p->run();
}
/*
Pig::speak()
Pig::run()
*/

虛表
? 虛函數(shù)的實(shí)現(xiàn)原理是虛表,這個(gè)虛表里面存儲(chǔ)著最終==需要調(diào)用的虛函數(shù)地址==,這個(gè)虛表也叫虛函數(shù)表

struct Animal {
int m_age;
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal {
int m_life;
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
cout<< sizeof (Cat)<<endl;
}
8
struct Animal {
int m_age;
virtual void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
virtual void speak()
cout<< sizeof (Cat)<<endl;
16

虛表里面存儲(chǔ)著最終需要調(diào)用的虛函數(shù)地址,這個(gè)虛表也叫虛函數(shù)表
(x86環(huán)境的圖)

int main() {
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
}
下面我們通過上述代碼的內(nèi)存數(shù)據(jù)及匯編指令,來窺探虛表的原理
cat堆內(nèi)存數(shù)據(jù)






總結(jié)一下Animal類由于存在虛函數(shù)virtual void speak() 和virtual void run() 所以Animal對(duì)象創(chuàng)建完成后,前8個(gè)字節(jié)是一個(gè)地址指針,指向了一個(gè)虛表的地址

由于有兩個(gè)虛函數(shù),所以指向的地址,前8位speak函數(shù)的地址,后8個(gè)字節(jié)為run函數(shù)的地址。
Animal 虛表地址之后,跟著為屬性成員的地址,movl $0x14, 0x8(%rax) 修改m_age的內(nèi)存地址的值為20.
0x10fe79035 <+85>: callq *(%rcx)及 0x10fe79041 <+97>: callq *0x8(%rcx)都是間接調(diào)用,先找到虛表地址,從內(nèi)存地址中,取出函數(shù)地址間接調(diào)用到對(duì)應(yīng)類對(duì)象真實(shí)實(shí)現(xiàn)函數(shù)的地址上。
調(diào)用父類的成員函數(shù)實(shí)現(xiàn)

