C++面向对象之多态的实现和应用详解

前言

本文主要给大家介绍的是关于C++面向对象之多态的实现和应用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

多态

大家应该都听过C++三大特性之一多态,那么什么多态呢?多态有什么用?通俗一点来讲->

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。当多态应用形参类型的时候,可以接受更多的类型。当多态用于返回值类型的时候,可以返回更多类型的数据。多态可以让你的代码拥有更好的扩展性。

多态分两种分别为静态多态和动态多态:

  • 静态多态:静态多态就是重载,因为是在编译期决议确定,所以称为静态多态。
  • 动态多态:动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运行时决议确定,所以称为动态多态。

而我们主要今天来看动态多态的问题。比如我们来看下面的代码,就是简单的动态多态:

class Person
{
public:
 virtual void BuyTickets()
 {
 cout << " 买票" << endl;
 } 

protected:
 string _name; // 姓名
}; 

class Student : public Person
{
public:
 virtual void BuyTickets()
 {
 cout << " 买票-半价 " << endl;
 } 

protected:
 int _num; //学号
}; 

void Fun(Person& p)
{
 p.BuyTickets();
} 

void Test()
{
 Person p;
 Student s;
 Fun(p);
 Fun(s);
}
int main()
{
 Test();
 system("pause");
 return 0;
} 

构成多态的四大条件: (缺一不可)

1.不在同一作用域(分别在父类和子类)

2.函数名相等/参数相等/返回值相同/(协变除外)

3.基类函数必须有virtual关键字

4.访问修饰符可以不同

具体多态是如何实现的?? 这里我们先从虚函数表这个知识点讲起,每一个带有虚函数的对象都会有一个虚函数表,虚函数表里存的是函数指针,然后调用的时候,指针回去虚函数表里面访问查找。对于这个知识点我的另外一个博客很详细的讲解到,大家可以先看看这个:http://www.jb51.net/article/123308.htm

然后我们来了解一下重写是什么东西?

重写的过程

如果这块还是不理解,你可以看我专门写虚函数那片博客,仔细看一定会看懂的.

接下来多态的原理我们就明白了吧. 发生重写之后,下一次父类指针指向我调用fun()函数的时候,它调用到的就是子类的fun()函数,其实多态就是这么简单,只要理解重写就理解多态. 虚函数表是我们必须掌握的一个知识点.

通过汇编来分析多态的实现

好了,我们继续往下走,刚刚我们从虚函数表这方面,探究了多态的实现,现在我们再从汇编的角度再来看多态是如何实现的。

我们来看一段新的代码:

class Person
{
public:
 virtual void BuyTickets()
 {
 cout << " 买票" << endl;
 } 

protected:
 string _name; // 姓名
}; 

class Student : public Person
{
public:
 virtual void BuyTickets()
 {
 cout << " 买票-半价 " << endl;
 } 

protected:
 int _num; //学号
};
void Fun(Person& p)
{
 p.BuyTickets();
} 

void Test()
{
 Person p;
 Student q;
 Person* ptr = &q;
 p.BuyTickets();
 ptr->BuyTickets();
}
int main()
{
 Test();
 system("pause");
 return 0;
} 

打开我们的反汇编窗口:

这里我们看到用指向子类的父类类型指针调用BuyTickets函数和直接用对象调用汇编代码相差巨大,一个只有2句话,一个那么长,这是因为在发生多态时当你用指针调用时,系统不知道你要用哪一个函数,因为这里有多态现象,所以系统只能老实的去虚函数表里查找,所以才会有这么多的代码,接下来我们来解释一下这些汇编,来看看系统是调用虚表的。

这里我们就关心到了那四个红色的句子,可以看到这里一直都是想讲虚函数表的地址传给系统,然后再传this指针,就可以调用哪个函数了。蓝色的就是一个小知识~ 知道有这么个东西就好了.

虚函数是在基类中定义的,目的是不确定它的派生类的具体行为。例:

  1. 定义一个基类:class Animal//动物。它的函数为breathe()//呼吸。
  2. 再定义一个类class Fish//鱼 。它的函数也为breathe()
  3. 再定义一个类class Sheep //羊。它的函数也为breathe()
  4. 为了简化代码,将Fish,Sheep定义成基类Animal的派生类。

然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸空气。所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数。具本的函数在子类中分别定义。程序一般运行时,找到类,如果它有基类,再找它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数。派生类也叫子类。基类也叫父类。这就是虚函数的产生,和类的多态性(breathe)的体现。

一般情况下(没有涉及virtual函数),当我们用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。

当设计到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。

现在我们来一个小练习:

#include<iostream>
#include<Windows.h>
using namespace std; 

