一.产生背景

先看下面的例子:

#include <iostream>
using namespace std; //基类People
class People{
public:
People(char *name, int age);
void display();
protected:
char *m_name;
int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
} //派生类Teacher
class Teacher: public People{
public:
Teacher(char *name, int age, int salary);
void display();
private:
int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
} int main(){
People *p = new People("王志刚", );
p -> display(); p = new Teacher("赵宏佳", , );
p -> display(); return ;
}

运行结果:

王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。

我们直观上认为,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,这符合人们的思维习惯。但是本例的运行结果却告诉我们,当基类指针 p 指向派生类 Teacher 的对象时,虽然使用了 Teacher 的成员变量,但是却没有使用它的成员函数,导致输出结果不伦不类(赵宏佳本来是一名老师,输出结果却显示人家是个无业游民),不符合我们的预期。

也就是说:

基类指针只能访问子类的成员变量,但不能访问子类的成员函数。

为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

修改上面的代码,将People类的display函数修改成虚函数:

#include <iostream>
using namespace std; //基类People
class People{
public:
People(char *name, int age);
virtual void display(); //声明为虚函数
protected:
char *m_name;
int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
} //派生类Teacher
class Teacher: public People{
public:
Teacher(char *name, int age, int salary);
virtual void display(); //声明为虚函数
private:
int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
} int main(){
People *p = new People("王志刚", );
p -> display(); p = new Teacher("赵宏佳", , );
p -> display(); return ;
}

再次运行:

王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)

多态是面向对象编程的主要特征之一,C++中虚函数的唯一用处就是构成多态。

C++提供多态的目的是:可以通过基类指针对所有子类(包括直接子类和间接子类)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量

二.借助引用也可以实现多态

int main(){
People p("王志刚", );
Teacher t("赵宏佳", , ); People &rp = p;
People &rt = t; rp.display();
rt.display(); return ;
}

运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

三.多态的其它用途

#include <iostream>
using namespace std; //军队
class Troops{
public:
virtual void fight(){ cout<<"Strike back!"<<endl; }
}; //陆军
class Army: public Troops{
public:
void fight(){ cout<<"--Army is fighting!"<<endl; }
};
//99A主战坦克
class _99A: public Army{
public:
void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
};
//武直10武装直升机
class WZ_10: public Army{
public:
void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
};
//长剑10巡航导弹
class CJ_10: public Army{
public:
void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
}; //空军
class AirForce: public Troops{
public:
void fight(){ cout<<"--AirForce is fighting!"<<endl; }
};
//J-20隐形歼击机
class J_20: public AirForce{
public:
void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
};
//CH5无人机
class CH_5: public AirForce{
public:
void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
};
//轰6K轰炸机
class H_6K: public AirForce{
public:
void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
}; int main(){
Troops *p = new Troops;
p ->fight();
//陆军
p = new Army;
p ->fight();
p = new _99A;
p -> fight();
p = new WZ_10;
p -> fight();
p = new CJ_10;
p -> fight();
//空军
p = new AirForce;
p -> fight();
p = new J_20;
p -> fight();
p = new CH_5;
p -> fight();
p = new H_6K;
p -> fight(); return ;
}

运行结果:

Strike back!
--Army is fighting!
----99A(Tank) is fighting!
----WZ-10(Helicopter) is fighting!
----CJ-10(Missile) is fighting!
--AirForce is fighting!
----J-20(Fighter Plane) is fighting!
----CH-5(UAV) is fighting!
----H-6K(Bomber) is fighting!

这个例子中的派生类比较多,如果不使用多态,那么就需要定义多个指针变量,很容易造成混乱;而有了多态,只需要一个指针变量 p 就可以调用所有派生类的虚函数。

从这个例子中也可以发现,对于具有复杂继承关系的大中型程序,多态可以增加其灵活性,让代码更具有表现力。

