大家好,今天我们来讲讲我们c++中的一个关键知识,叫做多态。但是我们学习多态之前必须将我们前面学习过的继承学习过后才能学习。当然大家可能会先想什么叫多态,我们从名字上上看的话就是多种姿态嘛。毕竟看起来这么容易理解,但其实也可以这么理解我们的多态就是多种姿态,但是有分“静”和“动”这两种多态。这里的序言我就不讲那么多了,我们的关键知识放在下面的主要知识点来讲。
多态的概览
我们要学习多态,那么我们我们肯定要先了解什么叫做多态,是吧。那么我们先来看看比较官方的解释吧:多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态),这⾥我们重点讲运⾏时多态,编译时多态(静态多态)和运⾏时多态(动态多态)。编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。大家看着这上面的解释我们多态就是多种结果。然后我们静态就像我们前面的重载。那么我们静态更加白话一点的话就是,我们在开始传参数的时候让我们知道走那一天大路,然后我们编译的时候根据传的参数的不同来确定我们的结果是啥。然后这就是我们的静态多态。那么动态多态就是在我们传参的时候是一样的然后我们编译的时候形式是一样的,但是我们会在运行的时候根据参数的形式或者是否符合条件然后那么得出不同的结果。
我们来举个例子来看看:运⾏时多态,具体点就是去完成某个⾏为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。⽐如买票这个⾏为,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票(5折或75折);军⼈买票时是优先买票。再⽐如,同样是动物叫的⼀个⾏为(函数),传猫对象过去,就是”(>^ω^<)喵“,传狗对象过去,就是"汪汪"。我们看到上面的例子就是我们的动态多态了。是不是和我上面讲的很像啊。虽然我们传的的参数是一样的,但是具体的是不一样的。所以我们多态就是依据我们不同的参数或者条件来达到不同的效果。
多态的定义及实现
当我们看了上面的多态的概念的时候我们就要看看如何实现了。像我们前面学习的继承。是不是我们在继承的时候有些前置条件,比如说什么要写个什么名词啊,在什么前后啊,或者什么不能改变什么东西啊。是吧。那么我们的多态当然也不能拉下啊。所以我们接下来就看看实现多态的两个必要条件
实现多态还有两个必须重要条件
虚函数
接下来我们要讲的就是我们实现多态的两个重要条件中的必须要存在的虚函数。而虚函数首先我们要知道什么是虚函数啊,是吧?那么我们接下来看一看虚函数的解释是什么?虚函数,是指被 virtual 关键字修饰的 成员函数。 在某基类中声明为 virtual 并在一个或多个 派生类 中被重新定义的成员函数,用法格式为: virtual 函数返回类型 函数名(参数表) {函数体};实现 多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。然后大家可以看到我们这里说了实现虚函数其实就是很简单,在我们的返回类型前面写一个virtual就可以了。但是大家可能会想,虚函数是干什么用的呢?这里其实我们就可以看出虚函数其实就是为了实现面向对象中的多态,就是为了实现我们多态。我们在上面也看到了我们的序函数很简单,就只需要写一个virtual就可以了。
虽然说呢其实大家只需要简单的理解为虚函数是为了实现我们的多态而实现创造出来的,并且虽然说很简单,我们只需要在积累前面写一个virtual就可以了。至于我这里为什么说只需要在积累上面写一个virtual就可以了呢?因为大家我们之前说过我们这里是继承类呀,是吧?继承那么我只需要在基类写了之后,那么我的派生类是不是就直接继承下来的?所以如果依照我们上面的这个来实现的话,我们可以将其中的派生类中的virtual给删掉也是无伤大雅的。大家可以后面去自己实现一下,反正我这里是已经给大家实验过了,是可以的。然后我就不在这里表现出来了。
虚函数的重写/覆盖
接下来我们要讲的就是我们实现多态的第二个条件中的很重要的了。重写和覆盖,我们大家可以简单理解一下重写和覆盖什么意思啊,是不是就是再写一遍或者将原来的东西给覆盖一遍?当然这里是我们自己诉说的,那么实际情况是怎么样呢?我们接下来看看官方的解释是怎么说的。虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值 类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。不知道大家看到这个后然后再看我们的第一张图片是不是觉得很符合啊。我们的派生类与基类中的函数是一样的。除了实现的内容不一样外。其他的简直就像一个翻版,当然我们的那个继承名肯定是该有区别是有区别的。
注意:在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使⽤,不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态。这里我给大家再次说明了我们的派生类其实也可以不用写virtual的。然后大家可以看看下面的两个代码来实现加深一下印象:(1)第一个是我们的关于票价
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;
}
(2):第二个是我们关于动物的叫声的
class Animal
{
public:
virtual void talk() const
{}
};
class Dog : public Animal
{
public:
virtual void talk() const
{
std::cout << "汪汪" << std::endl;
}
};
class Cat : public Animal
{
public:
virtual void talk() const
{
std::cout << "(>^ω^<)喵" << std::endl;
}
};
void letsHear(const Animal& animal)
{
animal.talk();
}
int main()
{
Cat cat;
Dog dog;
letsHear(cat);
letsHear(dog);
return 0;
}
我想当大家看了上面的两个例子之后,其实对我们的多态的实现和一些简单的定义应该已经了解的差不多了,如果大家还感兴趣的话,可以先看一下我们下面的一个题目。这个题目可不简单呀,大家要多思考一下,因为这是有一个很坑的很多面试题都在使用的,大家可以看一下下面的题,然后呢我会先将答案给大家。这道题选 B:我想大多数的朋友会选择D,包括我们开始也是选着的D,但是最后看了讲解后才发现应该选着B的。这可是一道面试题哦。大家想想要是在面试的时候看到这么一道题,原本是信心满满的但是后面一看发现选错了,会不会很气啊,所以我这里就想和大家打个预防针。想让大家体验一下社会的险恶。然后我在下一篇关于多态的博客中再给大家解释说明。