多态
多态的概念: 当不同的对象调用同一个函数产生出不同的状态。
构成多态的条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如B继承了A。A对象产生的行为和B对象不同。
要构成多态还有两个条件:
- 1.必须通过基类的指针或者引用调用虚函数
- 2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
虚函数
被virtual 关键字修饰的成员函数。
class Person {
public:
virtual void show() {
cout << "父类" << endl;
}
};
只有非静态成员函数才可以是虚函数,解决菱形继承使用到的虚继承和这里没有关系。
虚函数的重写
虚函数的重写(覆盖)指的是子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的返回值类型、函数名字、参数列表完全相同,称子类的虚函数重写了父类的虚函数。
class Person {
public:
virtual void show() {
cout << "父类" << endl;
}
};
class Student : public Person {
public:
//子类的virtual 关键字可以不写,子类已经继承了父类的虚函数,默认已带有virtual属性
virtual void show() {
cout << "子类" << endl;
}
};
重写中的特例
协变
子类重写父类虚函数时,与父类虚函数返回值类型不同。即父类虚函数返回父类对象的指针或者引用,子类虚函数返回子类对象的指针或者引用时,称为协变。
class A {};
class B :public A {};
class Person {
public:
virtual A* show() {
cout << "父类" << endl;
}
};
class Student :virtual public Person {
public:
virtual B* show() {
cout << "子类" << endl;
}
};
析构函数
子类和父类中的析构函数虽然名字不同,但是同样构成隐藏(重定义),编译器对类析构和子类析构作统一处理,都默认看为 destructor() ,为同一个函数。
class Person {
public:
~Person(){}
};
class Student :virtual public Person {
public:
~Student(){} //构成隐藏
};
如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加virtual关键字,都与父类的析构函数构成重写,虽然父类与子类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则, 其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
override & final
final
final关键字修饰虚函数,禁止虚函数被重写,如果发生重写编译器会报错。
class Person {
public:
virtual A* show() final {
cout << "父类" << endl;
}
};
class Student :virtual public Person {
public:
virtual B* show() {
cout << "子类" << endl;
}
};
override
检查子类虚函数是否重写父类的虚函数,如果未重写则编译器报错。
class Person {
public:
virtual void show(int i) {
cout << "父类" << endl;
}
};
class Student :virtual public Person {
public:
virtual void show(char c) override {
cout << "子类" << endl;
}
};
此时编译器报错
重载、重写、重定义的区别
重载
- 两个函数在同一作用域
- 函数名相同/参数列表不同
重写(覆盖)
- 两个函数分别在基类和派生类的作用域
- 函数名/参数/返回值都必须相同(协变除外),两个函数必须是虚函数
重定义(隐藏)
- 两个函数分别在基类和派生类的作用域
- 函数名相同
- 两个基类和派生类的同名函数不构成重写就是重定义
抽象类
在虚函数的后写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。子类继承后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。纯虚函数规范了子类必须重写,另外纯虚函数体现出了接口继承。
class Person {
public:
virtual void show() = 0;
};
接口继承和实现继承
普通函数的继承是一种实现继承,子类继承了父类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,子类继承的是父类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。