C++对多态的理解
c++的基本概念

C++是一种广泛使用的高级编程语言,它是由Bjarne Stroustrup于1983年开发出来的。
C++在C语言的基础上增加了许多新的特性,包括面向对象编程、异常处理、STL(标准模板库)等。
C++的基本概念包括:面向对象编程:C++是一种面向对象的编程语言,这意味着C++程序是由对象组成的,这些对象可以包含数据和操作数据的函数。
面向对象编程的核心概念包括类(class)、对象(object)、继承(inheritance)、多态(polymorphism)和封装(encapsulation)。
类和对象:类是定义对象的模板,而对象是类的实例。
类定义了对象的属性和方法。
例如,如果我们有一个“汽车”类,那么我们可以创建一个“宝马”对象,这个对象将继承“汽车”类的所有属性和方法。
继承:继承是面向对象编程中的一个重要概念,它允许我们基于已有的类创建新的类。
新类的对象将继承原有类的所有属性和方法,同时还可以添加新的属性和方法。
多态:多态是指一个接口或超类可以引用多种实际类型的对象。
这意味着,对于一个特定的接口或超类,其实际类型可以是多种不同的类。
封装:封装是指将数据和操作数据的函数捆绑在一起,形成一个独立的实体,即对象。
这样可以隐藏数据的细节,只通过对象的方法来访问数据。
异常处理:C++提供了异常处理机制,可以捕获和处理运行时错误。
异常是程序在运行时发生的问题,例如尝试打开一个不存在的文件。
STL(标准模板库):C++的STL是一组通用的模板类和函数,包括容器、迭代器、算法和函数对象。
这些组件可以极大地简化C++程序的编写。
内存管理:C++提供了对内存管理的精细控制,包括显式地分配和释放内存,以及智能指针等特性,可以帮助开发者避免内存泄漏和其他相关问题。
运算符重载:C++允许程序员重载运算符,这意味着程序员可以定义运算符的行为,以适应不同的类。
例如,程序员可以重载“+”运算符来定义两个自定义类型的对象的加法行为。
C语言中的多态

