初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。
来源:互联网 发布:网络主播直播结束语 编辑:程序博客网 时间:2024/05/21 17:00
1.c++多态的概念以及用途。
1.1虚函数实现多态
通过基类指针只能够访问派生类的成员变量,不能够访问派生类的成员函数。
解决问题的办法:使用虚函数(virtual function),只需要在函数声明前面增加virtual关键字。看下面的代码。
通过基类指针只能够访问派生类的成员变量,不能够访问派生类的成员函数。
解决问题的办法:使用虚函数(virtual function),只需要在函数声明前面增加virtual关键字。看下面的代码。
#include <iostream>using namespace std;class people{public :people(char *name, int age);virtualvoid show();protected :char *m_name;int m_age;};people :: people (char *name, int age) : m_name(name), m_age(age){}void people :: show(){cout<<m_name<<", "<<m_age<<" old"<<endl;}class student : public people{public : student (char *name, int age, float score);virtual void show();protected :float m_score;};student :: student (char *name, int age, float score) : people(name, age), m_score(score){}void student :: show(){cout<<m_name<<", "<<m_age<<" old, "<<m_score<<endl;}int main(){people *p = new people("xiaoli", 22);p->show();p = new student("xiaozhang", 16, 95.6);p->show();delete p;return 0;}结果:
aoli, 22 old
xiaozhang, 16 old, 95.6
如果不加virtual关键字,则运行结果是:
xiaoli, 22 old
xiaozhang, 16 old
有了虚函数,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,这就是多态(Polymorphism)!
c++中虚函数的唯一用处就是构成多态。
1.2借用引用也可以实现多态
在之前的学习中,可以了解到,指针在本质上是通过指针的方式实现的(引用只是对指针进行了封装,底层还是通过指针实现的)。
把上面的例子改一下:
int main()
{
people p("xiaoli", 22);
student q("xiaozhang", 16, 95.6)
people &rp = p;
people &rq = q;
rp.show();
rq.show();
return 0;
}
实现的功能和上面的例子是一样的。
1.3虚函数的要点
虚函数对于多态具有决定性的作用,有虚函数才能构成多态!
对于虚函数还有以下几点注意点:
1) 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。
2) 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。
3) 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。
4) 只有派生类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。
5) 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。
6) 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。
构成多态的条件:
1.必须存在继承关系!
2.继承关系中必须有同名的虚函数,并且它们是遮蔽(覆盖)关系!
3.存在基类的指针,通过该指针调用虚函数!
看下面的代码。
#include <iostream>using namespace std;class base{public :virtual void func();virtual void func(int);};void base :: func(){cout<<"base:func()"<<endl;}void base :: func(int a){cout<<"base:func(int)"<<endl;}class der : public base{public :void func();void func(char *);};void der :: func(){cout<<"der:func()"<<endl;}void der :: func(char *b){cout<<"der:func(char *)"<<endl;}int main(){base *p = new der;p->func(); //调用的是派生类的虚函数,构成了多态。p->func(100); //调用的是基类的虚函数,因为派生类中没有函数遮蔽它。//p_>func("hello"); //出现编译错误,因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。return 0;}再来补充一下什么时候需要用到虚函数:
首先看成员函数所在的类是否会作为基类。
然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。
2.虚析构函数
首先要明白,构造函数不能是虚函数,因为派生类不能继承基类的构造函数,将构造函数声明为虚函数没有意义!
先看代码
先看代码
#include <iostream>using namespace std;class base{public :base();virtual ~base();protected :char *ptr;};base :: base(){ptr = new char[100];cout<<"base construct"<<endl;}base :: ~base(){delete [] ptr;cout<<"~base deconstruct"<<endl;}class der : public base{public :der();~der();protected :char *name;};der :: der(){name = new char[100];cout<<"der construct"<<endl;}der :: ~der(){delete[] name;cout<<"~der deconstruct"<<endl;}int main(){base *p = new der;delete p;return 0;}将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;
也就是说,指针指向哪个类的对象就调用哪个类的函数。
(补充!!:指针访问非虚函数时,编译器会根据指针的类型来确定要调用的函数。如果指针是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,
在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,并且这个过程是隐式完成的)。
在实际开发中,一旦我们自己定义了析构函数,就是希望在对象销毁时用它来进行清理工作,比如释放内存、关闭文件等,如果这个类又是一个基类,
那么我们就必须将该析构函数声明为虚函数,否则就有内存泄露的风险。也就是说,大部分情况下都应该将基类的析构函数声明为虚函数。
3.虚函数表和多态实现机制。
强调!!!当通过指针访问类的成员函数时:
1.如果该函数是非虚函数,那么编译器会根据指针的类型找到该函数;也就是说,指针是哪个类的类型就调用哪个类的函数。
2.如果该函数是虚函数,并且派生类有同名的函数遮蔽它,那么编译器会根据指针的指向找到该函数;
也就是说,指针指向的对象属于哪个类就调用哪个类的函数。这就是多态。
1.如果该函数是非虚函数,那么编译器会根据指针的类型找到该函数;也就是说,指针是哪个类的类型就调用哪个类的函数。
2.如果该函数是虚函数,并且派生类有同名的函数遮蔽它,那么编译器会根据指针的指向找到该函数;
也就是说,指针指向的对象属于哪个类就调用哪个类的函数。这就是多态。
编译器之所以能通过指针指向的对象找到虚函数,是因为在创建对象时额外地增加了虚函数表。
看一个例子:
#include <iostream>#include <string>using namespace std;//People类class People{public: People(string name, int age);public: virtual void display(); virtual void eating();protected: string m_name; int m_age;};People::People(string name, int age): m_name(name), m_age(age){ }void People::display(){ cout<<"Class People:"<<m_name<<"今年"<<m_age<<"岁了。"<<endl;}void People::eating(){ cout<<"Class People:我正在吃饭,请不要跟我说话..."<<endl;}//Student类class Student: public People{public: Student(string name, int age, float score);public: virtual void display(); virtual void examing();protected: float m_score;};Student::Student(string name, int age, float score): People(name, age), m_score(score){ }void Student::display(){ cout<<"Class Student:"<<m_name<<"今年"<<m_age<<"岁了,考了"<<m_score<<"分。"<<endl;}void Student::examing(){ cout<<"Class Student:"<<m_name<<"正在考试,请不要打扰T啊!"<<endl;}//Senior类class Senior: public Student{public: Senior(string name, int age, float score, bool hasJob);public: virtual void display(); virtual void partying();private: bool m_hasJob;};Senior::Senior(string name, int age, float score, bool hasJob): Student(name, age, score), m_hasJob(hasJob){ }void Senior::display(){ if(m_hasJob){ cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,并且顺利找到了工作,Ta今年"<<m_age<<"岁。"<<endl; }else{ cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,不过找工作不顺利,Ta今年"<<m_age<<"岁。"<<endl; }}void Senior::partying(){ cout<<"Class Senior:快毕业了,大家都在吃散伙饭..."<<endl;}int main(){ People *p = new People("赵红", 29); p -> display(); p = new Student("王刚", 16, 84.5); p -> display(); p = new Senior("李智", 22, 92.0, true); p -> display(); return 0;}各个类的对象内存模型如下所示:
这里的东西及原理有些复杂,况且还是单继承。对于多继承,复杂程度不可想象,这里不做深究,有兴趣的同学自己研究吧。知道怎么用虚函数的我,先满足一下。代以后再深究。
4.纯虚函数
在C++中,可以将虚函数声明为纯虚函数,
语法格式为:
virtual 返回值类型 函数名 (函数参数) = 0;
1.纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。
2. 最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
语法格式为:
virtual 返回值类型 函数名 (函数参数) = 0;
1.纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。
2. 最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
包含纯虚函数的类称为抽象类(Abstract Class)。抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。看代码
#include <iostream>using namespace std;class line{public:line(float len);virtual float area() = 0;virtual float volume() = 0;protected:float m_len;};line :: line(float len): m_len(len){}class rec: public line{public:rec(float len, float width);float area();protected:float m_width;};rec :: rec(float len, float width): line(len), m_width(width){}float rec :: area(){return m_len * m_width;float area();protected: float m_width;}Rec::Rec(float len, float width): Line(len), m_width(width){}float Rec::area(){return m_len * m_width;}class cuboid: public rec{public:cuboid(float len, float width, float height); float area(); float volume();protected: float m_height;};Cuboid::Cuboid(float len, float width, float height): Rec(len, width), m_height(height){}float Cuboid :: area(){ return 2 * ( m_len*m_width + m_len*m_height + m_width*m_height);}float Cuboid :: volume(){return m_len * m_width * m_height;}class Cube: public Cuboid{public: Cube(float len); float area(); float volume();};Cube::Cube(float len): Cuboid(len, len, len){}float Cube::area(){return 6 * m_len * m_len; }float Cube::volume(){return m_len * m_len * m_len; }int main(){ Line *p = new Cuboid(10, 20, 30); cout<<"The area of Cuboid is "<<p->area()<<endl; cout<<"The volume of Cuboid is "<<p->volume()<<endl; p = new Cube(15); cout<<"The area of Cube is "<<p->area()<<endl; cout<<"The volume of Cube is "<<p->volume()<<endl;return 0;}在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现)。这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的。虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”。
注意:
1) 一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。
2) 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。
2) 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。
阅读全文
0 0
- 初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。
- 虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数
- C方式实现C++纯虚函数
- [C++]虚函数和纯虚函数
- 虚函数,纯虚函数和多态
- 虚函数、纯虚函数和多态
- C++纯虚函数实现
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数,纯虚函数的解释和内部实现&&虚函数表的真实样子
- 虚函数-虚表-虚指针-多态性-如何实现多态-纯虚函数-抽象类-转
- 虚函数-虚表-虚指针-多态性-如何实现多态-纯虚函数-抽象类
- 【C++】普通函数、虚函数和纯虚函数
- 【bzoj2770】YY的Treap
- 开发者招聘
- c语言枚举和数组
- 使用版本控制的注意事项(Git、SVN等)
- jQuery ajax中form.serialize() 方法-输出序列化表单值
- 初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。
- js的坑 js学习特殊语法
- 从Tomcat的架构谈Servlet职责
- 判断一个数组是否有序
- tensorflow训练cnn网络识别验证码
- nginx配置php-fpm
- 3-1 Score(P57) / UVA 1585
- jq的选择器
- CodeForces 681C Heap Operations