class A
{
public:
 void foo()
 {
 printf("1\n");
 }
 virtual void fun()
 {
 printf("2\n");
 }
};
class B : public A
{
public:
 void foo()
 {
 printf("3\n");
 }
 void fun()
 {
 printf("4\n");
 }
};
int main(void)
{
 A a;
 B b;
 A *p = &a;
 p->foo();
 p->fun();
 p = &b;
 p->foo();
 p->fun();
 system("pause");
 return 0;
} 

这道题的运行结果分别是 1 2 1 4,,现在我们来分析为什么?

首先当一个父类类型指针指向父类时,我们应该知道这里没有多态,该怎么调用就怎么调用,所以调用了父类里面的foo函数和fun函数。现在我们重点来看后面这个,现在B继承了A,我们先判断这里是否有多态现象(1.父类和子类是否有重写现象 2.是否有父类类型的指针指向子类),现在很明显子类的fun函数重写了父类的fun函数,所以现在p->fun()调用的就是子类的fun函数,然后foo函数,根本不构成多态,所以这里指针类型是什么那个对象就按那个对象调用。总结一下当你碰到关于继承的问题,首先判断它里面是否有多态现象,如果没有那就根据指针/引用类型调用。如果有多态的话,一定要注意根据指针/引用的指向对象判断。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Go语言到底有没有引用传参(对比 C++ )

    C++ 中三种参数传递方式 值传递: 最常见的一种传参方式,函数的形参是实参的拷贝,函数中改变形参不会影响到函数外部的形参.一般是函数内部修改参数而又不希望影响到调用者的时候会采用值传递. 指针传递 形参是指向实参地址的一个指针,顾名思义,在函数中对形参指向的内容操作,实参本身会被修改. 引用传递 在 C++ 中,引用是变量的别名,实际上是同一个东西,在内存中也存在同一个地址.换句话说,不管在哪里对引用操作,都相当直接操作被引用的变量. 下面看 demo: #include <iostream>

  • 关于C++中void*的小作用浅析

    本文主要给大家分享了关于C++中void*的一些你可能不了解的小作用,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 先来看一段代码: #include <iostream> #include <string> using namespace std; void o(int* x, void* y){ cout << *x << endl; cout << x << endl; cout << *(int

  • 关于C++的强制类型转换浅析

    前言 一说起强制类型转换大家都很熟悉,相信很多学习完C++的朋友还在使用C语言的强制类型的方式 (类型)变量. C++其实也具有自己的一套强制类型转换它们分明是:static_cast  reinterpret_cast  const_cast  dynamic_cast四种类型. 那么肯定会有人好奇C++是不是闲,C语言的强制类型用的舒舒服服的,为什么要新推出来这几个? 新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换.C++中风格是static_cast<typ

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • C/C++实现控制台输出不同颜色字体的方法

    本文实例讲述了C/C++实现控制台输出不同颜色字体的方法.分享给大家供大家参考,具体如下: 在控制台输出不同颜色的字 效果 代码: #include "stdio.h" #include "windows.h" int main(int argn, char **argv) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_GREEN); printf("Hello&q

  • C++ 中cerr和cout的区别实例详解

    C++ 中cerr和cout的区别实例详解 前言: cerrThe object controls unbuffered insertions to the standard error output as a byte stream. Once the object is nstructed, the expression cerr.flags & unitbuf is nonzero. Example // iostream_cerr.cpp // compile with: /EHsc /

  • C++ 实现哈希表的实例

    C++ 实现哈希表的实例 该散列表的散列函数采用了除法散列函数.乘法散列函数.全域散列函数,每一个槽都是使用有序单向链表实现. 实现代码: LinkNode.h #include<iostream> using namespace std; class Link; class LinkNode { private: int key; LinkNode* next; friend Link; public: LinkNode():key(-1),next(NULL){} LinkNode(int

  • C++面向对象之多态的实现和应用详解

    前言 本文主要给大家介绍的是关于C++面向对象之多态的实现和应用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 多态 大家应该都听过C++三大特性之一多态,那么什么多态呢?多态有什么用?通俗一点来讲-> 多态性可以简单地概括为"一个接口,多种方法",程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念.当多态应用形参类型的时候,可以接受更多的类型.当多态用于返回值类型的时候,可以返回更多类型的数据.多态可以让你的代码拥有更好的扩展性. 多态分

  • Java语言面向对象编程思想之类与对象实例详解

    在初学者学Java的时候,面向对象很难让人搞懂,那么今天小编就来为大家把这个思想来为大家用极为简单的方法理解吧. 首先我们来简单的阐述面向对象的思想. 面向对象: 官方的语言很抽象,我们把官方的解释和定义抛开.想想,自己有什么,对!!我们自己有手脚眼口鼻等一系列的器官.来把自己所具有的器官就可以看作我们的属性,自己是不是可以喜怒哀乐和嬉笑怒骂,这些是不是我们的行为,那么自己的具有的属性加自己有的行为就称为一个对象. 注意!!我们自己,一个个体是一个对象,因为,你是你,我是我,我们虽然有相同的,但

  • C++中多态的定义及实现详解

    1. 多态概念 1.1 概念 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态. 举个栗子:比如买票,当普通人买票时,是全价买票:学生买票时,是半价买票:军人买票时是优先买票.同一个事情针对不同的人或情况有不同的结果或形态. 2. 多态的定义及实现 2.1 多态的构成条件 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为.比如Student继承了Person. Person对象买票全价,Student对象买票半价. 注意:那么在继

  • Java 多态中继承的转型详解与用法分析

    目录 一.前言 二.转型 向上转型 向下转型 三.instanceof运算符 instanceof的用处 instanceof的使用格式: 一.前言 前面我们学习了多态的概述和使用,现在我们来学习前面剩下的转型问题. 二.转型

  • java中多态概念、实现原理详解

    一.什么是多态? 1.多态的定义 指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用) 2.多态的作用 消除类型之间的耦合关系 3.多态的说明 近代网络小说泛滥,我们可以用它来举一个例子 某日你看见你手机上有多部小说同时更新了,比如有大主宰,雪鹰领主,龙王传说-在这里我们可以描述成如下: 小说a=大主宰 小说b=雪鹰领主 小说c=龙王传说 - 这里所表现的就是多态,大主宰,雪鹰领主,龙王传说都是小说的子类,我们仅仅可以通过小说这个

  • C#面向对象特征的具体实现及作用详解

    众所周知,面向对象编程的特点为:封装.继承.多态.C#是一门完全面向对象的语言,由于比Java推出的时间还要晚,所以对面向对象的思想的体现比Java还要完美,那么在C#中如何体现封装.继承和多态呢?下面举例并进行说明. 1.封装 封装的好处有以下几点: ①数据不外泄,可以做到一定的保护 ②类的使用者不用考虑具体的数据运算,方便 ③程序结构性强,层次清晰,便于维护 对相关的字段.方法进行封装固然对面向对象编程起到不可缺少的重要作用,但并不代表不可以访问类或者说具体的实例化对象中的内容,而且为使用者

  • PHP面向对象程序设计重载(overloading)操作详解

    本文实例讲述了PHP面向对象程序设计重载(overloading)操作.分享给大家供大家参考,具体如下: 重载 PHP中的"重载"与其它绝大多数面向对象语言不同,只是他们都是用的相同的名词而已.传统的"重载"是用于提供多个同名的 类方法,但各方法的参数类型和个数不同. PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法.当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用.是通过魔术方法

  • PHP面向对象程序设计之构造方法和析构方法详解

    本文实例讲述了PHP面向对象程序设计之构造方法和析构方法.分享给大家供大家参考,具体如下: 构造方法和析构方法是对象中的两个特殊方法,它们都与对象的生命周期有关.构造方法是对象创建完成后第一个被对象自动调用的方法,这是我们在对象中使用构造方法的原因.而析构方法是对象在销毁之前最后一个被对象自动调用的方法,这也是我们在对象中使用析构方法的原因.所以通常使用构造方法完成一些对象的初始化工作,使用析构方法完成一些对象在销毁之前的清理工作. 1.构造方法 在每个声明的类中都有一个呗称为构造方法的特殊成员

  • JS面向对象编程实现的Tab选项卡案例详解

    本文实例讲述了JS面向对象编程实现的Tab选项卡.分享给大家供大家参考,具体如下: Tab选项卡案例 下面是一个简单面向过程的Tab选项卡. <!DOCTYPE html> <html> <head> <style> #tabBox input { background: #F6F3F3; border: 1px solid #FF0000; } #tabBox .active { background: #E9D4D4; } #tabBox div { w

  • Java中的多态、抽象类和接口详解

    目录 1.多态 1.1 向上转型 1.2 向下转型 1.3 实现多态的条件 1.4多态的特点与使用 1.5多态的应用 以父类类型作为方法的参数 使用父类型作为方法的返回值 1.6 多态的注意点 2.抽象类 2.1 abstract关键字 2.2 抽象方法和普通方法的区别 2.3 抽象类和普通类的区别 2.4 本质 2.5 抽象类局限 3.接口 3.1 定义接口 3.2 使用接口 3.3 实现多个接口 3.4 jdk8接口新特性 1.多态 一个特定类型的变量,可以引用多个不同类型的对象,并且能自动

随机推荐