C语⾔中的多态⼀、多态的主要特点1、继承体系下。
继承:是⾯向对象最显著的⼀个特性。
继承是从已有的类中派⽣出新的类,新的类能吸收已有类的数据属性和⾏为,并能扩展新的能⼒,已有类被称为⽗类/基类,新增加的类被称作⼦类/派⽣类。
2、⼦类对⽗类的虚函数进⾏重写。
3、虚表。
在⾯向对象语⾔中,接⼝的多种不同现⽅式即为多态。
同⼀操作作⽤于不同的对象,可以有不同的解释,产⽣不同的执⾏结果,这就是多态性。
简单说就是允许基类的指针指向⼦类的对象。
⼆、代码实现1、C++中的继承与多态1 class Base2 {3 public:4 virtual void fun() {} //基类函数声明为虚函数5 int B1;6 };7 class Derived :public Base //Derived类公有继承Base类8 {9 public:10 virtual void fun() { //函数重写,此时基类函数可以声明为虚函数,也可以不声明11 cout << "D1.fun" << endl;12 }13 int D1;14 };15 int main(){16 Base b1; //创建⽗类对象17 Derived d1;//创建⼦类对象1819 Base *p1 = (Base *)&d1;//定义⼀个⽗类指针,并通过⽗类指针访问⼦类成员20 p1->fun();2122 Derived *p2 = dynamic_cast<Derived*> (&b1); //dynamic_cast⽤于将⼀个⽗类对象的指针转换为⼦类对象的指针或引⽤(动态转换)23 p2->fun();2425 getchar();26 return 0;27 }2. C语⾔实现C++的继承与多态1 typedef void(*FUNC)(); //定义⼀个函数指针来实现对成员函数的继承2 struct _Base //⽗类3 {4 FUNC _fun;//由于C语⾔中结构体不能包含函数,故借⽤函数指针在外⾯实现5 int _B1;6 };7 struct _Derived//⼦类8 {9 _Base _b1;//在⼦类中定义⼀个基类的对象即可实现对⽗类的继承10 int _D1;11 };12 void fb_() //⽗类的同名函数13 {14 printf("_b1:_fun()\n");15 }16 void fd_() //⼦类的同名函数17 {18 printf("_d1:_fun()\n");19 }20 int main() {21 _Base _b1;//定义⼀个⽗类对象_b122 _Derived _d1;定义⼀个⼦类对象_d12324 _b1._fun = fb_;//⽗类的对象调⽤⽗类的同名函数25 _d1._b1._fun = fd_;//⼦类的对象调⽤⼦类的同名函数2627 _Base *_p1 = &_b1;//定义⼀个⽗类指针指向⽗类的对象28 _p1-> _fun(); //调⽤⽗类的同名函数2930 _p1 = (_Base *)&_d1;//让⽗类指针指向⼦类的对象,由于类型不匹配所以要进⾏强转31 _p1->_fun(); //调⽤⼦类的同名函数3233 getchar();34 return 0;35 }。
多态的概念和作用(深入理解)

多态的概念和作⽤(深⼊理解)多态是⾯向对象的重要特性,简单点说:“⼀个接⼝,多种实现”,就是同⼀种事物表现出的多种形态。
编程其实就是⼀个将具体世界进⾏抽象化的过程,多态就是抽象化的⼀种体现,把⼀系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进⾏对话。
对不同类的对象发出相同的消息将会有不同的⾏为。
⽐如,你的⽼板让所有员⼯在九点钟开始⼯作, 他只要在九点钟的时候说:“开始⼯作”即可,⽽不需要对销售⼈员说:“开始销售⼯作”,对技术⼈员说:“开始技术⼯作”, 因为“员⼯”是⼀个抽象的事物, 只要是员⼯就可以开始⼯作,他知道这⼀点就⾏了。
⾄于每个员⼯,当然会各司其职,做各⾃的⼯作。
多态允许将⼦类的对象当作⽗类的对象使⽤,某⽗类型的引⽤指向其⼦类型的对象,调⽤的⽅法是该⼦类型的⽅法。
这⾥引⽤和调⽤⽅法的代码编译前就已经决定了,⽽引⽤所指向的对象可以在运⾏期间动态绑定。
再举个⽐较形象的例⼦:⽐如有⼀个函数是叫某个⼈来吃饭,函数要求传递的参数是⼈的对象,可是来了⼀个美国⼈,你看到的可能是⽤⼑和叉⼦在吃饭,⽽来了⼀个中国⼈你看到的可能是⽤筷⼦在吃饭,这就体现出了同样是⼀个⽅法,可以却产⽣了不同的形态,这就是多态!多态的作⽤:1. 应⽤程序不必为每⼀个派⽣类编写功能调⽤,只需要对抽象基类进⾏处理即可。
⼤⼤提⾼程序的可复⽤性。
//继承2. 派⽣类的功能可以被基类的⽅法或引⽤变量所调⽤,这叫向后兼容,可以提⾼可扩充性和可维护性。
//多态的真正作⽤,以前需要⽤switch实现----------------------------------------------------多态是⾯向对象程序设计和⾯向过程程序设计的主要区别之⼀,何谓多态?记得在CSDN⾥⼀篇论C++多态的⽂章⾥有⼀名话:“龙⽣九⼦,⼦⼦不同”多态就是同⼀个处理⼿段可以⽤来处理多种不同的情况,在钱能⽼师的《C++程序设计教程》书中有这样⼀个例⼦:定义了⼀个⼩学⽣类[本⽂全部代码均⽤伪码]class Student{public:Student(){}~Student(){}void 交学费(){}//......};⾥⾯有⼀个 “交学费”的处理函数,因为⼤学⽣和⼩学⽣⼀些情况类似,我们从⼩学⽣类中派⽣出⼤学⽣类:class AcadStudent:public Student{public:AcadStudent(){}~ AcadStudent(){}void 交学费(){}//.......};我们知道,中学⽣交费和⼤学⽣交费情况是不同的,所以虽然我们在⼤学⽣中继承了中学⽣的"交学费"操作,但我们不⽤,把它重载,定义⼤学⽣⾃⼰的交学费操作,这样当我们定义了⼀个⼩学⽣,⼀个⼤学⽣后:Student A;AcadStudent B;A.交学费(); 即调⽤⼩学⽣的,B.交学费();是调⽤⼤学⽣的,功能是实现了,但是你要意识到,可能情况不仅这两种,可能N种如:⼩学⽣、初中⽣、⾼中⽣、研究⽣.....它们都可以以Student[⼩学⽣类]为基类。
C++中的封装、继承、多态理解

C++中的封装、继承、多态理解封装(encapsulation):就是将抽象得到的数据和⾏为(或功能)相结合,形成⼀个有机的整体,也就是将数据与操作数据的源代码进⾏有机的结合,形成”类”,其中数据和函数都是类的成员。
封装的⽬的是增强安全性和简化编程,使⽤者不必了解具体的实现细节,⽽只是要通过外部接⼝,特定的访问权限来使⽤类的成员。
封装可以隐藏实现细节,使得代码模块化。
继承(inheritance):C++通过类派⽣机制来⽀持继承。
被继承的类型称为基类或超类,新产⽣的类为派⽣类或⼦类。
保持已有类的特性⽽构造新类的过程称为继承。
在已有类的基础上新增⾃⼰的特性⽽产⽣新类的过程称为派⽣。
继承和派⽣的⽬的是保持已有类的特性并构造新类。
继承的⽬的:实现代码重⽤。
派⽣的⽬的:实现代码扩充。
三种继承⽅式:public、protected、private。
继承时的构造函数:(1)、基类的构造函数不能被继承,派⽣类中需要声明⾃⼰的构造函数;(2)、声明构造函数时,只需要对本类中新增成员进⾏初始化,对继承来的基类成员的初始化,⾃动调⽤基类构造函数完成;(3)、派⽣类的构造函数需要给基类的构造函数传递参数;(4)、单⼀继承时的构造函数:派⽣类名::派⽣类名(基类所需的形参,本类成员所需的形参):基类名(参数表) {本类成员初始化赋值语句;};(5)、当基类中声明有默认形式的构造函数或未声明构造函数时,派⽣类构造函数可以不向基类构造函数传递参数;(6)、若基类中未声明构造函数,派⽣类中也可以不声明,全采⽤缺省形式构造函数;(7)、当基类声明有带形参的构造函数时,派⽣类也应声明带形参的构造函数,并将参数传递给基类构造函数;(8)、构造函数的调⽤次序:A、调⽤基类构造函数,调⽤顺序按照它们被继承时声明的顺序(从左向右);B、调⽤成员对象的构造函数,调⽤顺序按照它们在类中的声明的顺序;C、派⽣类的构造函数体中的内容。
继承时的析构函数:(1)、析构函数也不被继承,派⽣类⾃⾏声明;(2)、声明⽅法与⼀般(⽆继承关系时)类的析构函数相同;(3)、不需要显⽰地调⽤基类的析构函数,系统会⾃动隐式调⽤;(4)、析构函数的调⽤次序与构造函数相反。
【C++面向对象的程序设计】6多态性

虚析构函数
析构函数的作用是对象撤销之后清理现场。 在派生类对象撤销时,一般先调用派生类的 析构函数。再调用基类的析构函数。
然而,当定义的是一个指向基类的指针变量, 使用new运算符建立临时对象时,如果基类 中有析构函数,则在使用delete析构时只会 调用基类的析构函数。
这就需要将基类中的析构函数声明为虚函数。
虚函数的声明与使用
声明虚函数的一般格式如下: virtual 函数原型;
⑴ 必须首先在基类中声明虚函数。 ⑵ 派生类中与基类虚函数原型完全相同的成员函 数,即使在说明时前面没有冠以关键字virtual也 自动成为虚函数。
声明虚函数
⑶ 只有非静态成员函数可以声明为虚函数。 ⑷ 不允许在派生类中定义与基类虚函数名字及参数 特征都相同,仅仅返回类型不同的成员函数。 编译时 出错。 ⑸ 系统把函数名相同但参数特征不同的函数视为不 同的函数。 ⑹ 通过声明虚函数来使用C++提供的多态性机制时, 派生类应该从它的基类公有派生。
构函数等内容。
本章内容
静态联编与动态联编 虚函数的声明与使用 纯虚函数和抽象类 虚析构函数
Hale Waihona Puke 静态联编与动态联编所谓联编(tinding),就是使一个计算机程序的不同部 分彼此关联的过程。
静态联编在编译阶段完成,因为所有联编过程都在程 序开始运行之前完成,因此静态联编也叫先前联编或早期 联编。
另一种情况编译程序在编译时并不确切知道应把发送 到对象的消息和实现消息的哪段具体代码联编在一起,而 是在运行时才能把函数调用与函数体联系在一起,则称为 动态联编。
动态联编的实现
C ++语言中的动态联编是通过使用虚函数表 (Virtual Function Table)来实现的,虚函数表也称 为v-表。
C的运行时类型识别实现动态多态性

C的运行时类型识别实现动态多态性在C语言中,没有原生的运行时类型识别(Runtime Type Identification,RTTI)机制,而RTTI是实现多态性的关键。
然而,我们可以通过一些技巧和约定来模拟实现动态多态性,即在运行时根据对象的类型来决定调用哪个函数。
本文将介绍一种常用的C语言中实现动态多态性的方法。
一、使用函数指针表进行类型识别为了实现运行时类型识别,我们可以使用函数指针表(Function Pointer Table)来存储不同类型对象的函数指针。
函数指针表是一个包含一组函数指针的数组,数组的索引对应于对象的类型。
首先,我们定义一个基础的类型,作为其他类型的父类型,例如Shape类型:```ctypedef struct {void (*draw)();} Shape;```接下来,我们定义继承自Shape的具体类型,例如Rectangle和Circle:```ctypedef struct {Shape shape;int width;int height;} Rectangle;typedef struct {Shape shape;int radius;} Circle;```我们为每个具体类型实现相应的draw函数:```cvoid rectangle_draw() {printf("Drawing rectangle\n");}void circle_draw() {printf("Drawing circle\n");}```然后,我们为每个具体类型创建函数指针表,并将draw函数指针赋值给相应的表项:```cShape shape;shape.draw = rectangle_draw;Shape shape;shape.draw = circle_draw;```现在,我们可以通过调用shape.draw()来动态地调用相应类型的draw函数。
C语言中的多态实现方式

C语言中的多态实现方式
多态是面向对象编程中一个重要的概念,它允许不同的对象对同一个消息做出不同的响应。
在C语言中,虽然没有内置的多态特性,但我们可以通过一些技巧来实现多态效果。
一种常见的实现多态的方式是使用函数指针。
函数指针可以指向不同的函数,我们可以将函数指针作为参数传递给一个函数,然后根据不同的函数指针调用不同的函数。
这样就可以实现在运行时根据对象类型来选择不同的函数处理。
另一种实现多态的方式是使用结构体和函数指针的组合。
我们可以定义一个结构体,其中包含一个函数指针指向操作函数,然后针对不同类型的对象定义不同的操作函数。
通过这种方式,我们可以在运行时根据对象的类型来调用相应的操作函数,实现多态效果。
除此之外,我们还可以使用函数表来实现多态。
函数表是一个包含函数指针的数组,每个函数指针指向一个操作函数。
我们可以将函数表作为对象的一个成员变量,在运行时根据对象类型选择相应的函数表,然后调用相应的操作函数。
总结来说,虽然C语言本身并不支持多态特性,但通过使用函数指针、结构体和函数表等技巧,我们仍然可以在C语言中实现多态效果。
这些方法需要我们在设计程序结构时仔细思考,合理利用指针和结构体的特性,才能实现灵活而高效的多态效果。
希望以上内容能够帮助您了解C语言中的多态实现方式。
简述面向对象中的多态

简述面向对象中的多态
面向对象中的多态是指一个对象可以以多种形态存在。
简单来说,多态就是同一个方法可以有不同的实现方式。
在面向对象的程序设计中,多态是一种非常重要的概念。
它能够提高代码的灵活性和可扩展性,使得代码更易于维护和扩展。
多态的实现方式主要有两种:静态多态和动态多态。
静态多态是通过函数的重载和运算符的重载来实现的。
函数的重载是指在同一个类中定义多个同名函数,但这些函数具有不同的参数列表。
运算符的重载是指对于某个运算符,可以定义多个不同的操作方式。
在编译时,编译器会根据调用时的参数类型来确定具体调用的函数或运算符。
动态多态是通过继承和虚函数来实现的。
继承是指子类可以继承父类的属性和方法,通过定义一个指向父类对象的指针或引用,可以调用子类对象中重写的方法。
虚函数是在父类中声明为虚函数的函数,子类可以对其进行重写。
在运行时,根据实际对象的类型来调用相应的方法。
多态具有很多优点。
首先,它可以提高代码的重用性,一个类的方法可以被多个类继承并重写,这样可以减少代码的重复编写。
其次,多态可以使代码更加灵活,可以根据需要动态地根据对象的类型来调用相应的方法。
再次,多态可以提高代
码的可扩展性,当需要添加新的功能时,只需要在子类中重写相应的方法即可,而不需要修改已有的代码。
总之,多态是面向对象编程中非常重要的概念,通过使用多态可以使代码更灵活、可扩展和易于维护。
它是面向对象编程中的重要特性之一,值得我们深入理解和应用。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1. 什么是多态多态是C++中的一个重要的基础,可以这样说,不掌握多态就是C++的门个汉。
我就给它定一个这样的名字-- “调用’同名函数’却会因上下文不同会有不同的实现的一种机制”。
这个名字长是长了点儿,可是比“多态”清楚多了。
看这个长的定义,我们可以从中找出多态的三个重要的部分。
一是“相同函数名”,二是“依据上下文”,三是“实现却不同”。
我们且把它们叫做多态三要素吧。
2. 多态带来的好处多态带来两个明显的好处:一是不用记大量的函数名了,二是它会依据调用时的上下文来确定实现。
确定实现的过程由C++本身完成,另外还有一个不明显但却很重要的好处是:带来了面向对象的编程。
3. C++中实现多态的方式C++中共有三种实现多态的方式。
由“容易说明白”到“不容易说明白”排序分别为: 第一种是函数重载;第二种是模板函数;第三种是虚函数。
4. 细说用函数重载实现的多态函数重载是这样一种机制:允许有不同参数的函数有相同的名字。
具体一点讲就是:假如有如下三个函数:void test(int arg){} //函数1void test(char arg){} //函数2void test(int arg1,int arg2){} //函数3如果在C中编译,将会得到一个名字冲突的错误而不能编译通过。
在C++中这样做是合法的。
可是当我们调用test的时候到底是会调用上面三个函数中的哪一个呢?这要依据你在调用时给的出的参数来决定。
如下:test(5); //调用函数1test('c');//调用函数2test(4,5); //调用函数3C++是如何做到这一点的呢?原来聪明的C++编译器在编译的时候悄悄的在我们的函数名上根据函数的参数的不同做了一些不同的记号。
具体说如下:void test(int arg) //被标记为‘test有一个int型参数’void test(char arg) //被标记为‘test有一个char型的参数’void test(int arg1,int arg2) //被标记为‘test第一个参数是int型,第二个参数为int型’这样一来当我们进行对test的调用时,C++就可以根据调用时的参数来确定到底该用哪一个test函数了。
噢,聪明的C++编译器。
其实C++做标记做的比我上面所做的更聪明。
我上面哪样的标记太长了。
C++编译器用的标记要比我的短小的多。
看看这个真正的C++的对这三个函数的标记:?test@@YAXD@Z?test@@YAXH@Z?test@@YAXHH@Z是不是短多了。
但却不好看明白了。
好在这是给计算机看的,人看不大明白是可以理解的。
还记得cout吧。
我们用<<可以让它把任意类型的数据输出。
比如可以象下面那样:cout << 1; //输出int型cout << 8.9; //输出double型cout << 'a'; //输出char型cout << "abc";//输出char数组型cout << endl; //输出一个函数cout之所以能够用一个函数名<<(<<是一个函数名)就能做到这些全是函数重载的功能。
要是没有函数重载,我们也许会这样使用cout,如下:cout int<< 1; //输出int型cout double<< 8.9; //输出double型cout char<< 'a'; //输出char型cout charArray<< "abc"; //输出char数组型cout function(…)<< endl; //输出函数为每一种要输出的类型起一个函数名,这岂不是很麻烦呀。
不过函数重载有一个美中不足之处就是不能为返回值不同的函数进行重载。
那是因为人们常常不为函数调用指出返回值。
并不是技术上不能通过返回值来进行重载。
5. 细说用模板函数实现的多态所谓模板函数(也有人叫函数模板)是这样一个概念:函数的内容有了,但函数的参数类型却是待定的(注意:参数个数不是待定的)。
比如说一个(准确的说是一类或一群)函数带有两个参数,它的功能是返回其中的大值。
这样的函数用模板函数来实现是适合不过的了。
如下。
template < typename T>T getMax(T arg1, T arg2){return arg1 > arg2 ? arg1:arg2; //代码段1}这就是基于模板的多态吗?不是。
因为现在我们不论是调用getMax(1, 2)还是调用getMax(3.0, 5.0)都是走的上面的函数定义。
它没有根据调用时的上下文不同而执行不同的实现。
所以这充其量也就是用了一个模板函数,和多态不沾边。
怎样才能和多态沾上边呢?用模板特化呀!象这样:template<>char* getMax(char* arg1, char* arg2){return (strcmp(arg1, arg2) > 0)?arg1:arg2;//代码段2}这样一来当我们调用getMax(“abc”, “efg”)的时候,就会执行代码段2,而不是代码段1。
这样就是多态了。
更有意思的是如果我们再写这样一个函数:char getMax(char arg1, char arg2){return arg1>arg2?arg1:arg2; //代码段3}当我们调用getMax(‘a’, ‘b’)的时候,执行的会是代码段3,而不是代码段1或代码段2。
C++允许对模板函数进行函数重载,就象这个模板函数是一个普通的函数一样。
于是我们马上能想到写下面这样一个函数来做三个数中取大值的处理:int getMax( int arg1, int arg2, int arg3){return getMax(arg1, max(arg2, arg3) ); //代码段4}同样我们还可以这样写:template <typename T>T getMax(T arg1, T arg2, T arg3){return getMax(arg1, getMax(arg2, arg3) ); //代码段5}现在看到结合了模板的多态的威力了吧。
比只用函数重载厉害多了。
6. 小结上面的两种多态在C++中有一个总称:静态多态。
之所以叫它们静态多态是因为它们的多态是在编译期间就确定了。
也就是说前面所说的函数1,2,3代码段1,2,3,4,5这些,在编译完成后,应该在什么样的上下文的调用中执行哪一些就确定了。
比如:如果调用getMax(0.1, 0.2, 0.3)就会执行代码段5。
如果调用test(5)就执行函数1。
这些是在编译期间就能确定下来的。
静态多态还有一个特点,就是:“总和参数较劲儿”。
下面所要讲的一种多态就是必需是在程序的执行过程中才能确定要真正执行的函数。
所以这种多态在C++中也被叫做动态多态。
7. 细说用虚函数实现的多态7.1.虚函数是怎么回事首先来说一说虚函数,所谓虚函数是这样一个概念:基类中有这么一些函数,这些函数允许在派生类中其实现可以和基类的不一样。
在C++中用关键字virtual 来表示一个函数是虚函数。
C++中还有一个术语“覆盖”与虚函数关系密切。
所谓覆盖就是说,派生类中的一个函数的声明,与基类中某一个函数的声明一模一样,包括返回值,函数名,参数个数,参数类型,参数次序都不能有差异。
(注1)说覆盖和虚函数关系密切的原因有两个:一个原因是,只有覆盖基类的虚函数才是安全的。
第二个原因是,要想实现基于虚函数的多态就必须在派生类中覆盖基类的虚函数。
接下来让我们说一说为什么要有虚函数,分析一下为什么派生类非要在某些情况下覆盖基类的虚函数。
就以那个非常著名的图形绘制的例子来说吧。
假设我们在为一个图形系统编程。
我们可能有如下的一个类结构。
图7-1形状对外公开一个函数来把自己绘制出来。
这是合理的,形状就应该能绘制出来,对吧?由于继承的原因,多边形和圆形也有了绘制自己这个函数。
现在我们来讨论在这三个类中的绘制自己的函数都应该怎么实现。
在形状中嘛,什么也不做就行了。
在多边形中嘛,只要把它所有的顶点首尾相连起来就行了。
在圆形中嘛,依据它的圆心和它的半径画一个360度的圆弧就行了。
可是现在的问题是:多边形和圆形的绘制自己的函数是从形状继承而来的,并不能做连接顶点和画圆弧的工作。
怎么办呢?覆盖它,覆盖形状中的绘制自己这个函数。
于是我们在多边形和圆形中各做一个绘制自己的函数,覆盖形状中的绘制自己的函数。
为了实现覆盖,我们需要把形状中的绘制自己这个函数用virtual修饰。
而且形状中的绘制自己这个函数什么也不干,我们就把它做成一个纯虚函数。
纯虚函数还有一个作用,就是让它所在的类成为抽象类。
形状理应是一个抽象类,不是吗?于是我们很快写出这三个类的代码如下:class Shape//形状{public:virtual void DrawSelf()//绘制自己{cout << "我是一个什么也绘不出的图形" << endl;}};class Polygo:public Shape//多边形{public:void DrawSelf() //绘制自己{cout << "连接各顶点" << endl;}};class Circ:public Shape//圆{public:void DrawSelf() //绘制自己{cout << "以圆心和半径为依据画弧" << endl;}};下面,我们将以上面的这三个类为基础来说明动态多态。
在进行更进一步的说明之前,我们先来说一个不得不说的两个概念:“子类型”和“向上转型”。
7.2.向上转型子类型很好理解,比如上面的多边形和圆形就是形状的子类型。
关于子类型还有一个确切的定义为:如果类型X扩充或实现了类型Y,那么就说X是Y的子类型。
向上转型的意思是说把一个子类型转的对象换为父类型的对象。
就好比把一个多边形转为一个形状。
向上转型的意思就这么简单,但它的意义却很深远。
向上转型中有三点需要我们特别注意。
第一,向上转型是安全的。
第二,向上转型可以自动完成。
第三,向上转型的过程中会丢失子类型信息。
这三点在整个动态多态中发挥着重要的作用。
假如我们有如下的一个函数:void OutputShape( Shape arg)//专门负责调用形状的绘制自己的函数{arg.DrawSelf();}那么现在我们可以这样使用OutputShape这个函数:Polygon shape1;Circ shape2;OutputShape(shape1);OutputShape(shape2);我们之所以可以这样使用OutputShape函数,正是由于向上转型是安全的(不会有任何的编译警告),是由于向上转弄是自动的(我们没有自己把 shape1和shape2转为Shape类型再传给OutputShape函数)。