父類和子類指針
1 父類指針可以指向子類對(duì)象顿天,這是安全的犀忱,開(kāi)發(fā)中經(jīng)常用到拣展,繼承方式必須是public方式。
2 子類指針指向父類是不安全的梭灿,因?yàn)樽宇愔羔樋赡茉L問(wèn)到父類以外的數(shù)據(jù)画侣,而子類對(duì)象并沒(méi)有創(chuàng)建。
class Person {
public:
int m_age;
};
class Student : public Person {
public:
int m_score;
};
int main() {
//父類指針指向子類對(duì)象堡妒,安全
Person *p = (Person *) new Student();
p->m_age = 10;
//子類指針指向父類對(duì)象配乱,不安全
Student *s = (Student *) new Person();
//指向父類對(duì)象的子類指針訪問(wèn)了自己的成員變量,指針指向超出了范圍
s->m_score = 10;
return 0;
}
多態(tài)
多態(tài)是面向?qū)ο笠粋€(gè)非常重要的特性皮迟,同一操作作用于不同的對(duì)象搬泥,產(chǎn)生不同的執(zhí)行結(jié)果,在運(yùn)行時(shí)万栅,可以識(shí)別出真正的對(duì)象類型,調(diào)用對(duì)應(yīng)子類的函數(shù)西疤。產(chǎn)生多態(tài)的條件如下:子類重寫父類的成員函數(shù)烦粒,父類指針指向子類對(duì)象,利用父類指針調(diào)用重寫的成員函數(shù),這個(gè)成員函數(shù)必須是由Virtual修飾的成員函數(shù),父類只要聲明了Virtual代赁,子類自動(dòng)轉(zhuǎn)為Virtual函數(shù)
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
};
class Dog : public Animal {
public:
void run() {
cout << "Dog::run()" << endl;
}
};
class ErHa : public Dog {
public:
void run() {
cout << "ErHa::run()" << endl;
}
};
int main() {
Dog *dog0 = new Dog();
dog0->run(); //調(diào)用dog下的run函數(shù)
Dog *dog1 = new ErHa();
dog1->run();//調(diào)用ErHa下的run函數(shù)
return 0;
}
以上就是虛函數(shù)實(shí)現(xiàn)的列子扰她,我們?cè)龠M(jìn)一步,為什么不加Virtual 就不能實(shí)現(xiàn)多態(tài)了芭碍,加了Virtual 就能實(shí)現(xiàn)多態(tài)呢徒役,多態(tài)的實(shí)現(xiàn)是靠虛表實(shí)現(xiàn)的。我們先看看這幾個(gè)類的大小
//8
cout << sizeof(Dog) << endl;
//8
cout << sizeof(ErHa) << endl;
//8
cout << sizeof(Animal) << endl;
這幾個(gè)類sizeof大小是8窖壕,我們這是64位的忧勿,其實(shí)這個(gè)新增加的大小就是虛表的地址杉女,指向虛表,而且這個(gè)虛表地址在類的最前面鸳吸,而虛表里面存著本類虛函數(shù)的地址熏挎,從而進(jìn)行真正的調(diào)用,每一個(gè)類只有一份虛表,多個(gè)對(duì)象共享一份虛表晌砾,無(wú)論這個(gè)對(duì)象是在堆還是棧還是全局對(duì)象坎拐。我們可以通過(guò)反匯編和內(nèi)存調(diào)試也能看出來(lái),這里我就不演示了养匈。當(dāng)父類實(shí)現(xiàn)了虛函數(shù)哼勇,而子類沒(méi)有實(shí)現(xiàn)該虛函數(shù)的時(shí)候,我們來(lái)看看這個(gè)情況:
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
virtual void speak() {
cout << "Animal::speak()" << endl;
}
};
class Dog : public Animal {
public:
int m_age; //dog的age
};
int main() {
Animal *animal = new Animal();
animal->run();會(huì)調(diào)用Animal::run()
animal->speak();會(huì)調(diào)用Animal::speak()
Dog *dog0 = new Dog();
dog0->run(); //會(huì)調(diào)用Animal::run()
dog0->speak(); //會(huì)調(diào)用Animal::speak()
return 0;
}
當(dāng)子類沒(méi)有重寫父類虛函數(shù)的時(shí)候呕乎,它也會(huì)調(diào)用父類的虛函數(shù)积担,其底層實(shí)現(xiàn)也是通過(guò)虛表查找到函數(shù)調(diào)用,也就是子類即時(shí)沒(méi)有虛函數(shù)楣嘁,也有自己的虛表磅轻,我反匯編看到此時(shí)父類子類的虛表地址一樣,可能不同的平臺(tái)有不同的處理逐虚,虛表沒(méi)有繼承一說(shuō)聋溜。如果子類想調(diào)用父類的虛函數(shù)方法的時(shí)候,應(yīng)該顯示調(diào)用叭爱,注意C++ 沒(méi)有super等類似關(guān)鍵字撮躁,正確調(diào)用如下:
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
virtual void speak() {
cout << "Animal::speak()" << endl;
}
};
class Dog : public Animal {
public:
int m_age; //dog的age
void run() {
Animal::run(); //直接用類::顯示調(diào)用
cout << "Dog::run()" << endl;
}
void speak() {
Animal::speak(); //直接用類::顯示調(diào)用
cout << "Dog::speak()" << endl;
}
};
int main() {
Dog *dog0 = new Dog();
dog0->run();
dog0->speak();
return 0;
}
含有虛虛函數(shù)實(shí)現(xiàn)的父類時(shí)候,父類的析構(gòu)函數(shù)也需要聲明為virtual函數(shù)买雾,此時(shí)析構(gòu)函數(shù)變成了虛析構(gòu)函數(shù)把曼,這樣才能夠保證銷毀對(duì)象的時(shí)候父類子類析構(gòu)函數(shù)調(diào)用,保證析構(gòu)的完整性,子類可不加(virtaul)漓穿。
純虛函數(shù)
沒(méi)有函數(shù)體嗤军,且初始化為0的虛函數(shù),用來(lái)定義接口規(guī)范
class Animal {
public:
virtual void speak() = 0;
virtual void run() = 0;
};
含有純虛函數(shù)的類是抽象類晃危,不可以實(shí)例化叙赚,不能創(chuàng)建對(duì)象,抽象類成也可以包含其它非純虛函數(shù),以及其他成員變量僚饭,抽象類的指針可以指向子類對(duì)象,如果父類是抽象類震叮,子類沒(méi)有完全實(shí)現(xiàn)純虛函數(shù),那么這個(gè)子類依然是抽象類
多繼承
C++允許一個(gè)類繼承多個(gè)類鳍鸵,可以擁有多個(gè)類的特性苇瓣,多繼承增加了設(shè)計(jì)的復(fù)雜性,不建議使用偿乖。
#include <iostream>
using namespace std;
class Student {
public:
int m_score;
Student(int score = 0) :m_score(score) { }
void study() {
cout << "Student::study() - score = " << m_score << endl;
}
~Student() {
cout << "~Student" << endl;
}
};
class Worker {
public:
int m_salary;
Worker(int salary = 0) :m_salary(salary) { }
void work() {
cout << "Worker::work() - salary = " << m_salary << endl;
}
~Worker() {
cout << "~Worker" << endl;
}
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
Undergraduate(
int score = 0,
int salary = 0,
int grade = 0) :Student(score), Worker(salary), m_grade(grade) {
}
void play() {
cout << "Undergraduate::play() - grade = " << m_grade << endl;
}
~Undergraduate() {
cout << "~Undergraduate" << endl;
}
};
int main() {
{
Undergraduate ug;
ug.m_score = 100;
ug.m_salary = 2000;
ug.m_grade = 4;
ug.study();
ug.work();
ug.play();
}
cout << sizeof(Undergraduate) << endl;
return 0;
}
注意:這種多繼承击罪,父類的成員變量在子類前面哲嘲,先繼承誰(shuí),誰(shuí)的成員就在前面外邓。多繼承的構(gòu)造函數(shù)一樣需要使用初始化列表調(diào)用父類構(gòu)造函數(shù),
父類如果都含有虛函數(shù)撤蚊,子類多繼承多個(gè)父類后,子類對(duì)象會(huì)產(chǎn)生多個(gè)虛函數(shù)表损话,順序跟繼承順序有關(guān)侦啸。
#include <iostream>
using namespace std;
class Student {
public:
virtual void study() {
cout << "Student::study()" << endl;
}
};
class Worker {
public:
virtual void work() {
cout << "Worker::work()" << endl;
}
};
class Undergraduate : public Student, public Worker {
public:
void study() {
cout << "Undergraduate::study()" << endl;
}
void work() {
cout << "Undergraduate::work()" << endl;
}
void play() {
cout << "Undergraduate::play()" << endl;
}
};
int main() {
//含有16個(gè)字節(jié),因?yàn)橛?張?zhí)摫? cout << sizeof(Undergraduate) << endl;
Student *stu = new Undergraduate();
stu->study();
Worker *worker = new Undergraduate();
worker->work();
return 0;
}
同名成員
C++允許同名成員函數(shù)丧枪,同名成員變量光涂,子類不會(huì)覆蓋,訪問(wèn)的時(shí)候加上類名表示作用域拧烦,如下所示:
#include <iostream>
using namespace std;
class Student {
public:
int m_age;
};
class Worker {
public:
int m_age;
};
class Undergraduate : public Student, public Worker {
public:
int m_age;
};
int main() {
Undergraduate ug;
ug.m_age = 10;
ug.Student::m_age = 20;
ug.Worker::m_age = 30;
ug.Undergraduate::m_age = 40;
//這里等于12
cout << sizeof(Undergraduate) << endl;
return 0;
}
虛繼承
虛繼承是為了解決菱形繼承帶來(lái)的成員變量冗余忘闻,重復(fù)。而且最底層子類因?yàn)槎x性無(wú)法訪問(wèn)基類的的成員變量恋博。我們先來(lái)看看菱形繼承:
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student : public Person {
public:
int m_score;
};
class Worker : public Person {
public:
int m_salary;
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
};
int main() {
Undergraduate ug;
ug.m_grade = 10;
ug.m_score = 20;
ug.Student::m_age = 20;
ug.Worker::m_age = 30;
cout << sizeof(Undergraduate) << endl; //20
return 0;
}
這里我們看到Undergraduate的大小是20,因?yàn)檫@樣繼承Undergraduate的父類兩個(gè)類都有m_age成員變量,而且訪問(wèn)的時(shí)候我們需要通過(guò)作用域去訪問(wèn)齐佳,直接訪問(wèn)會(huì)報(bào)錯(cuò)。這種繼承方式债沮,基類的成員變量在最底層子類就冗余了炼吴,沒(méi)有必要,為了解決這種問(wèn)題疫衩,可以使用虛繼承硅蹦。加上virtual關(guān)鍵字:
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student :virtual public Person {
public:
int m_score;
};
class Worker :virtual public Person {
public:
int m_salary;
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
};
class Person1
{
};
int main() {
Undergraduate ug;
ug.m_grade = 10;
ug.m_score = 20;
ug.m_age = 30;
return 0;
}
此時(shí)三個(gè)類比如:Student、Worker闷煤、Undergraduate都有了虛函數(shù)表指針童芹,此時(shí)成員變量m_age在最底層子類只有一份內(nèi)存。此時(shí)Person類被稱為虛基類鲤拿。