一、多态的概念
多态(Polymorphism)是面向对象编程(OOP)的三大核心特性之一(另两个为封装和继承)。它允许不同类的对象对同一消息作出不同的响应,即通过统一的接口调用不同实现。多态分为编译时多态(静态绑定)和运行时多态(动态绑定)。
人话:你是一个孩子,但你可以有很多与你相似的兄弟姐妹。(多胞胎)虽然是多胞胎但你们的性格啥的总有一点是不一样的。很相似但!=完全一样;
二、多态的类型
2.1静态多态(编译时的多态)(早期绑定)
以std作用域的流输出<<为列子:
/*int a = 1;
double b = 1.1;
cout << a << endl;
cout << b << endl;*/
//静态多态;
流输出本质也是个函数(operator<<()),可以看出a,和b,是两个不同的类型,但是用同一个<<都能给他俩打印出来,其实就是调用了<<的重载,重载的时候参数列表可能不同,返回值也可能不同,但函数名是相同的;这就构成了静态多态;
2.2动态多态(运行时的多态)(晚期绑定)
2.2.1动态多态的规则(必须满足的条件)
------------重点----------------------重点--------------------------------------------------重点---------------------------
1.必须是以基类指针或基类引用调用的虚函数;
2.被调用的虚函数必须完成重写;
上面提到了许多新名词,什么是虚函数?什么是重写?什么叫用基类指针去调用?
直接上代码:
#include <iostream>
using namespace std;
// 基类
class Animal {
public:
virtual void speak() {
cout << "Animal speaks" << endl;
}
};
// 派生类
class Dog : public Animal {
public:
void speak() override {
cout << "Dog barks" << endl;
}
};
// 派生类
class Cat : public Animal {
public:
void speak() override {
cout << "Cat meows" << endl;
}
};
int main() {
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
animal1->speak(); // 输出: Dog barks
animal2->speak(); // 输出: Cat meows
delete animal1;
delete animal2;
return 0;
}
如代码所示我构建了一个基类Animal,和派生类Dog和Cat,子类和父类都有同名的代码,例如speak()函数,而其函数的类型前有virtual的定义,没错有virtual就代表的这个函数其实是一个虚函数。
2.2.2虚函数的定义
概念:虚函数是面向对象编程(OOP)中的一个重要概念,主要用于实现运行时多态。在C++中,虚函数允许派生类重写基类的成员函数,并通过基类指针或引用调用派生类的实现。
virtual 返回类型 函数名(参数列表){};
人话:在基类中任意一个函数前加上virtual他就构成了虚函数
如果子类也有与其同名的同返回类型的同参数列表的(注意只要参数列表的对象类型和对象个数相等就行,名字不重要如他是int a另一个是int b也构成虚函数)遵循这三同原则的就构成了虚函数的重写。
2.2.3虚函数重写构成多态
虚函数在基类和派生类重写后,再用基类的指针或者基类的引用调用这个函数,就成了多态调用,
如上述代码speak()函数,在主函数中以基类指针生成了 Animal* animal1 = new Dog();
Animal* animal2 = new Cat();两个对象(注意派生类是能直接使用切割的方法传给父类的)
这里虽然都是Animal*指针调用speak()但是结果分别为Dog 与Cat他俩所对应虚函数函数体输出的结果;为什么会这样呢?我简单讲一下,其实在虚函数构成后其基类和子类构建出来的对象中会有一个专门存储虚函数的函数指针数组。他会指向你创建对象的虚函数。
强调一下:虚函数重写只是重写了函数体下面我写一个列子你们看一下选哪个:
class person
{
public:
virtual void func(int val = 0)
{
cout << "A->" << val << endl;
}
virtual person* p()
{
cout << "per" << endl;
return 0;
}
void text()
{
func();
}
virtual ~person()
{
cout << "~person()" << endl;
}
};
class student :public person
{
public:
virtual void func(int val = 1)
{
cout << "B->" << val << endl;
}
virtual student* p()
{
cout << "student" << endl;
return 0;
}
virtual ~student()//析构函数的虚函数的多态调用
{
cout << "~student()" << endl;
}
};
void func2(person* str)
{
str->p();
}
int main()
{
student* p = new student();
p->text();
delete p;//结果为B->0进行了虚函数的重写,但是虚函数重写只是重写了结构体;
}
结果为:B->0;首先p是一个student类的指针对象,在p调用text()函数时会使用从父类继承来的text(),而你要知道父类的text()的()中其实是隐藏了一个person* this的在调用时会把p转化成person*类的this,然后this->func()这就达成多态的第一个条件,使用基函数的指针调用虚函数,第二个条件:显然func()是重写过的他俩的参数唯一不一样的就是val=1和val=0(前面提了只要参数类型一样就行),所以构成多态,所以调用student的虚函数。
这个时候就有人问了调用的student的虚函数不应该生成的是B->1吗怎么是0啊//强调虚函数重写只是重写结构体也就是{ }里的东西。参数还是用的父类的val = 1;
2.2.4虚函数重写的其他问题
虚函数重写前提是三同原则(返回值,函数名,参数列表),但也有特例
2.2.4.1特例一协变
协变的概念:基类虚函数的返回值是基类对象的指针或引用virtual person(任意基类)*,派生类的虚函数的返回值是这个基类的派生类的指针或引用virtual student(任意与之相对的派生类)*,也构成虚函数的重写;
2.2.4.2特例二析构函数
我直接说了:基类和派生类的析构函数的函数名最后都会被编译器给改成destroyed()这个函数名如果在基类的洗后函数前加上virtual他就成了虚函数也构成多态(防止析构时只析构父类忘记析构子类)推荐把所有的继承类别的析构函数都写成虚函数;
防止下面情况发生:
person* a = new person();
person* b = new student();
delete a;
delete b;
如果析构函数不是虚函数不构成多态在delete删除b时会只析构父类的部分而子类不被析构;
不加virtual与加vitual的对比:


三、纯虚函数和抽象类
抽象类不能够实例化对象,而含有纯虚函数的类叫抽象类
纯虚函数就是在虚函数后写=0;
virtual void Drive ()=0;纯虚函数一般不写结构体应为没有用他存在的意义就是让他的派生类重写他;
1310

被折叠的 条评论
为什么被折叠?



