[toc]
繼承
? 繼承,可以讓子類擁有父類的所有成員(變量\函數(shù))
對象的內存布局
struct Person {
};
struct Student : Person {
int m_no;
};
struct GoodStudent : Student {
int m_money;
};
? 父類的成員變量在前涉兽,子類的成員變量在后
成員訪問權限
? 成員訪問權限锣咒、繼承方式有3種
public:公共的,任何地方都可以訪問(struct默認)
protected:子類內部胁后、當前類內部可以訪問
private:私有的挟阻,只有當前類內部可以訪問(class默認)
? 子類內部訪問父類成員的權限琼娘,是以下2項中權限最小的那個
成員本身的訪問權限
上一級父類的繼承方式
? 開發(fā)中用的最多的繼承方式是public,這樣能保留父類原來的成員訪問權限
? 訪問權限不影響對象的內存布局
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 {
};
初始化列表
? 特點
一種便捷的初始化成員變量的方式
只能用在構造函數(shù)中
初始化順序只跟成員變量的聲明順序有關
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
- 初始化順序只跟成員變量的聲明順序有關
說明輸出和定義在類中成員的順序有關附鸽,和代碼Person(int age , int height):m_age(myAge()), m_height(myHeight())
定義的順序無關脱拼。
初始化列表與默認參數(shù)配合使用
? 如果函數(shù)聲明和實現(xiàn)是分離的
初始化列表只能寫在函數(shù)的實現(xià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);
}
構造函數(shù)的互相調用
struct Person {
int m_age;
int m_height;
/*Person() :Person(10, 20) {
}*/
Person() :Person(10, 20) {
// 創(chuàng)建了一個臨時的Person對象
// 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;
}
父類的構造函數(shù)
? 子類的構造函數(shù)默認會調用父類的無參構造函數(shù)
? 如果子類的構造函數(shù)顯式地調用了父類的有參構造函數(shù),就不會再去默認調用父類的無參構造函數(shù)
? 如果父類缺少無參構造函數(shù)坷备,子類的構造函數(shù)必須顯式調用父類的有參構造函數(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()
*/
- 子類的構造函數(shù)默認會調用父類的無參構造函數(shù)
構造熄浓、析構順序
多態(tài)
? 默認情況下,編譯器只會根據(jù)指針類型調用對應的函數(shù)省撑,不存在多態(tài)
? 多態(tài)是面向對象非常重要的一個特性
同一操作作用于不同的對象赌蔑,可以有不同的解釋,產生不同的執(zhí)行結果
在運行時竟秫,可以識別出真正的對象類型娃惯,調用對應子類中的函數(shù)
? 多態(tài)的要素
子類重寫父類的成員函數(shù)(override)
父類指針指向子類對象
利用父類指針調用重寫的成員函數(shù)
父類指針、子類指針
struct Person {
int m_age;
};
struct Student : public Person {
int m_score;
};
void test() {
// 父類指針 指向 子類對象
// Person *p = new Student();
// p->m_age = 10;
Student *p = (Student *) new Person();
p->m_age = 10;
p->m_score = 100;//危險鸿摇,動了age后面的4個字節(jié),修改了非自己內存的數(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;
}
};
- 同一操作作用于不同的對象潮孽,可以有不同的解釋,產生不同的執(zhí)行結果
//同一操作作用于不同的對象筷黔,可以有不同的解釋往史,產生不同的執(zhí)行結果
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()
*/
- 默認情況下,編譯器只會根據(jù)指針類型調用對應的函數(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)來實現(xiàn)
? 虛函數(shù):被virtual修飾的成員函數(shù)
? 只要在父類中聲明為虛函數(shù)椎例,子類中重寫的函數(shù)也自動變成虛函數(shù)(也就是說子類中可以省略virtual關鍵字)
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ù)的實現(xiàn)原理是虛表,這個虛表里面存儲著最終==需要調用的虛函數(shù)地址==请祖,這個虛表也叫虛函數(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
虛表里面存儲著最終需要調用的虛函數(shù)地址订歪,這個虛表也叫虛函數(shù)表
(x86環(huán)境的圖)
int main() {
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
}
下面我們通過上述代碼的內存數(shù)據(jù)及匯編指令,來窺探虛表的原理
cat堆內存數(shù)據(jù)
總結一下Animal
類由于存在虛函數(shù)virtual void speak()
和virtual void run()
所以Animal
對象創(chuàng)建完成后肆捕,前8個字節(jié)是一個地址指針刷晋,指向了一個虛表的地址
由于有兩個虛函數(shù),所以指向的地址慎陵,前8位speak函數(shù)的地址眼虱,后8個字節(jié)為run函數(shù)的地址。
Animal
虛表地址之后席纽,跟著為屬性成員的地址捏悬,movl $0x14, 0x8(%rax)
修改m_age
的內存地址的值為20.
0x10fe79035 <+85>: callq *(%rcx)
及 0x10fe79041 <+97>: callq *0x8(%rcx)
都是間接調用,先找到虛表地址润梯,從內存地址中过牙,取出函數(shù)地址間接調用到對應類對象真實實現(xiàn)函數(shù)的地址上。