第七章 纯虚函数、抽象类、多继承、菱形继承、虚继承、虚基类
纯虚函数
- 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范
- 抽象类(Abstract Class)
- 就是:含有纯虚函数的类,不可以实例化(不可以创建对象)
- 抽象类也可以包含非纯虚函数
- 如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类;反之,如过父类是抽象类,子类完全实现纯虚函数,那么这个子类就不是抽象类。
- 抽象类不可以被实例化
class Animal {
public:
//纯虚函数
virtual void speak() = 0;
virtual void run() = 0;
//抽象类也可以包含非纯虚函数
void test() {
}
};
//父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类
class Cat : public Animal {
public:
void speak() {
cout << "Cat-speak()" << endl;
}
};
//完全实现纯虚函数,该子类就不是抽象类
class Dog : public Animal {
public:
void speak() {
cout << "Dog-speak()" << endl;
}
void run() {
cout << "Dog-run()" << endl;
}
};
//应用
//含有纯虚函数的类不能被实例化
//Animal *ani = new Animal();
//也不可以实例化,因为该子类没有完全实现父类的纯虚函数
//Animal *cat = new Cat();
//完全实现纯虚函数,该子类就不是抽象类
Animal *dog = new Dog();
多继承
-
C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
- 父类在内存中的顺序跟Undergraduate继承顺序有关
- 打印ug占用字节数为12个字节。
-
多继承体系下的构造函数调用
class Student { int m_score; public: Student(int score) { this->m_score = score; } }; class Worker { int m_salary; public: Worker( int salary) { this->m_salary = salary; } }; class Undergraduate : public Student,public Worker { public: Undergraduate(int score,int salary) : Student(score),Worker(salary){ } };
- 多继承-虚函数
-
如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表
class Student { public: int m_score; virtual void study() { cout << "Student::study()" << endl; } }; class Worker { public: int m_salary; virtual void work() { cout << "Worker::work()" << endl; } }; class Undergraduate : public Student,public Worker { public: int m_grade; void study() { cout << "Undergraduate::study()" << endl; } void work() { cout << "Undergraduate::work()" << endl; } void play() { cout << "Undergraduate::play()" << endl; } }; int main() { //调用Student相关的虚表 Student *student = new Undergraduate(); student->m_score = 100; student->study(); //调用worker相关的虚表 Worker *worker = new Undergraduate(); worker->m_salary = 2000; worker->work(); cout << sizeof(Undergraduate) << endl; getchar(); return 0; } //打印 Undergraduate::study() Undergraduate::work() 20
- 20个字节,本来ug只有3个成员变量,应该是12个字节
- 但是多了8个字节,那么这8个字节分别是每4个字节指向一张虚表
- 每继承一个父类就多一张虚表
- 一张虚表存放Student相关的虚函数
- 一张虚表存放Worker相关的虚函数
-
-
同名函数
class Student { public: void eat() { cout << "Student::eat()" << endl; } }; class Worker { public: void eat() { cout << "Worker::eat()" << endl; } }; class Undergraduate : public Student, public Worker { public: void eat() { cout << "Undergraduate::eat()" << endl; } }; int main() { /*Undergraduate ug; ug.Student::eat(); ug.Worker::eat(); ug.Undergraduate::eat(); ug.eat();*/ Undergraduate *ug2 = new Undergraduate(); ug2->Student::eat(); ug2->Worker::eat(); ug2->Undergraduate::eat(); ug2->eat(); //打印 /*Student::eat() Worker::eat() Undergraduate::eat() Undergraduate::eat()*/ getchar(); return 0; }
- 同名成员变量
- 内存中顺序跟继承顺序有关。
- 菱形继承
- 菱形继承带来的问题
- 最底下子类从基类继承的成员变量冗余、重复
- 最底下子类无法访问基类的成员,有二义性
-
举例说明
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; //20个字节,但是我们只需要1个m_age即可 cout << sizeof(Undergraduate) << endl; //可以 ug.Student::m_age = 20; ug.Worker::m_age = 30; //二义性,因为他不知道m_age来自谁 //ug.m_age = 10; getchar(); return 0; }
- 从上代码可以看出ug无法直接访问m_age成员,因为具有二义性,不知道m_age来自谁
- Undergraduate类占用20个字节,很显然重复了一个m_age,那么问题来了,能不能Worker跟Student公用一个m_age呢?—虚继承
- 菱形继承带来的问题
- 虚继承
- 虚继承可以解决菱形继承带来的问题
- Person类被称为虚基类
- 呈现样式:在继承的父类前添加一个virtual字段
- 虚继承也会增加4个字节存放一张虚表的指针。
多继承的应用
- 多继承:增加程序的复杂度
- 多继承有一个很好的用途:
- 一个类实现多个接口(Java)
- 一个类遵守多份协议(OC)
- 一个类继承多个抽象类(C++)
- 场景
- 兼职中心,招聘一些人来做兼职,兼职的岗位很多种(保姆、老师、开挖掘机),应聘的人种类也很多(学生、XXX)
-
代码示例
//保姆的条件 class JobBaomu { virtual void clean() = 0; virtual void cook() = 0; }; //老师的条件 class JobTeacher { virtual void playBaseball() = 0; virtual void playFootball() = 0; }; //应聘者1 class SalesMan : public JobBaomu { void clean() { } void cook() { } }; //应聘者2: class Student : public JobBaomu, public JobTeacher { void clean() { } void cook() { } void playBaseball() { } void playFootball() { } };