派生一个类的原因并非总是为了继承或是添加新的成员,有时是为了重新定义基类的成员,使得基类成员“获得新生”。面向对象的程序设计真正的力量不仅仅是继承,而且还在于允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编。
(一)多态性
多态是指同样的消息被不同的对象接收时导致不同的行为。所谓消息是指对类成员函数的调用,不同的行为是指的不同的实现,也就是调用了不同的函数。
1)多态的分类
广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++中,这种多态性可以通过重载多态(函数和运算符重载),强制重载(类型强制转换),类型参数化多态(模板)
,包含多态(继承与虚函数)四种方式来实现。类型参数化多态和包含多态称为一般多态性,是用来系统地刻画语义上相关的一组类型;重载多态和强制多态性称为特殊多态性,用来刻画语义上无关连的类型间关系。
C++中采用虚函数实现包含多态。虚函数为C++提供了更为灵活的多态机制,这种多态性在程序运行时才能够确定,因此虚函数是多态性的精华,至少含有一个虚函数的类称为多态类。包含多态在面向对象的程序设计中使用很频繁。
2)静态联编
联编又称为绑定,就是将模块或函数合并在一起生成可执行代码的处理过程,同时对每个模块或函数分配内存地址,对外部访问也提供正确的内存地址。
在编译阶段就将函数实现与函数调用绑定起来称为静态联编。静态联编在编译阶段就必须了解所有函数与模块执行所需要的信息,它对函数的选择是基于指向对象的指针(或引用)的类型。在C语言中所有的联编都是静态联编;C++中一般情况也是静态联编。
class Point{ public: void area(){cout<<"point";} }; class Circle:public Point{ public: void area(){cout<<"circle";} }; Point a; Circle c; a.area(); //调用a.Point::area() c.area(); //调用c.Circle::area(),名字支配规则 Point * pc=&c,&rc=c; //上篇所讲的赋值兼容性规则 pc->area(); //调用pc->Point::area() rc.area(); //调用rc.Point::area()
3)动态联编
如果程序在运行时候才进行函数实现和函数调用的绑定称为动态联编。以上面的例子为例,在编译时如果只根据兼容性规则检查它的合理性,即检查它是否符合派生类对象地址可以赋值给基类指针变量的条件。至于pc->area()调用哪个函数等到程序运行到这里才做决定。如果希望其调用Circle::area(),那么需要将Point类的area()函数指定为虚函数。定义形式为:
virtual void area(){cout<<"point";}
当编译器编译含有虚函数的类时候,将为他建立一个虚函数表VTABLE,它相当于一个指针数组,存放每一个虚函数的入口地址。编译器为该类增减一个额外的数据成员,这个数据成员是一个指向虚函数表的指针,称为vptr。
如果派生类没有重写这个虚函数,则派生类的虚函数列表里元素指向的地址就是基函数area()的地址,即派生类仅仅继承基类的虚函数
如果派生类重新写这个虚函数如下:
virtual void area() {cout<<"circle";}
那么这时编译器将派生类虚函数表里的元素指向Circle::area()
编译器为含有虚函数的对象先建立一个函数入口地址,这个地址用来存放指向虚函数表的指针vptr,然后按照类中虚函数的声明次序一一填入函数指针。当调用虚函数时候,先通过vptr找到虚函数表,然后找出虚函数真正的地址。
派生类能够继承基类的虚函数表,而且只要是和基类同名(参数也相同)的成员函数,无论是否使用virtual声明,它们都自动生成虚函数。如果派生类没有改写继承基类的虚函数,则函数指针将调用基类的虚函数。
(二)虚函数
1)虚函数定义
虚函数只是类中的一个成员函数,且不能是静态的。在成员函数定义或声明之前加上关键字virtual,即定义了虚函数:
class类名{ ... virtual 返回类型 函数名 (形式参数列表)//虚函数 ... }; class Point { virtual void area (); //虚函数声明 virtual double volumn(){} //虚函数定义 };
需要注意virtual关键字只在类体中使用。
利用虚函数可以在基类和派生类中使用相同的函数名定义函数不同的实现,从而实现“一个接口,多种方式”。当基类指针或引用对虚函数进行访问时,系统将根据运行时指针或引用所指向或引用的实际对象来自动确定调用对象所在类的虚函数版本。
2)虚函数实现多态的条件
关键字virtual指示C++编译器对调用虚函数进行动态联编。这种多态性是程序运行到相应语句才动态确定的,称为运行时的多态。不过,使用虚函数不一定产生多态性,也不一定使用动态联编。例如,在调用中对虚函数使用成员名限定,可以强制C++对该函数的调用使用静态联编。
虚函数产生运行时的多态性必须有2个条件。
a)派生类改写了同名的虚函数
b)根据赋值兼容性规则使用指针或引用
Point *p=new Circle; //基类指针指向派生类 cout<<p->area(); //动态联编 void fun(Point *p) {cout<<p->area();} //动态联编
3)在一个派生类中,当一个指向基类成员函数的指针指向一个虚函数,并且通过指向对象的指针或引用访问这个虚函数时候将发生多态性。
#include<iostream> using namespace std; class Base{ public: virtual void print(){cout<<"base"<<endl;} }; class Derived :public Base{ public: void print(){cout<<"derive"<<endl;} }; //void(Base::*pf)(); void display(Base *p,void(Base::*pf)()) { (p->*pf)(); } int main() { Derived d; Base b; display(&d,&Base::print); display(&b,&Base::print); return 0; } lzb@lzb:~/classic_lib/C++_learning$ g++ 427.cpp lzb@lzb:~/classic_lib/C++_learning$ ./a.out derive base
display有两个函数,第一个参数是基类指针,第二个参数是指向类成员函数的指针。display使用基类指针调用指向成员函数的指针所指向的成员函数。是调用基类的虚函数还是派生类的虚函数,取决于基类指针指向的对象。
补充:
面向对象的三个特征:封装、继承和多态。
什么是多态
多态的意思是一个事物有多种形态,英文单词为polymorphism,向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(方法)。也就是说,每个对象可以用自己的方式去相应共同的消息。
例如函数的重载、运算符的重载都是多态现象。
一个生活中的例子,比如学生开学,校长发布一条哪一天开学的信息,不同的对象会产生不同的反应,学生就要准备上学,家长要准备学费,老师也要开始备课,学校食堂开始采购食材,这就是多态性,如果没有多态性的话,校长就需要分别对学生、教师和家长等不同的对象单独发通知。
在C++中,多态性表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
从系统实现的角度来看,多态性分为两类:静多态性和动多态性
静多态性是通过函数重载实现的,动多态性是通过虚函数实现的。
什么是虚函数
C++中的虚函数就是用来解决动态多态问题的,所谓虚函数,就是在基类声明函数是虚拟的,并不是实际存在的,然后在派生类中才正式定义此函数,在程序运行期间,用指针指向某一派生类对象,这样就能调用指针指向的派生类对象中的函数,而不对调用其他派生类中的函数。
总结
以上所述是小编给大家介绍的C++多态性与虚函数,希望对大家有所帮助!
本文向大家介绍深入浅析Nginx虚拟主机,包括了深入浅析Nginx虚拟主机的使用技巧和注意事项,需要的朋友参考一下 一 虚拟主机 1.1 虚拟主机概念 对于Nginx而言,每一个虚拟主机相当于一个在同一台服务器中却相互独立的站点,从而实现一台主机对外提供多个 web 服务,每个虚拟主机之间是独立的,互不影响的。 1.2 虚拟主机类型 通过 Nginx 可以实现虚拟主机的配置,Nginx 支持三
教学目标 了解多态性的概念 了解怎样声明和使用实现多态性的虚函数 了解抽象类和具体类的区别 学会怎样声明建立抽象类的纯虚函数 认识多态性是如何扩展和维护系统 了解 C++ 如何实现虚函数和动态关联
本文向大家介绍深入浅析C#泛型类型,包括了深入浅析C#泛型类型的使用技巧和注意事项,需要的朋友参考一下 上篇文章给大家介绍了浅析C# 中的类型系统(值类型和引用类型),接下来通过本文给大家介绍下c# 泛型类型, 说下C#中的泛型,熟练地使用泛型能提高代码的重用性,使用我们代码瞬间就高大上了,当然只有一点点,真的只有一点点,因为后面要学习和掌握的知识还有很多。先来看下一个使用Dictionary<T
本文向大家介绍深入浅析javascript立即执行函数,包括了深入浅析javascript立即执行函数的使用技巧和注意事项,需要的朋友参考一下 javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花; 当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解。 JavaScript 函数语法 函数就是包裹在花括号中的代
本文向大家介绍深入浅析python 中的匿名函数,包括了深入浅析python 中的匿名函数的使用技巧和注意事项,需要的朋友参考一下 定义 匿名函数指一类无须定义标识符的函数或子程序。Python用lambda语法定义匿名函数,只需用表达式而无需申明。 lambda语法的定义如下: 匿名函数就是没有实际名称的函数。其主体仅仅是一个表达式,而不需要使用代码块。 <函数对象名> = lambda <
主要内容:借助引用也可以实现多态,多态的用途在《 C++将派生类赋值给基类(向上转型)》一节中讲到,基类的 指针也可以指向派生类对象,请看下面的例子: 运行结果: 王志刚今年23岁了,是个无业游民。 赵宏佳今年45岁了,是个无业游民。 我们直观上认为,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,这符合人们的思维习惯。但是本例的运行结果却告诉我们,当基类指针 p 指向派生类 Teacher 的对象时,虽然使用了 Tea