多态
概念
调用函数的多种形态,
多态构成条件
1)父子类完成虚函数的重写(三同:函数名,参数,返回值相同)
2)父类的指针或者引用调用虚函数
虚函数
被virtual修饰的类成员函数
注意
1)只有类的非静态成员函数前才可以加上virtual
2)虚函数的virtual与虚继承的virtual是同一个关键字。虚函数中的virtual是为了实现多态,虚继承的virtual是为了菱形继承中数据冗余和二义性。
虚函数的重写
也叫虚函数的覆盖
派生类中有一个完全相同的虚函数(返回值类型相同,函数名相同,参数列表相同),就叫做派生类的虚函数重写了基类的虚函数
结果:通过父类Person的指针或引用调用虚函数
父类指针要是指向父类对象则调用父类的虚函数
要是指向子类对象则调用子类虚函数
#include<iostream>
using namespace std;
class Person
{
public:virtual void BuyTicket(){cout << "买票半价" << endl;}
};
class Student :public Person
{
public:virtual void BuyTicket(){cout << "买票半价" << endl;}
};
void Func(Person& people)
{people.BuyTicket();
}
int main()
{Student s;Func(s);return 0;
}
注意
1)派生类的虚函数可以不用加上virtual关键字(不规范,建议加上)
虚函数的两个例外
1)协变
基类和派生类的返回值不同
当基类虚函数返回基类对象的指针或引用
派生类虚函数返回派生类对象的指针或引用
#include<iostream>
using namespace std;//基类
class A
{};
//子类
class B : public A
{};
//基类
class Person
{
public://返回基类A的指针virtual A* fun(){cout << "A* Person::f()" << endl;return new A;}
};
//子类
class Student : public Person
{
public://返回子类B的指针virtual B* fun(){cout << "B* Student::f()" << endl;return new B;}
};
void Func(Person& p)
{p.fun();
}
int main()
{Student s;Func(s);return 0;
}
2)析构函数的重写
基类和派生类的析构函数名字不同
当基类的析构函数是虚函数,只要派生类析构函数定义,就构成重写
作用:
要是没有重写,指向子类的指针只会调用父类的析构函数,有可能造成内存泄漏
推荐:
一般把父类的析构函数写成虚函数,反之发生内存泄漏
注意:
在继承中,子类和父类的析构函数会构成隐藏就是因为,虽然子类析构函数和父类的析构函数函数名虽然不同,但是为了构成重写,编译后的析构函数名字会统一处理为destructor();
void test2()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;//本来希望指向父类的指针析构父类//指向子类的指针去析构子类//因此需要虚函数,构成重写
}
c++11
1)override:检查派生类虚函数是否重写了基类的某个虚函数,没有则编译错误
//子类
class Student : public Person
{
public://子类完成了父类虚函数的重写,编译通过virtual void BuyTicket() override{cout << "买票-半价" << endl;}
};
2)final:修饰虚函数,表示该虚函数不能再重写
//父类
class Person
{
public://被final修饰,该虚函数不能再被重写virtual void BuyTicket() final{cout << "买票-全价" << endl;}
};
易错例题
纯虚函数
在虚函数后面写上=0
抽象类(接口类):包含纯虚函数的类
特点
1)抽象类不能实例化对象
2)强制派生类重写虚函数(不重写则会继承纯虚函数,又变为抽象类了又不能实例化对象了)
3)用抽象类(父类)的指针调用虚函数
作用
1)抽象类能更好的
#include <iostream>
using namespace std;
//抽象类(接口类)
class Car
{
public://纯虚函数virtual void Drive() = 0;
};
int main()
{Car c; //抽象类不能实例化出对象,errorreturn 0;
}
#include <iostream>
using namespace std;
//抽象类(接口类)
class Car
{
public://纯虚函数virtual void Drive() = 0;
};
//派生类
class Benz : public Car
{
public://重写纯虚函数virtual void Drive(){cout << "Benz-舒适" << endl;}
};
//派生类
class BMV : public Car
{
public://重写纯虚函数virtual void Drive(){cout << "BMV-操控" << endl;}
};
int main()
{//派生类重写了纯虚函数,可以实例化出对象Benz b1;BMV b2;//不同对象用基类指针调用Drive函数,完成不同的行为Car* p1 = &b1;Car* p2 = &b2;p1->Drive(); //Benz-舒适p2->Drive(); //BMV-操控return 0;
}
抽象类作用
1)用来表达没有实例对象的抽象类型
2)强制子类重写虚函数(同override)
原理
多态调用:运行时,到指向对象的虚表中找虚函数调用
普通调用:编译时,调用的对象是那个类型就调用它的函数
虚表:虚函数表,存虚函数的地址,目标实现多态
虚基表:存的是当前位置距离虚基类部分的偏移量,解决菱形继承和二义性的问题
虚函数和普通函数一样:在代码段中,不是在虚表中的(虚表内的是虚函数的地址
虚表在常量区