C/C++——多态性和虚函数
来源:互联网 发布:php sqllite 编辑:程序博客网 时间:2024/05/17 02:23
面向对象程序设计有4个特点:抽象、封装、继承和多态性。其中多态性是面向对象程序设计的重要特征。本文主要讲解一下C++多态性的一些基本知识,以便于大家在程序设计中更好地利用多态性。
1. 多态性
多态性可以这样概括:不同对象接受到同样的消息后,产生不同的动作。封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。多态性分为静态多态性和动态多态性。静态多态性又称编译时多态性,包括函数重载和运算符重载,采用迟绑定(late binding)的技术,要求在编译的时候就知道调用函数的信息决定要调用的函数,所以静态多态性效率高但灵活性差。动态多态性又称运行时多态性,主要通过虚函数实现,采用早绑定(late binding)的技术,是在程序运行过程中动态确定调用的函数,所以灵活性高但效率差。
先看下面的一个例子:
#include<iostream>
using namespacestd;
class animal
{public:
virtual voidbreath()
// void breath()
{ cout<<"animal"<<endl; }
};
classfish:public animal
{public:
void breath() { cout<<"fish"<<endl; }
};
int main()
{ animal ani; //定义一个animal对象ani
fish fi; //定义一个fish对象fi
ani.breath(); //调用animal的breath方法
fi.breath(); //调用fish的breath方法
animal* pt1=&ani; //定义一个animal类型的指针,并用animal对象初始化
pt1->breath(); //通过指针调用animal的breath方法
fish* pt3=&fi; //定义一个fish类型的指针,并用fi对象初始化
pt3->breath(); //通过指针调用fish的breath方法
animal* pt=&fi; //定义一个animal类型的指针,并用fi对象初始化
pt->breath(); //通过指针调用fish的breath方法
// fish* pt2=&ani; //定义一个fish类型的指针,并用animal对象初始化,想通过指针调用fish的breath方法,编译错误
// pt2->breath();
return 0;
}
上面定义了两个类,类animal和类fish,类fish是从类animal继承得到的。两个类里都定义了方法breath()。在主函数里分别实例化了两个对象ani和fi,分别用不同方法调用相应breath()函数。
①通过点操作符“.”调用函数,即 ani.breath(); 和 fi.breath();。
②通过指针和指向操作符,即animal* pt1=&ani; pt1->breath(); 和 fish*pt3=&fi; pt3->breath();
上面这两种方法的意图都很明显,通过相应对象或相应对象的指针调用函数,指针类型和初始化指针的对象的类型相同,不会产生歧义。
③ animal* pt=&fi; pt->breath(); 定义一个animal类型的指针,用fish类的对象fi初始化指针,然后通过指针调用fish的breath方法。这种方式可以调用成功,是因为在类animal里定义breath()时,用了关键字virtual,表示这是一个虚函数,如果不加这个关键子,则结果就会调用animal的breath()方法,读者可以试一下,这相当于是函数的覆盖,即两个类里函数名、返回值和参数表都相同。当我们用fi对象初始化animal指针时,指针指向的是基类的地址,所以调用基类的breath()方法。而加了virtual关键字后,用fi对象初始化话animal指针,指针虽指向基类的地址,但调用的是fish类的breath()方法。当C++编译器在编译的时候,发现Animal类的breathe()函数是虚函数,这个时候C++就会采用迟绑定(late binding)的技术,在运行时,依据对象的类型(在程序中,我们传递的Fish类对象的地址)来确认调用的哪一个函数,这种能力就做C++的多态性。也就是说,用哪个派生类对象初始化基类指针,通过这个指针调用的函数就是哪个派生类的相应函数。
同样的道理解释析构函数。析构函数是对象撤销时的做一些清理工作,一般会先调用派生类的析构函数,再调用基类的析构函数。但是当我们用派生类对象的地址赋值给基类指针,在删除这个指针撤销对象时,只调用了基类的析构函数。这是因为,用派生类对象的地址赋值给基类指针,对象撤销时,通过指针只能调用基类的析构函数,要解决这个问题,可以把基类的析构函数声明为虚函数,这样通过这个指针就可以调用到派生类的析构函数,然后再由派生类的析构函数调用基类的析构函数。所以一般将基类的析构函数声明为虚函数,这样如果用派生类对象的地址赋值给基类指针,通过delete手动删除派生类对象,就可以调用到派生类的析构函数。
这里有几点需要注意:
(1)在基类的函数前加virtual,变为虚函数,声明的时候加virtual,实现的时候不加virtual,这类似友元函数。当传递子类对象的地址调用函数,如果子类中有函数就调用子类的,子类没有的就调用基类的。
(2) 当子类继承父类的虚函数,则继承下来就是虚函数,不管子类里函数前是否加virtual。
(3) 重载和派生是不同的概念。重载发生在同一个类里,函数名相同,参数表不同;派生发生在不同类里(基类和派生类),函数名和参数表都相同。
(4) 这是讲重载、覆盖和隐藏的一篇文章:
http://www.cppblog.com/lingyun1120/archive/2011/04/27/145135.html
2. 纯虚函数
被标明不具体实现的虚成员函数,可以让类先有一个名称,没有函数体,让派生类在定义的时候给出具体的定义,称这种函数为纯虚函数。纯虚函数声明形式:virtual 函数类型 函数名(参数表)=0; 后面的“=0”是专门用来声明纯虚函数的,没有什么实际意义。因为没有函数体,所以有纯虚函数的类是一个“不完整的类” ,不能实例化对象。纯虚函数作用是为派生类保留一个函数名,派生类根据需要自己定义这个函数,实现特定操作。
3. 抽象类
一个基类如果包含一个或一个以上的纯虚函数,就是抽象类,由于这样的类常作为基类,所以又称抽象基类。如果在基类声明了虚函数,在派生类中不管这个函数前面是否有关键字virtual,继承下来的这个函数都是虚函数,但一般为了清晰明了,会在虚函数前virtual。而在基类声明的纯虚函数,只有在派生类里在用“=0”再次声明为纯虚函数,在派生类中才是纯虚函数,负责就是虚函数了。因为纯虚函数实际上只有函数名而已,所以抽象类不能实例化对象。抽象基类也是这个类的公共接口,从同一个类派生出的类有同一接口,因此能相应同一形式的消息,即将派生类对象的地址赋值给基类指针,通过基类指针访问派生类成员函数。
参考文献:
http://blog.csdn.net/hackbuteer1/article/details/7475622
http://my.oschina.net/hnuweiwei/blog/280894
- C/C++——多态性和虚函数
- c++--多态性和虚函数
- 多态性与虚函数——C/C++学习笔记
- c++--多态性与虚函数
- [C++] 多态性与虚函数
- C++:多态性(虚函数)
- C/C++ 初学简单笔记 —4— 多态性 虚函数和抽象类
- C/C++学习----第三章 多态性和虚函数
- C语言模拟C++虚函数多态性
- C/C++多态性(polymorphism)虚函数
- C/C++中多态性与虚函数
- C++__多态性与虚函数
- C语言实现函数多态性
- C++-虚函数,多态性,纯虚函数,抽象类
- 【C++】多态性(函数重载与虚函数)
- 多态性和虚函数
- 虚函数和多态性
- 虚函数和多态性
- Otter(一)初识----简介和基本架构图
- Ubuntu14.04 mysql 5.5 的中文乱码问题
- 剖析js对浮点数运算精度问题
- SQL SUM() 函数、SQL GROUP BY 语句、SQL HAVING 子句
- android 5.0及以上,seekbar thumb 透明效果出现父布局背景颜色的解决方法
- C/C++——多态性和虚函数
- C# 中的委托与事件
- [Built-in Functions] - N
- SESSION跨域访问的相关总结
- Using GLEW, GLFW, and GLM
- git 使用 常用配置
- 无法安装64位版本的Office,因为在您的PC上找到了以下32位程序:
- rvm安装ruby简易教程
- linux创建sftp账号及访问权限