赞
踩
以下代码在vs2019下的x86程序中,涉及指针为4bytes。
多态:多种形态。具体一些,就是不同的对象去做同样的行为,会产生出不同的状态。
例如:买车票的时候,当你是学生的时候,可能就会半价;当你是其他人的时候可能就是全价了。
//举例,这个代码可以暂时不用看懂 class Person { public: virtual void BuyTicket() { cout << "普通票---全价" << endl; } }; class Student : public Person { virtual void BuyTicket() { cout << "学生票---半价" << endl; } }; void func(Person& p) { p.BuyTicket(); } int main(void) { Person p1; Student s1; //不同的对象做同一件事,得到的状态不同 func(p1); func(s1); return 0; }
多态分为:静态的多态、动态的多态。
静态的多态:是在编译时实现的。例如函数重载,看上去像是在调用同一个函数有着不同的行为。
动态的多态:是在运行时实现的。基类的指针或者引用去调用同一个函数,传不同类型的对象会调用不同的函数。
1、多态是基于继承的,只有在有继承关系的体系中才能构成多态。
2、必须通过基类的指针或者引用去调用虚函数。
3、被调用的成员必须是虚函数,且派生类必须对基类的虚函数进行重写。
虚函数:被virtual
关键字修饰的非静态类成员。(ps:在返回值前面加virtual
)
虚函数的重写:派生类中有一个和基类基本相同的函数,这里的基本相同指,同样是虚函数并且返回值、函数名、参数都相同。而函数体内的内容不同,类似于将这个函数重新改写了,这种情况称为派生类的虚函数重写了基类的虚函数。
构成多态的一个例外:返回值不同,其他条件满足,并且这两个函数的返回值是继承关系(父子关系)的指针或者引用,这个时候也构成多态。这种情况称为协变
//协变也构成多态 //协变:返回值可以不同,但是必须是继承关系(父子关系) class Person { public: virtual Person* BuyTicket() { cout << "普通票---全价" << endl; return nullptr; } }; class Student : public Person { virtual Student* BuyTicket() //派生类中可以不是虚函数 { cout << "学生票---半价" << endl; return nullptr; } }; void func(Person& p) { p.BuyTicket(); } int main(void) { Person p1; Student s1; func(p1); func(s1); return 0; }
构成多态的另一个例外:基类成员是虚函数、而派生类成员可以不是虚函数,也就是不用virtual修饰,但是其他条件还是需要满足的。这样也构成多态,虽然派生类成员没有被virtual修饰,但它是先继承了基类虚函数的属性,再完成的重写,那么它也算是虚函数。
class Person { public: virtual void BuyTicket() { cout << "普通票---全价" << endl; } }; class Student : public Person { void BuyTicket() //派生类可以不是虚函数 { cout << "学生票---半价" << endl; } }; void func(Person& p) { p.BuyTicket(); } int main(void) { Person p1; Student s1; func(p1); func(s1); return 0; }
存在着一个场景,析构函数必须是虚函数:动态申请基类、派生类的对象,如果都交给了基类指针管理,那么析构函数需要是虚函数。
//存在着一种场景,析构函数必须是函数 //基类、派生类对象,都交给了基类指针管理。 class Person { public: virtual void BuyTicket() { cout << "普通票---全价" << endl; } ~Person() { cout << "~Person" << endl; } }; class Student : public Person { virtual void BuyTicket() { cout << "学生票---半价" << endl; } ~Student() { cout << "~Student" << endl; } }; void func(Person& p) { p.BuyTicket(); } int main(void) { //派生类对象交给了基类指针管理 Person* p2 = new Person; Person* s2 = new Student; delete p2; delete s2; return 0; }
运行结果:p2和s2都是调用基类的析构函数去清理资源。
派生类对象地址赋值给基类指针,虽然被切片了,但还是需要调用派生类自己的析构函数去清理资源。所以这并没有正确地调用析构函数,我们需要的是,S2调用Student自己的析构函数。析构函数的函数名,编译时会被统一处理成destructor
,并且它不需要参数和返回值,所以认为析构函数函数名、参数、返回值相同。那要使得析构函数构成多态,那么可以在用virtual修饰这两个析构函数,或者修饰基类的析构函数就可以了。
class Person { public: virtual void BuyTicket() { cout << "普通票---全价" << endl; } virtual ~Person() //析构函数修饰成虚函数 { cout << "~Person" << endl; } }; class Student : public Person { virtual void BuyTicket() { cout << "学生票---半价" << endl; } ~Student() { cout << "~Student" << endl; } }; void func(Person& p) { p.BuyTicket(); } int main(void) { //派生类对象交给了基类指针管理 Person* p2 = new Person; Person* s2 = new Student; delete p2; delete s2; return 0; }
这里调用了两次基类的析构函数,是因为,派生类的析构函数调用之后,会自动调用一次基类的析构函数去析构被继承下来的基类部分。
总结:如果存在切片行为,那么析构函数必须是虚函数,使之构成多态。
final
:修饰虚函数,表示该虚函数不能被重写。
修饰方法:在基类虚函数参数括号后,函数体之前使用。
//关键字final
class Car
{
public:
virtual void Drive() final
{}
};
class Benz :public Car
{
public:
virtual void Drive()
{ cout << "Benz-舒适" << endl; }
};
【补充】
使用final
修饰类,那么会使得该类不能被继承。修饰方法:在基类类名之后,类体之前使用。
//关键字final
class Car final
{
public:
virtual void Drive()
{}
};
class Benz :public Car
{
public:
virtual void Drive()
{ cout << "Benz-舒适" << endl; }
};
override
:修饰虚函数,检查派生类的虚函数是否重写了基类的某个虚函数,如果没有重写编译报错。
修饰方式:在派生类虚函数参数括号后面,函数体之前使用。
//关键字override //使用override修饰派生类的虚函数,判断该虚函数是否重写,如果没有重写则会编译报错 class Car { public: virtual void Drive() {} }; class Benz :public Car { public: virtual void Drive(int i) override { //cout << "Benz-舒适" << endl; } };
重载:
1、两个函数在同一作用域中
2、要求函数名相同。参数个数、类型或者顺序不同
重写(覆盖):
1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域。
2、函数名、参数、返回值都必须相同(协变除外,返回值可以不同,但是一定是父子关系)
3、两个函数都是虚函数,或者基类中的那个函数是虚函数。
重定义(隐藏)
1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域
2、两个函数的函数名、参数相同
3、两个基类和派生类的同名函数不构成重写就是重定义
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。