C++ 多态性初步了解

一 前提引入

  在说多态之前,我们先来看一段代码,猜测代码输出

class Animal {
public :
    Animal(const string &name) : __name(name) {}
    void run() {
        cout << "I don't know how to run" << endl;
    }
protected :
    string __name;
};


class Cat : public Animal {
public :    
    Cat() : Animal("cat") {}
    void run() {
        cout << "I can run with four legs" << endl;
    }
};

int main() {
    Cat a;
    Animal &b = a;
    Animal *c = &a;

    a.run();
    b.run();
    c->run();

    return 0;
}

   观察代码可知,我们定义了父类Animal子类Cat,并且子类public公有继承自父类,在Cat子类和Animal父类中均定义了函数run()。之后我们在主函数进行实例化等操作,那么代码的输出是什么呢?
  代码输出如下:

在这里插入图片描述
  结合输出和代码分析:Cat类实例化出来的a对象,调用其run()方法,对应的是Cat类中的run()方法,但是对于后面的定义的引用b和指针c(注意这里的引用和指针,类型对应的都是父类Animal)虽然指向对象a,但是并没有调用Cat类中的run()方法,而是调用了父类Animal中的run()方法。


  现在我们对代码进行小的修改,在父类Animal的run()方法前面加上virtual关键字修饰,将run()函数声明为虚函数,并且在Cat类的run()后加上override关键字,main函数内容保持不变。之后我们再来执行代码,观察代码的输出。

class Animal {
public :
    Animal(const string &name) : __name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
protected :
    string __name;
};


class Cat : public Animal {
public :    
    Cat() : Animal("cat") {}
    void run() override {
        cout << "I can run with four legs" << endl;
    }
};

  代码输出如下:

输出2
  观察输出可知:虽然b和c都是父类类型的引用和指针,但执行的都是对象a的成员方法。
  其实,以上的代码中就体现了C++中的多态性,多态中最关键的就是 virtual关键字修饰普通成员函数使之成为虚函数。

二 多态性

  当出现父类指针或引用指向子类对象,并且通过该指针或者引用调用子类对象中的成员方法时,为了分清到底调用的是父类中定义的方法还是子类中的方法,只需要记住一句话:普通成员方法跟着前面指针或者引用的类型走,对于virtual修饰的虚函数是跟着后面的对象走。
  这也是为什么上面的例子中,run()方法添加virtual关键字修饰为虚函数之后,用父类类型的指针或者引用调用子类对象中的run()方法的时候,调用的方法就是子类中的方法(虚函数跟着后面的对象a走)

在这里插入图片描述


1. 为什么要用父类的指针指向子类对象,子类指针指向子类对象不就行了吗?

  在上面的说了那么多,都是因为用指向父类的指针或引用指向子类对象才导致了那么多事情?那么我直接用与子类对应的指针和引用指向子类对象不就行了吗?这样的话调用的肯定是子类中的方法,就没有那么多麻烦的事情了。Emmmm~,其实小编最开始的时候也是这样想的,但这其实还是因为没有理解多态的真正作用。
  这里直接引用一片文章:为什么不直接用子类引用指向子类对象,而用父类引用指向子类对象

  1. 实际工程代码开发的时候,当拿到一个对象,我们有时候不知道这个对象的具体类型是什么,但这些对象都有一个共同的父类,因此运用多态性质,我们可以用父类的指针或者引用来调用该对象中的方法。
  2. 更多的时候我们不太关注这个对象的类型是什么,只要可以调用这个对象中的方法就行。
  3. 利用多态,父类的指针和引用可以对一类对象进行操作,而不是一个对象进行操作。

2. 重载overload、重写override和重定义redefining之间的区别

  重载overload:在同一个类中,函数的返回值类型和函数名称都相同,但是函数的参数不完全相同,编译器会根据函数的参数列表,生成名称不同的预处理函数,所以没有体现多态性,或者说编译时多态。
  在重载中,最典型的就是构造函数的重载,有:无参构造函数,一个参数的转换构造函数,还有多个函数的构造函数等等,在实例化对象的时候,根据传入的参数匹配相应的构造函数完成对象的实例化。
  重写override:一般在父子类中,函数的返回值类型、名称、参数完全一致,子类对父类中的虚函数重写,也可称为覆盖,运行时多态。
  重定义redefining:一般出现父子类中,函数的名称、返回值类型相同,但是函数参数列表可相同也可不同,子类中重定义了父类中同名函数中的代码逻辑。


3. 父类的析构函数一定是虚函数

  若父类的虚构函数不是虚函数,带来的后果是显而易见的,那就是内存泄漏
  比如子类在继承父类数据的基础上,又添加了一个数组或者其他成员变量。若父类析构函数不是虚函数,那么在析构的时候,调用的就是父类中的析构函数,父类的析构函数析构时,自然不会析构子类中有但是父类中没有的成员变量,那么自然就会造成内存泄漏的问题。


三 纯虚函数

在这里插入图片描述
  上图中可以看出,抽象类Animal中的run()方法没有实现,对于这样的类,我们没有办法实例化对象,那么问题来了,一个类连实例化生成对象都不行,那可以干嘛?答案是:定义接口

纯虚函数
  接口在C++和Java中都很重要。
  Java中一个类不允许有多个父类,粗俗的说,就是一个儿子不能有多个父亲,但是接口作为一种特殊的类,可以由多个子类继承。所以说Java中的interface接口,弥补了一个类不允许有多个父类的缺陷(也不算缺陷吧,就是实现了之前完成不了的操作)
  此外C++是允许一个类有多个父类的,但是这样会带了很多意想不到的麻烦,因此并不推崇,如果真的要弄的话,还是使用接口吧。
  说句题外话,也正是因为Java中一个类最多只有一个父类,所以在Java中才有super关键字,用于指代父类,而C++中并没有super关键字,因为C++允许一个类有多个父类,用super关键字指代父类,你究竟想要指代那个父类呢?这显然不确定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值