目录
一、多态的概念及构成
1.1概念
从表面意思上来说,就是多种形态,具体来说就是去完成某个行为,当不同的对象去完成时会产生不同的状态。那么运用到类中,多态是在不同的继承关系的类对象去调用同一个函数,产生了不同的行为。
例如:坐飞机,高铁等,买同一趟的班次,有的人花的价钱却不一样。
1.2多态的构成条件(继承+虚函数)
在程序中,构成多态是在继承中发生,同时还有两个条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
光看概念还是懵懂,那么还得来了解虚函数是啥。
二、虚函数和虚函数重写
2.1虚函数和虚函数重写的概念
虚函数:在类中,被virtual修饰的函数就叫做虚函数
虚函数的重写(覆盖):
①派生类有一个跟基类完全相同的虚函数
②完全相同指的是他们的返回值类型、函数名字、参数列表完全相同
满足这两点,就可以说子类虚函数构成重写。
直接上一个多态例子:
#include <iostream>
using namespace std;
class Person
{
public:
virtual void rice()//虚函数
{
cout << "吃两碗饭" << endl;
}
};
class Student : public Person
{
public:
virtual void rice()//虚函数,对基类虚函数重写
{
cout << "吃一碗饭" << endl;
}
};
int main()
{
Person p;
Person& ps = p;//基类引用基类
ps.rice();//基类引用调用基类虚函数
Student s;
ps = s;//ps 最开始引用了基类对象 p。无论后来如何操作,ps 指向的始终是基类对象 p。即使将派生类对象 s赋值给 ps;,
//也只是将派生类对象的部分成员切片到了基类对象中,但 ps 本身依然是一个基类引用,
ps.rice();//因此调用 ps.rice() 时会调用基类 Person 中的 rice() 函数,而不是派生类 Student 中的版本。
Person& pp = s;
pp.rice();
pp = p;//同理pp 最开始引用了派生类对象s。无论后来如何操作,pp 指向的始终是派生类类对象 s
pp.rice();//因此调用 pp.rice() 时会调用派生类 Student 中的rice版本。
//指针就不一样了,指针是指向谁就调用谁的,因为指针指向的是对象的地址。
Person* sp = &p;//基类指针指向基类
sp->rice();//基类指针调用基类虚函数
sp = &s;//基类指针指向子类中父类的成员
sp->rice();//基类指针调用子类中父类的虚函数
return 0;
}
输出结果:
由上述多态可以总结:
1.基类引用调用虚函数时,调用其初始化引用中的虚函数,无论后来如何操作,始终不变。
2.基类指针调用虚函数时,指向谁就调用谁的。
同时要注意的是多态调用和普通调用的区别,普通调用规则:
1.在多态中,子类调用子类自己的方法为普通调用
2.非多态中,就是普通调用
普通调用就只跟对象类型有关了,对象类型是属于谁的就调用谁的。
了解了多态,虚函数的概念使用,对此,虚函数还有一些扩展,来继续学习吧
2.2虚函数的"异变"(协变+析构重写)
①子类虚函数重写时,不加关键字virtual也构成虚函数重写,其依然是虚函数,但是父类的关键字virtual不能省略。注意:只要父类的virtual未省略,其所有直接继承、非直接继承中子类虚函数重写都可不加virtual,其依然是虚函数。例如:
class Person
{
public:
virtual void rice()//虚函数
{
cout << "吃两碗饭" << endl;
}
};
class Student : public Person
{
public:
void rice()//子类虚函数的重写不加virtual依然构成重写
{
cout << "吃一碗饭" << endl;
}
};
② 协变(基类与派生类虚函数返回值类型不同)
当派生类重写基类虚函数时,其返回值类型与基类的虚函数返回值类型可以不同,构成这样的条件是,有额外的继承关系,且当前基类虚函数的返回值类型为额外的基类对象的指针或者引用,派生类虚函数的返回值类型为额外的派生类对象的指针或者引用。
#include <iostream>
using namespace std;
//额外的继承关系
class A
{};
class B : public A
{};
class Person
{
public:
//virtual A* rice()//基类虚函数返回额外的派生类对象的引用指针
//{
// cout << "吃两碗饭" << endl;
// return new A;
//}
virtual const A& rice()//基类虚函数返回额外的派生类对象的引用
{
cout << "吃两碗饭" << endl;
return A();//构造匿名对象并返回,因为该匿名对象在函数结束时会销毁,
//所以返回时会生成临时对象,匿名对象给给临时对象,而临时对象具有常属性,所以返回类型需要加const
}
};
class Student : public Person
{
public:
//B* rice()//派生类虚函数返回额外的派生类对象的指针
//{
// cout << "吃一碗饭" << endl;
// return new B;
//}
const B& rice()//派生类虚函数返回额外的派生类对象的引用
{
cout << "吃一碗饭" << endl;
return B();
}
};
int main()
{
Person p;
Person& ps = p;
ps.rice();
Person* sp = &p;
sp->rice();
Student stu;
sp = &stu;
sp->rice();
return 0;
}
输出结果:
按照协变的规则,其明显违反了多态的规则,但就是能支持,没办法,C++的语法就是这么复杂,在这里协变又可以看做一个特例。
③析构函数的重写(基类与派生类析构函数的名字不同)
如果基类析构函数是虚函数,那么子类的析构函数就构成对父类析构函数的重写。这是为什么呢,明明他们的函数名称不同为何构成重写?
实则,编译器会在编译后,对析构函数的名称统一处理成destructor,这样一来,他们的名称就相同了,从而构成了重写
#include <iostream>
using namespace std;
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
}