虚函数
虚函数属于类的成员函数,只需在前面加上virtual关键字
virtual double getArea() {return 0;}
作用:
在公有继承时派生类会对虚函数进行重定义,当派生类对象使用基类的指针或引用时将调用该对象的成员函数
该功能是通过虚函数表来实现的,虚函数表在编译阶段被创建,当一个类中有虚函数时,就会创建一个虚函数表并将虚函数的指针存入虚函数表,所以虚函数表是一个指针数组,用来存虚函数的指针。每个类都有自己的虚函数表,只是对于基类的虚函数,若继承类没有对其进行重写,则继承类虚函数表的指针也将指向基类虚函数指针的地址;若改写了,则会指向一个新地址。
来看一个例子:
#include <iostream>
using namespace std;
class A{
public:
virtual void display(){
cout << "A::display()" << endl;
}
};
class B : virtual public A{
public:
void display(){
cout << "B::display()" << endl;
}
};
int main()
{
A *myclass[] = {new A(), new B()};
for (auto ptr : myclass)
{
ptr->display();
delete ptr;
}
}
这里声明了一个类A的指针myclass来指向类B,虽然myclass是基类的指针只能指向基类的部分,但是虚表指针亦属于基类部分,所以myclass可以访问到对象B的虚表指针,而对象B已经重写了display函数,结果如下:
A::display()//第一次循环
A::~A()
B::display()//第二次循环
B::~B()
A::~A()
性质:
“一但为虚,永远为虚”
在公有继承中,只要某个类的成员被声明为虚函数,则其派生类中相同签名的函数无论是否有virtual,是否重新定义,都是虚函数
对刚才例子的扩展:
#include <iostream>
using namespace std;
class A{
public:
virtual void display(){
cout << "A::display()" << endl;
}
virtual ~A(){
cout << "A::~A()" << endl;
}
};
class B : virtual public A{
public:
void display(){
cout << "B::display()" << endl;
}
~B(){
cout << "B::~B()" << endl;
}
};
class C : virtual public A{
public:
void display(){
cout << "C::display()" << endl;
}
~C(){
cout << "C::~C()" << endl;
}
};
class D : virtual public C{
public:
void display(){
cout << "D::display()" << endl;
}
~D(){
cout << "D::~D()" << endl;
}
};
class E : virtual public D, virtual public B{//注意这里要顺序反一下
public:
void display(){
cout << "E::display()" << endl;
}
~E(){
cout << "E::~E()" << endl;
}
};
A *myclass[] = {new E()};
for (auto ptr : myclass)
{
ptr->display();
delete ptr;
}
注意析构顺序在同级之间是从右到左,由于类B和类D都从A派生出来,所以最后再析构类A
输出为:
E::display()
E::~E()
B::~B()//先类B
D::~D()//后类D
C::~C()
A::~A()
upcast和downcast
将子类看做父类对象称为upscast,父类看做子类称为downcast。将派生类指针(或引用)转换为基类指针(或引用)的过程称为upcasting,将基类指针(或引用)转换为派生类指针(或引用)的过程称为downcasting。
class B : public A {};
B b;
A *a = b;//upcast
A a;
B *b = a;//downcast
upcasting显隐式转换均合法;downcasting时必须显式转换。因为upcast是安全的,downcast是有风险的
多态
多态特指一个标识符指代不同类型的数据
c++中满足多态定义的标识符
静态绑定 | 动态绑定 | ||
函数重载 | 函数重载 | √ | |
操作符重载 | √ | ||
方法覆盖 | 虚函数 | √ | |
泛型 | 模版 | √ |
静态绑定
简单来说就是前面没有virtual,函数被覆盖或隐藏,如果用基类指针指向派生对象则会出问题
class Fruit{
public:
void say() {
printf("I'm a fruit!\n");
}
};
class Apple : public Fruit {
public:
void say() {
printf("I'm an apple!\n");
}
};
int main(){
Apple a;
Fruit *fPtr = &a;
a.say();
fPtr->say();
}
将会输出
I'm an apple!
I’m a fruit!
动态绑定
动态类型需满足
- 多态类型指针或引用
- 访问的成员是虚函数
在基类成员函数前加上virtual关键字,将函数声明为虚函数,该类及其子类都是多态类型。当多态类型指针(或引用)调用虚函数时,则会产生多态现象,即调用指针所指向的对象的成员函数
class Fruit{
public:
virtual void say() { printf("I'm a fruit!\n"); }
};
class Apple : public Fruit {
public:
void say() { printf("I'm an apple!\n"); }
};
int main(){
Apple a;
Fruit *fPtr = &a;
fPtr->say();
}
输出为
I’m an apple!
纯虚函数与抽象类
用“=0”作为虚函数申明的后缀,表示该函数是纯虚函数。一般纯虚函数不建议给定义,若是析构函数则必须提供定义。
virtual void eat() = 0;
定义或继承了至少一个纯虚函数的类为抽象类
- 抽象类不能被实例化
- 抽象类的子类也是抽象类,除非所有纯虚函数都被覆写为非纯虚函数
覆写语法如下:
virtual double travelTime(double distance) const = 0; // 纯虚函数
//覆写
double travelTime(double distance) const override{//由于纯虚函数有const,记得加const
return 0;
}
所以要防止一个类被创建可以在该类中添加纯虚函数,当然将该类的构造函数=default也可以做到同样的效果
抽象类为多态而生,能且仅能作为基类指针或引用,不能申明抽象类的对象,不能被显示转为抽象类对象,不能作为函数参数类型或者返回值
虚析构
基类声明的时候在析构函数前面加上virtual,则派生的析构函数会始终覆盖它(虽然析构函数时不继承的),这使得可以通过指向基类的指针delete动态分配的对象。任何包含虚函数的基类,其析构函数必须公开且虚,或受保护且非虚,否则容易内存泄露。
#include<iostream>
#include<string.h>
#include<cstring>
#include <fstream>
using namespace std;
class Creature {
public:
Creature(const int& hands, const int& legs) {
_hands = hands;
_legs = legs;
cout << "A Creature has been created!" << endl;
cout << "It has " << hands << " hand(s)!" << endl;
cout << "It has " << legs << " leg(s)!" << endl;
}
virtual ~Creature() { //这里是虚析构函数
cout << "Creature object exiled!" << endl;
}
int GetHands() const {
return _hands;
}
int GetLegs() const {
return _legs;
}
private:
int _hands;
int _legs;
};
class Beast : virtual public Creature {
public:
Beast(const int& hands, const int& legs, const string& name) : Creature(hands, legs) {
_name = name;
cout << "Its beast name is " << _name << endl;
}
~Beast() {
cout << "Beast object exiled!" << endl;
}
string GetName() const {
return _name;
}
private:
string _name;
};
int main() {
cout << "*************** Testing class Beast ***************" << endl;
Creature *beast1 = new Beast(2, 4, "SpiderKing");//通过基类指针
cout << "Calling GetHands(): " << beast1->GetHands() << endl;
cout << "Calling GetLegs(): " << beast1->GetLegs() << endl;
delete beast1;
cout << endl;
}
RTTI
RTTI即运行时类型识别,程序可以使用基类的指针或者引用来检查这些指针或引用所指的对象的实际派生类型
typeid
使用typeid要包含头文件<typeinfo>
typeid(类型)或者 typeid(表达式)
dynamic_cast
可以安全的转换到其他类的指针和引用
dynamic_cast <新类型> (表达式)
final
指定某个虚函数不能在子类中杯覆盖,某个类不能被继承
struct Base {
virtual void foo();
};
struct A : Base {
void foo() final; // Base::foo 被覆盖而 A::foo 是最终覆盖函数
void bar() final; // 错误: bar 不能为 final 因为它非虚
};
struct B final : A { // struct B 为 final
void foo() override; // 错误:foo 不能被覆盖,因为它在 A 中是 final
};
struct C : B { // 错误:B 为 final
};