C++语言基础(11)-多态的更多相关文章

  1. C语言基础(11)-随机数发生器

    一. rand() rand是一个C语言库函数,功能是生成一个随机数.rand需要一个不同的种子,才能生成不同的随机数. 二. srand(int seed) rand需要一个不同的种子,才能生成不同 ...

  2. C语言基础 (11) 结构体 ,共用体 枚举 typedef

    1 课堂回顾 作用域与生命周期 2 static 局部变量 2 打字游戏 3 内存分区代码分析 4 结构体基本操作 (复合类型[自定义类型 #include <stdio.h> #incl ...

  3. Java语言基础(11)

    1 构造方法 构造方法是一种特殊的方法,只有在创建对象的时候才被调用,用来执行初始化的操作,比如给属性赋值... 1) 构造方法名字跟类名一致,没有返回值也就没有返回值类型 2) 格式: 类名(参数列 ...

  4. C语言基础11

    函数指针的定义: 函数类型 (标识符 指针变量名)(形参列表) void  printHello( ); void printHello( ){ printf("hello world!!! ...

  5. Java入门 - 语言基础 - 11.switch_case

    原文地址:http://www.work100.net/training/java-switch-case.html 更多教程:光束云 - 免费课程 switch_case 序号 文内章节 视频 1 ...

  6. C++基础 (8) 第八天 数组指针 模板指针 C语言中的多态 模板函数

    1昨日回顾 2 多态的练习-圆的图形 3多态的练习-程序员薪资 4员工管理案例-抽象类和技术员工的实现 employee.h: employee.cpp: technician.h: technici ...

  7. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  8. GO学习-(11) Go语言基础之map

    Go语言基础之map Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现. map map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能 ...

  9. D06——C语言基础学PYTHON

    C语言基础学习PYTHON——基础学习D06 20180821内容纲要: 面向对象初级学习 1 面向对象 2 类 (1)封装 (2)继承 (3)多态 3 小结 4 练习:选课系统 5 课外拓展:答题系 ...

随机推荐

  1. luogu P1194 买礼物

    题目描述 又到了一年一度的明明生日了,明明想要买B样东西,巧的是,这B样东西价格都是A元. 但是,商店老板说最近有促销活动,也就是: 如果你买了第I样东西,再买第J样,那么就可以只花K[I,J]元,更 ...

  2. python3-开发面试题(python)6.23基础篇(2)

    1.请至少列举5个 PEP8 规范(越多越好). 一.代码编排 1.缩进.4个空格的缩进,不使用Tap,更不能混合使用Tap和空格 2.每行最大长度79,换行可以使用反斜杠,最好使用圆括号.换行点要在 ...

  3. 1.5(学习笔记)Cookie

    一.Cookie简介 Cookie是网站发送的一小段数据,在用户访问浏览网站时通过浏览器存储在用户的计算机上. 主要用于记录一些用户状态信息,例如记录用户的账号,当前所在地等,根据这些信息网站 可以提 ...

  4. <摘录>Linux下动态共享库加载时的搜索路径详解

    对动态库的实际应用还不太熟悉的读者可能曾经遇到过类似“error while loading shared libraries”这样的错误,这是典型的因为需要的动态库不在动态链接器ld.so的搜索路径 ...

  5. [转] matlab调用opencv函数的配置

    原文地址百度账户 aleasa123 方式1 1.  首先保证vs2010能正确调用opencv函数, 2.  Matlab中选择编译器,操作如下: 打开matlab2012,输入mex –setup ...

  6. Session集中式管理

          Asp.net Session集中式管理主要有StateServer(状态服务器).Sqlserver(数据库服务器).自定义(如Redis缓存服务器)等,本文主要介绍StateServe ...

  7. Android中的线程池 ThreadPoolExecutor

    线程池的优点: 重用线程池中的线程,避免因为线程的创建和销毁带来的性能消耗 能有效的控制线程的最大并发数,避免大量的线程之间因抢占系统资源而导致的阻塞现象 能够对线程进行简单的管理,并提供定时执行以及 ...

  8. linux过滤ip地址

    一.系统版本 [root@zabbix-server tmp]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 二.用awk ...

  9. dx12 memory management

    https://msdn.microsoft.com/en-us/library/windows/desktop/dn508285(v=vs.85).aspx Map with D3D11_MAP_W ...

  10. Laravel 5系列教程二:路由,视图,控制器工作流程

    免费视频教程地址https://laravist.com/series/laravel-5-basic 上一篇教程我们走了那么长的路,终于把Laravel安装好了,这一篇教程我们就要进入Laravel ...