本文是小编巩固自身而作,如有错误,欢迎指出!
之前我们简单介绍了c++面向对象的三大属性,今天我们就来介绍其中之一——多态。
一.多态是什么
多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运⾏时多 态(动态多态),这⾥我们重点讲运⾏时多态,编译时多态(静态多态)和运⾏时多态(动态多态)。编译时 多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的 函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在 编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。
或者简单来说,多态就是:
同⼀个继承关系的下的不同类对象,去调用同⼀函数,产⽣了不同的行为。
二.多态
2.1多态的构成条件
多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。⽐如Student继承了 Person。Person对象买票全价,Student对象优惠买票。
其中就有构成多态的必要条件
• 必须是基类的指针或者引⽤调⽤虚函数
• 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖。
2.1.1虚函数
虚函数是面向对象编程中实现运行时多态的关键机制,通过virtual
关键字声明。当基类的指针或引用指向派生类对象时,虚函数确保调用的是实际对象的函数版本,而非指针或引用的静态类型所绑定的版本。
class person {
public:
virtual void buyticket() { cout << "买票-全价" << endl; }
};
class student : public person {
public:
virtual void buyticket() { cout << "买票-打折" << endl; }
};
void func(person* ptr)
{
// 这⾥可以看到虽然都是person指针ptr在调⽤buyticket
// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。
ptr->buyticket();
}
int main()
{
person ps;
student st;
func(&ps);
func(&st);
return 0;
}
就如上述代码所示,在不同的类中,在相同函数前加上visual,在调用时通过指针的区别就可以分辨出区别
2.1.2虚函数的重写与覆盖
虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值 类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。
上述代码其实就是虚函数的重写,那么其和重载之类的又有什么样的关系呢?
2.1.3虚函数经典例题
class A
{
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A
{
public:
void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0;
}
上述代码的运行结果是什么?
答案是B->1
下面分析原因
首先,p调用的是父类的函数test(),然后test()又调用了func()而p又是B类的指针,所以调用的是B的func(),但是我们要了解一点,虚函数是什么原理?是通过不同类型的指针找到不同的实现,所以是相当于call了A的函数,其val自然就是A,而在实现时又转向B,因此是B->。
2.1.4纯虚函数
在虚函数的后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被 派⽣类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例 化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了 派⽣类重写虚函数,因为不重写实例化不出对象。
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
2.1.5override和final关键字
在上述的学习中,我们了解了C++对重写的要求还是很严格的,但是由于其不会在编译阶段报错,导致不好排查错误源,因此C++11提供了override,可以帮助⽤户检测是否重写。如果我们不想让 派⽣类重写这个虚函数,那么可以⽤final去修饰。
// error C3668: “Benz::Drive”: 包含重写说明符“override”的⽅法没有重写任何基类⽅法
class Car {
public:
virtual void Dirve()
{}
};
class Benz :public Car {
public:
virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{
return 0;
}
// error C3248: “Car::Drive”: 声明为“final”的函数⽆法被“Benz::Drive”重写class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
return 0;
}
三.多态的原理
3.1虚函数表指针和虚函数表
要了解其中的原理,我们来思考一下下面代码的结果是什么
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
protected:
int _b = 1;
char _ch = 'x';
};
int main()
{
Base b;
cout << sizeof(b) << endl;
return 0;
}
有同学就好奇了,其根据之前学的对齐规则,应该结果是8?我们看看监视是什么情况。我们发现,其中多了一个东西_vfptr,这是什么呢?
对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代 表function)。⼀个含有虚函数的类中都⾄少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要 被放到这个类对象的虚函数表中,虚函数表也简称虚表。
满⾜多态条件后,底层 不再是编译时通过调⽤对象确定函数的地址,⽽是运⾏时到指向的对象的虚表中确定对应的虚函数的 地址,这样就实现了指针或引⽤指向基类就调⽤基类的虚函数,指向派⽣类就调⽤派⽣类对应的虚函数。
也就是说,所有的虚函数对应的实现都储存在一个名为虚函数表的东西中,通过指针类型的不同,来对应不同的实现。
本次的分享就到这里结束了,后续会继续更新,感谢阅读!