虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数

来源:互联网 发布:商户销售数据采集监控 编辑:程序博客网 时间:2024/06/05 14:10

1.c++多态的概念以及用途。

1.1虚函数实现多态
通过基类指针只能够访问派生类的成员变量,不能够访问派生类的成员函数。
解决问题的办法:使用虚函数(virtual function),只需要在函数声明前面增加virtual关键字。看下面的代码。
[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class people{  
  5. public :  
  6.     people(char *name, int age);  
  7.     virtual void show();  
  8. protected :  
  9.     char *m_name;  
  10.     int m_age;  
  11. };  
  12.   
  13. people :: people (char *name, int age) : m_name(name), m_age(age){}  
  14.   
  15. void people :: show()  
  16. {  
  17.     cout<<m_name<<", "<<m_age<<" old"<<endl;  
  18. }  
  19.   
  20. class student : public people{  
  21. public :   
  22.     student (char *name, int age, float score);  
  23.     virtual void show();  
  24. protected :  
  25.     float m_score;  
  26. };  
  27.   
  28. student :: student (char *name, int age, float score) : people(name, age), m_score(score){}  
  29.   
  30. void student :: show()  
  31. {  
  32.     cout<<m_name<<", "<<m_age<<" old, "<<m_score<<endl;  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.     people *p = new people("xiaoli", 22);  
  38.     p->show();  
  39.       
  40.     p = new student("xiaozhang", 16, 95.6);  
  41.     p->show();  
  42.       
  43.     delete p;  
  44.       
  45.     return 0;  
  46. }  
结果:
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.存在基类的指针,通过该指针调用虚函数!
看下面的代码。
[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class base{  
  5. public :  
  6.     virtual void func();  
  7.     virtual void func(int);  
  8. };  
  9.   
  10. void base :: func()  
  11. {  
  12.     cout<<"base:func()"<<endl;  
  13. }  
  14.   
  15. void base :: func(int a)  
  16. {  
  17.     cout<<"base:func(int)"<<endl;  
  18. }  
  19.   
  20. class der : public base{  
  21. public :  
  22.     void func();  
  23.     void func(char *);  
  24. };  
  25.   
  26. void der :: func()  
  27. {  
  28.     cout<<"der:func()"<<endl;  
  29. }  
  30.   
  31. void der :: func(char *b)  
  32. {  
  33.     cout<<"der:func(char *)"<<endl;  
  34. }  
  35.   
  36. int main()  
  37. {  
  38.     base *p = new der;  
  39.     p->func();          //调用的是派生类的虚函数,构成了多态。  
  40.     p->func(100);       //调用的是基类的虚函数,因为派生类中没有函数遮蔽它。  
  41.     //p_>func("hello"); //出现编译错误,因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。  
  42.       
  43.     return 0;  
  44. }  
再来补充一下什么时候需要用到虚函数:
首先看成员函数所在的类是否会作为基类。
然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。

2.虚析构函数

首先要明白,构造函数不能是虚函数,因为派生类不能继承基类的构造函数,将构造函数声明为虚函数没有意义!
先看代码
[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class base{  
  5. public :  
  6.     base();  
  7.     virtual ~base();  
  8. protected :  
  9.     char *ptr;  
  10. };  
  11.   
  12. base :: base()  
  13. {  
  14.     ptr = new char[100];  
  15.     cout<<"base construct"<<endl;  
  16. }  
  17.   
  18. base :: ~base()  
  19. {  
  20.     delete [] ptr;  
  21.     cout<<"~base deconstruct"<<endl;  
  22. }  
  23.   
  24. class der : public base{  
  25. public :  
  26.     der();  
  27.     ~der();  
  28. protected :  
  29.     char *name;  
  30. };  
  31.   
  32. der :: der()  
  33. {  
  34.     name = new char[100];  
  35.     cout<<"der construct"<<endl;  
  36. }  
  37.   
  38. der :: ~der()  
  39. {  
  40.     delete[] name;  
  41.     cout<<"~der deconstruct"<<endl;  
  42. }  
  43.   
  44. int main()  
  45. {  
  46.     base *p = new der;  
  47.     delete p;     
  48.       
  49.     return 0;  
  50. }  
将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;
也就是说,指针指向哪个类的对象就调用哪个类的函数。
(补充!!:指针访问非虚函数时,编译器会根据指针的类型来确定要调用的函数。如果指针是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,
在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,并且这个过程是隐式完成的)。
在实际开发中,一旦我们自己定义了析构函数,就是希望在对象销毁时用它来进行清理工作,比如释放内存、关闭文件等,如果这个类又是一个基类,
那么我们就必须将该析构函数声明为虚函数,否则就有内存泄露的风险。也就是说,大部分情况下都应该将基类的析构函数声明为虚函数。

3.虚函数表和多态实现机制。

强调!!!当通过指针访问类的成员函数时:
1.如果该函数是非虚函数,那么编译器会根据指针的类型找到该函数;也就是说,指针是哪个类的类型就调用哪个类的函数。
2.如果该函数是虚函数,并且派生类有同名的函数遮蔽它,那么编译器会根据指针的指向找到该函数;
也就是说,指针指向的对象属于哪个类就调用哪个类的函数。这就是多态。

编译器之所以能通过指针指向的对象找到虚函数,是因为在创建对象时额外地增加了虚函数表。

看一个例子:
[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4. //People类  
  5. class People{  
  6. public:  
  7.     People(string name, int age);  
  8. public:  
  9.     virtual void display();  
  10.     virtual void eating();  
  11. protected:  
  12.     string m_name;  
  13.     int m_age;  
  14. };  
  15. People::People(string name, int age): m_name(name), m_age(age){ }  
  16. void People::display(){  
  17.     cout<<"Class People:"<<m_name<<"今年"<<m_age<<"岁了。"<<endl;  
  18. }  
  19. void People::eating(){  
  20.     cout<<"Class People:我正在吃饭,请不要跟我说话..."<<endl;  
  21. }  
  22. //Student类  
  23. class Student: public People{  
  24. public:  
  25.     Student(string name, int age, float score);  
  26. public:  
  27.     virtual void display();  
  28.     virtual void examing();  
  29. protected:  
  30.     float m_score;  
  31. };  
  32. Student::Student(string name, int age, float score):  
  33.     People(name, age), m_score(score){ }  
  34. void Student::display(){  
  35.     cout<<"Class Student:"<<m_name<<"今年"<<m_age<<"岁了,考了"<<m_score<<"分。"<<endl;  
  36. }  
  37. void Student::examing(){  
  38.     cout<<"Class Student:"<<m_name<<"正在考试,请不要打扰T啊!"<<endl;  
  39. }  
  40. //Senior类  
  41. class Senior: public Student{  
  42. public:  
  43.     Senior(string name, int age, float score, bool hasJob);  
  44. public:  
  45.     virtual void display();  
  46.     virtual void partying();  
  47. private:  
  48.     bool m_hasJob;  
  49. };  
  50. Senior::Senior(string name, int age, float score, bool hasJob):  
  51.     Student(name, age, score), m_hasJob(hasJob){ }  
  52. void Senior::display(){  
  53.     if(m_hasJob){  
  54.         cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,并且顺利找到了工作,Ta今年"<<m_age<<"岁。"<<endl;  
  55.     }else{  
  56.         cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,不过找工作不顺利,Ta今年"<<m_age<<"岁。"<<endl;  
  57.     }  
  58. }  
  59. void Senior::partying(){  
  60.     cout<<"Class Senior:快毕业了,大家都在吃散伙饭..."<<endl;  
  61. }  
  62. int main(){  
  63.     People *p = new People("赵红", 29);  
  64.     p -> display();  
  65.     p = new Student("王刚", 16, 84.5);  
  66.     p -> display();  
  67.     p = new Senior("李智", 22, 92.0, true);  
  68.     p -> display();  
  69.     return 0;  
  70. }  
各个类的对象内存模型如下所示:


这里的东西及原理有些复杂,况且还是单继承。对于多继承,复杂程度不可想象,这里不做深究,有兴趣的同学自己研究吧。知道怎么用虚函数的我,先满足一下。代以后再深究。

4.纯虚函数

在C++中,可以将虚函数声明为纯虚函数,
语法格式为:
               virtual 返回值类型 函数名 (函数参数) = 0;
1.纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。
2. 最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

包含纯虚函数的类称为抽象类(Abstract Class)。抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。看代码
[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class line{  
  5. public:  
  6.     line(float len);  
  7.     virtual float area() = 0;  
  8.     virtual float volume() = 0;  
  9. protected:  
  10.     float m_len;  
  11. };  
  12.   
  13. line :: line(float len): m_len(len){}  
  14.   
  15. class rec: public line{  
  16. public:  
  17.     rec(float len, float width);  
  18.     float area();  
  19. protected:  
  20.     float m_width;  
  21. };  
  22.   
  23. rec :: rec(float len, float width): line(len), m_width(width){}  
  24.   
  25. float rec :: area()  
  26. {  
  27.     return m_len * m_width;  
  28.     float area();  
  29. protected:  
  30.     float m_width;  
  31. }  
  32.   
  33. Rec::Rec(float len, float width): Line(len), m_width(width){}  
  34. float Rec::area()  
  35. {  
  36.     return m_len * m_width;  
  37. }  
  38.   
  39. class cuboid: public rec{  
  40. public:  
  41.     cuboid(float len, float width, float height);  
  42.     float area();  
  43.     float volume();  
  44. protected:  
  45.     float m_height;  
  46. };  
  47.   
  48. Cuboid::Cuboid(float len, float width, float height): Rec(len, width), m_height(height){}  
  49.   
  50. float Cuboid :: area()  
  51. {   
  52.     return 2 * ( m_len*m_width + m_len*m_height + m_width*m_height);  
  53. }  
  54.   
  55. float Cuboid :: volume(){  
  56.     return m_len * m_width * m_height;  
  57. }  
  58.   
  59. class Cube: public Cuboid{  
  60. public:  
  61.     Cube(float len);  
  62.     float area();  
  63.     float volume();  
  64. };  
  65.   
  66. Cube::Cube(float len): Cuboid(len, len, len){}  
  67.   
  68. float Cube::area(){  
  69.     return 6 * m_len * m_len;   
  70. }  
  71.   
  72. float Cube::volume(){  
  73.     return m_len * m_len * m_len;   
  74. }  
  75.   
  76. int main()  
  77. {  
  78.     Line *p = new Cuboid(10, 20, 30);  
  79.     cout<<"The area of Cuboid is "<<p->area()<<endl;  
  80.     cout<<"The volume of Cuboid is "<<p->volume()<<endl;  
  81.     
  82.     p = new Cube(15);  
  83.     cout<<"The area of Cube is "<<p->area()<<endl;  
  84.     cout<<"The volume of Cube is "<<p->volume()<<endl;  
  85.       
  86.     return 0;  
  87. }   
在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现)。这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的。虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”。

注意:
1) 一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。

2) 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。
原创粉丝点击