C++ 多态
文章目录
1.多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成同一事情时会产生出不同的状态
多态是在继承关系
中不同的类对象,去调用同一函数,产生了不同的行为
2.多态的定义及实现
2.1 多态的构成条件
继承中要构成多态的两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
满足上述条件后,基类的指针指向基类对象则调用基类的虚函数,指向派生类对象则调用派生类的虚函数
2.2 虚函数定义
虚函数:被virtual修饰的类成员函数称为虚函数
class Person
{
public:
virtual void BuyTicket() {
cout << "普通人 - 正常排队 - 全价买票" << endl;}
};
2.3 虚函数的重写(覆盖)
虚函数的重写(覆盖)的条件:派生类中有一个跟基类完全相同
的虚函数(即派生类虚函数与基类虚函数的函数名字、参数列表、函数返回值完全相同,即三同+虚函数),称派生类的虚函数重写了基类的虚函数
注:派生类中重写基类的虚函数可以不加 virtual 关键字(因为基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,所以建议加上
虚函数重写的两个例外:协变和析构函数重写—这种情况也是虚函数重写
1.协变(基类与派生类虚函数返回值类型不同)
理解:派生类重写基类虚函数时,与基类虚函数返回值类型不同。即返回值必须为父子类类型的指针或引用时,称为协变
// A和B为父子类 class A { }; class B : public A { }; class Person//父类 { public: virtual A* BuyTicket()//返回值为父类(A)的指针 { cout << "普通人 - 全价买票" << endl; return new A; } }; class Student : public Person //子类 { public: virtual B* BuyTicket()//返回值为子类(B)的指针 { cout << "学生 - 半价买票" << endl; return new B; } }; void Func(Person* ptr) { // 多态 -- ptr指向基类对象则调用基类的虚函数,指向派生类对象则调用派生类的虚函数 ptr->BuyTicket(); } int main() { Person per; Func(&per);//打印普通人 - 全价买票 Student stu; Func(&stu);//打印学生 - 半价买票 return 0; }
2.析构函数的重写(基类与派生类析构函数的名字不同)
理解:如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
class Person { public: virtual ~Person() { cout << "~Person()" << endl; } // ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: virtual ~Student() { cout << "~Student()" << endl; } // ~Student() { cout << "~Student()" << endl; } }; // 基类和派生类的析构函数不是虚函数,它们是隐藏关系 // 基类和派生类的析构函数是虚函数,它们是重写关系 int main() { // 普通场景下,虚函数是否重写都是ok的 Person p; Student s; // new对象特殊场景 // 只有派生类Student的析构函数重写了Person的析构函数, // 下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。 Person* p1 = new Person; Person* p2 = new Student; delete p1; // p1->destructor + operator delete(p1) delete p2; // p2->destructor + operator delete(p2) return 0; }
2.4 重载、覆盖(重写)、隐藏(重定义)的对比
重载:overload,同一作用域,函数名相同,参数列表不同(个数/顺序/类型),与返回值无关
重写(覆盖):override,父子类两个虚函数完全相同,即三同+虚函数
两个函数分别在基类和派生类的作用域
两个函数必须是虚函数
函数名、参数列表、函数返回值都完全相同(协变例外,返回值可以不同)
两个例外:协变、析构函数重写
隐藏(重定义):不是重写就是重定义两个函数分别在基类和派生类的作用域
函数名相同
两个基类和派生类的同名函数不构成重写就是隐藏(重定义)
3.抽象类
3.1 抽象类的概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象
// 基类 - 抽象类 - 不能实例化出对象
class Car
{
public:
virtual void Drive() = 0; // 纯虚函数,不