第七章 纯虚函数、抽象类、多继承、菱形继承、虚继承、虚基类

纯虚函数

  1. 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范
  2. 抽象类(Abstract Class)
    1. 就是:含有纯虚函数的类,不可以实例化(不可以创建对象)
    2. 抽象类也可以包含非纯虚函数
    3. 如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类;反之,如过父类是抽象类,子类完全实现纯虚函数,那么这个子类就不是抽象类。
    4. 抽象类不可以被实例化
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();

多继承

  1. C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)

    图1

    1. 父类在内存中的顺序跟Undergraduate继承顺序有关
    2. 打印ug占用字节数为12个字节。
  2. 多继承体系下的构造函数调用

     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){
        
     	}
     };
    
  3. 多继承-虚函数
    1. 如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表

       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
      
      1. 20个字节,本来ug只有3个成员变量,应该是12个字节
      2. 但是多了8个字节,那么这8个字节分别是每4个字节指向一张虚表
      3. 每继承一个父类就多一张虚表
      4. 一张虚表存放Student相关的虚函数
      5. 一张虚表存放Worker相关的虚函数
  4. 同名函数

     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;
     }
    
  5. 同名成员变量 图1
    1. 内存中顺序跟继承顺序有关。
  6. 菱形继承
    1. 菱形继承带来的问题
      1. 最底下子类从基类继承的成员变量冗余、重复
      2. 最底下子类无法访问基类的成员,有二义性
        图1
    2. 举例说明

       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;
       }
      
      1. 从上代码可以看出ug无法直接访问m_age成员,因为具有二义性,不知道m_age来自谁
      2. Undergraduate类占用20个字节,很显然重复了一个m_age,那么问题来了,能不能Worker跟Student公用一个m_age呢?—虚继承
  7. 虚继承
    1. 虚继承可以解决菱形继承带来的问题
    2. Person类被称为虚基类
    3. 呈现样式:在继承的父类前添加一个virtual字段
    4. 虚继承也会增加4个字节存放一张虚表的指针。

    图1

多继承的应用

  1. 多继承:增加程序的复杂度
  2. 多继承有一个很好的用途:
    1. 一个类实现多个接口(Java)
    2. 一个类遵守多份协议(OC)
    3. 一个类继承多个抽象类(C++)
  3. 场景
    1. 兼职中心,招聘一些人来做兼职,兼职的岗位很多种(保姆、老师、开挖掘机),应聘的人种类也很多(学生、XXX)
  4. 代码示例

     //保姆的条件
     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() {
        
     	}
     };
    
Table of Contents