c++ 多态小结
2016-06-10 10:01
417 查看
定义:
多态:指相同对象接收到不同消息或者不同的对象接收到相同的消息时产生的不同的动作。前者,相同的对象接收不同的消息可以看做函数的多态,也就是函数的重载,这种多态是静态的多态,表面上看函数的名称一样,或者由于参数的类型或者个数或者返回值形成的不同的函数重载,在编译器编译的时候,尽管函数名称相同,但是在编译过后会生成不同的函数,这发生在函数运行之前,称之为静态的多态。
如
int max(int a,int b); int max(int a,int b,int c);函数名相同,
后者,不同对象接收到相同的消息指的是类的多态。这种多态发生在运行的时候,称之为动态的多态。动态的多态是建立在类的封装和继承的基础上的。首先来看一个例子。
#include <iostream> #define PI 3.14 using namespace std; class Base{ public: double area(); //virtual double area(); }; double Base::area() { cout<<"Base area"<<endl; return 0; } class Circle : public Base{ public: Circle(double r):radies(r){} double area(); private: double radies; }; double Circle::area() { cout<<"Circle area“<<endl; return PI*radies*radies; } class Rectangle : public Base{ public: Rectangle(double len,double wid):length(len),width(wid){} double area(); private: double length,width; }; double Rectangle::area() { cout<<"Rectangle area"<<endl; return length*width; } int main(int argc,char *argv[]) { Base *base1=new Circle(1); Base *base2=new Rectangle(1,2); base1->area(); base2->area(); delete base1; delete base2; return 0; }
在这里,用基类的指针Base去指向两个派生类的对象,最后输出的结果是:
也就是说输出的都是基类的area函数中的内容,根本没有多态性。
c++为了实现多态性,采用了一个关键字,就是virtual,将它加在基类的即将要实现多态的函数前,即可实现多态。顺便提一下,在派生类中如果不加virtual 也是可以的,只要基类加了virtual即可,编译器会为我们加上。但是我们一般会在派生类中也添加virtual,是为了使得程序便于阅读,而且也便于派生类再派生一个对象,可以直接看出它含有虚函数。现在将上面的Base 类中的area函数前面加上virtual,就像程序中注释的那样。则输出:
这就是类的多态。基类的指针base1指向一个派生类的对象Circle(1),和Rectangle(1,2),可以分别调用各自派生类对应的函数。这就是c++类的多态性。
虚析构函数:
使用示例:#include <iostream> #define PI 3.14 using namespace std; class Base{ public: ~Base(); // virtual ~Base(); }; Base::~Base() { cout<<"~Base"<<endl; } //定义一个圆心类,以坐标的形式 class Point{ public: Point(int x_,int y_):x(x_),y(y_){} ~Point(){cout<<"~point"<<endl;} private: int x,y; }; class Circle : public Base{ public: Circle(double r); ~Circle(); private: double radies; Point *center; }; Circle::Circle(double r) { radies=r; center=new Point(1,2); } Circle::~Circle() { delete center; cout<<"~Circle"<<endl; } int main() { Base *base=new Circle(1); delete base; base=NULL; return 0; }上述定义了一个圆心类,使用动态获取内容的方法。
编译运行:
尽管base使用多态特性,可以指向Circle对象,但是在delete base时,还是只能析构基类的指针,这显然使得Circle类没有析构,何况里面还有在堆中请求的内存块,由此造成内存泄露这一严重问题。此时虚析构函数也就可以派上用场了,就在基类虚构函数前加上virtual,则可以正常析构。一句话;虚析构函数是父类指针指向子类对象时为了避免子类内存泄露而采用的办法。
那我怎么知道什么时候用虚析构函数呢?
没有特别的准则,最好是类中出现了多态,也就是出现了virtual,那么就用虚析构函数,不会出错。
虚析构函数不能加在以下函数前面:
1.不能加在构造函数的前面;
2.不能加在普通的函数前面;
3.不能加到静态成员函数前面;
4.不能加在内联函数前面。
原理:
虚函数表:类的每个对象除了拥有该类的数据成员,还拥有该类的虚函数表指针。该指针指向一个虚函数表。
如图一个示例,假设有一个CBasic类,它有两个数据成员,分别为int i,和int *Array,当我们声明一个CBasic对象的时候(特别注意,虚函数指针是属于每一个类的对象的,不是属于类),该对象在内存中存的形式就如同上图所示。首先,它有一个虚函数指针,它指向一个虚函数表。如上图,——vfptr存放的是虚函数表的地址,也就是0x00ea6804,该虚函数表中存放的是一个函数指针(也就是一个地址),上图中也就是0x00ea1113,该地址是CBasic::add的函数首地址。当一个基类的对象调用该虚函数时,就通过这种方法找到该函数。
当我们用一个基类指针指向一个派生类的对象,同样的,该派生类的对象有一个虚函数指针,它存储的是派生类虚函数表的地址,这个虚函数表的地址和派生类的虚函数表的地址不一样,所以尽管用基类的指针去指向派生类的对象,本质上它还是一个派生类的对象,它和基类拥有的虚函数表地址不同,故找到的函数也就不一样了。
重载,覆盖和隐藏
先介绍下定义:重载:
首先是在同一个域中,例如在同一个类中,函数的名称相同,但是参数个数,或者类型或者返回值不同,这就是函数重载。
覆盖:
针对的是派生类和子类,函数的名称相同,而且参数类型个数相同,基类还必须要有virtual关键字,
隐藏:
针对的是派生类和子类,
如果派生类和子类同函数名,但是参数不同,不论有无virtual关键字,基类函数被隐藏。(这和重载有区别,重载是在一个类中)
如果派生类和子类函数名相同,参数也相同,但是没有virtual关键字,这样基类函数被隐藏。
看个例子:
#include <iostream> using namespace std; class Base{ public: void f(int x) {cout<<"Base::f(int x)"<<endl;} void f(double x) {cout<<"Base::f(double x)"<<endl;} //重载f(int x); virtual void g(int x) {cout<<"Base::g(int x)"<<endl;} }; class Derived : public Base{ public: void f(int x) {cout<<"Derived::f(int x)"<<endl;} //隐藏Base::f(int x) virtual void g(int x) {cout<<"Derived::g(int x)"<<endl;} //覆盖Base::g(int x) }; int main() { Base base; Derived derived; base.f(1); base.f(1.0); base.g(1); derived.g(1); base.f(1); derived.f(1); derived.f(1.0); }结果:
再来一个例子:
#include<iostream> #include<string> using namespace std; class A { public : A(){} void f(){cout<<'a'<<endl;} virtual void g(){cout<<"invoke aaa"<<endl;} }; class B:public A { public: B(){} virtual void g(){cout<<"invoke bbb"<<endl;} void f(){cout<<'b'<<endl;} }; int main() { A * a = new B; A * b = new A; B * c = new B; a->f(); b->f(); c->f(); cout<<"-------------------------"<<endl; a->g(); b->g(); c->g(); } 结果是 a a b invoke bbb invoke aaa invoke bbb说明:如果是隐藏的话,用指针调用这个函数,根据的是指针自己的类型
如果是覆盖的话,用指针的调用,根据的是指针所指的对象的类型。
一句话:覆盖是多态的表现形式,而隐藏则破坏多态性。
纯虚函数和抽象类
定义一个虚函数,不实现它,只将它赋予0.该函数就是纯虚函数。拥有纯虚函数的类就是抽象类。抽象类是用于子类继承用的,根本不能用于实例化对象。class Base{ public: virtual area()=0; }如,area()就是纯虚函数,Base类就是抽象类。
相关文章推荐
- C++ STL 之 lower_bound and upper_bound
- 21.3 windows_21_Library_DLL_test&&CPP&&DEF 动态库补充3
- C语言关系和逻辑运算符与分支语句
- C语言算数运算符和算数表达式
- C语言变量的使用
- C++ c++初识
- C语言基础学习
- C++11智能指针
- C++复习3.继承的相关概念
- c语言结构体小知识
- C++学习(三)——Effective C++
- *leetcode #84 in cpp
- 深度探索C++对象模型--关于对象
- 工程脚本插件方案 - c集成Python基础篇(VC++嵌入Python)
- 《C++语言基础》程序阅读——指针、const、static(二)
- C++标准库中正则表达式简介
- C语言中的位操作
- 七大排序算法C++实现(代码分享)
- C++中rdbuf()简介及文件流的概念
- C++实现目录下所有文件添加后缀名