C++继承二之虚函数

简介: 在类方法声明如果包含了 vritual关键字那么该方法称为虚行数,继承类中相同的定义的函数可以使用virtual也可以不使用 虚函数一般用于使用相同的原型重新定义基类的函数,实现不同的功能 一般使用virtual更加明确 我们还是应用C++ Primer plu...
在类方法声明如果包含了 vritual关键字那么该方法称为虚行数,继承类中相同的定义的函数可以使用virtual也可以不使用
虚函数一般用于使用相同的原型重新定义基类的函数,实现不同的功能
一般使用virtual更加明确
我们还是应用C++ Primer plus中的例子
#ifndef BRASS_H_
#define Brass_H_
#include
using namespace std;


class Brass
{
        private:
                string fullName;
                long acctNum;
                double balance;
        public:
                Brass(const string& s="Nullbody",long an = -1,double bal=0.0);
                void Deposit(double amt);
                virtual void Withdraw(double amt);
                virtual void ViewAcct() const;
                double Balance() const;
                virtual ~Brass(){}
};


class BraussPlus:public Brass
{
        private:
                double maxLoan;
                double rate;
                double owesBank;
        public:
                BrassPlus(const string& s="Nullbody",long an=-1,double bal=0.0,double ml=500,double r=0.11125);
                BrassPlus(const Brass& ba,double ml=500,double r=0.11125);
                virtual void ViewAcct() const; //这里的virtual是可选的
                virtual void Withdraw(double amt); //这里的virtual是可选的
                void ResetMax(double m){maxLoan=m;}
                void ResetRate(double r){rate=r;}
                void ResetOwes(){owesBank = 0;}
                 virtual ~BrassPlus(){}
};


#endif


引用:
Brass dom("test",123,123.23);
Brassplus dot("test",123,123.23);
Brass& b1_ref = dom;
Brass& b2_ref = dot;
b1_ref.ViewAcct(); //1
b2_ref.ViewAcct(); //2
指针:
Brass dom("test",123,123.23);
Brassplus dot("test",123,123.23);
Brass* b1_ref = &dom;
Brass* b2_ref = ˙
b1_ref->ViewAcct(); //1
b2_ref->ViewAcct(); //2


第一个b1_ref.ViewAcct(); b1_ref->ViewAcct(); 调用Brass::ViewAcct()
第二个b2_ref.ViewAcct(); b2_ref->ViewAcct();  调用Brassplus::ViewAcct()
如果不是虚函数那么他们都是调用的Brass::ViewAcct()
因为这个引用是Brass的引用


  由此我们可以推断出虚函数的主要目的在于能够根据引用或者指针指向的对象的类型来
调用相应的函数,在前面说的一个基类的引用或者指针可以指向派生类,这种叫做向上
转换,但是调用的接口一定是基类的,一旦有了虚函数就可以进行自适应。
  再来看一个例子:
  void t1(Brass& in);
  我们传入 t1(dom)和t1(dot) 如果in.ViewAcct()显然两者不同,
  更可能的我们可以定义一个基类的引用数组或者指针数组,将基类和派生类的指针存放到一个数组中。
  Brass* in[2] 
  或者
  Brass& in[2]
  显然 in[0] = *dom或者in[0]=dom
  和   in[1] = *dot或者in[1]=dot
  是成立的。
  
  然后我们需要谈一下虚函数的动态编联以及虚函数原理
  
  一般函数或者方法在编译的时候是其形参类型是固定的,直接编译即可,这叫做静态变联。
  但是这种方式放到虚函数就不适用了比如前面的void t1(Brass& in);显然in这个形参的含义
  是不固定他肯能指向Brass或者Brassplus类型的引用,所以需要在运行的时候才能确定,这叫做
  动态编联
  虚函数会位每个对象末尾增加一个指针,这个指针指向一个这样的数组,数组中全是指向虚函数的
  地址,这个数组叫做vtbl(virtual function table),如果派生类包含相同的虚函数将会覆盖基类的
  虚函数的地址,如果没有定义相同的虚函数就沿用老的地址,如果派生类新建了新的虚函数在数组
  中分配一个地址,如果
  比如前面的Brass dom和Brassplus dot
  dom-->包含地址-->指向vtbl-->vtbl包含地址 a1|a2 分别是Withdraw和ViewAcct
  dot-->包含地址-->指向vtbl-->vtbl包含地址 a3|a4 分别是Withdraw和ViewAcct
  因为Brassplus定义了新的虚函数所以a3 a4覆盖了a1 a2.
  
  说明一下虚拟析构函数,如果析构函数不是虚析构函数,那么只会调用指针或者引用类类型的析构函数
  单数如果析构函数也许虚函数,那么会首先调用指针或者引用指向的对象的析构函数然后调用基类的析构函数
  比如Brassplus包含了virtual ~BrassPlus()这是默认存在的然后基类定义了virtual ~Brass()
  
  最后我们来看一下关注点
  1、在基础类中包含了virtual关键字,那么在派生出来的类中相同的函数将成为虚函数,派生类中virtual是可选的
  2、析构函数应该是虚函数,除非类不会成为基类
  3、新的虚函数应该和基类的虚函数原型保持一致,
     比如virtual void Withdraw(double amt);
     不要变为
         virtual void Withdraw(void);
  4、虚函数实际上一种基于向上转换特性的,一种根据基类指针自适应的特性
  5、基类指针或者引用可以说是一种可以指向和引用派生类或者派生类的派生类的万能指针和引用。
     基于这种特性基类指针可以指向任何派生类,虚函数能够自适应指向的类,调用正确的方法。
  6、如果不使用虚函数的特性,我们只需要定义派生类指针即可,或者不使用指针和引用,因为虚方法
     的自适应特性只有指针和引用才可以
     比如我们可以
     Brassplus dot("test",123,123.23);
     Brassplus& b2_ref = dot; 
     b2_ref.ViewAcct 必然指向Brassplus::ViewAcct()
     因为默认向下转换是不可行的,在前面已经指出
     或者
     Brassplus dot1=dot
     dot1.ViewAcct 必然使用Brassplus::ViewAcct()
     因为这不是指针也不是引用
7、如上虚函数自适应基于基类的指针或者引用和向上转换特性完成。
8、虚函数使用动态编联,实现是通过 vtbl(virtual function table)实现的。

来看一段代码,本代码用于简单的检查一个用户是否是普通用户还是VIP用户还是注册用户

点击(此处)折叠或打开

  1. 头文件
  2. /*************************************************************************
  3.   > File Name: class.h
  4.   > Author: gaopeng
  5.   > Mail: gaopp_200217@163.com
  6.   > Created Time: Wed 31 Aug 2016 04:52:14 AM CST
  7.  ************************************************************************/

  8. #include<iostream>
  9. #include<string.h>
  10. #include <stdlib.h>
  11. using namespace std;


  12. class normalu
  13. {
  14.         private:
  15.                 string name;
  16.                 unsigned int id;
  17.                 char* password;
  18.         public:
  19.                 unsigned int pri;
  20.                 normalu(string username,unsigned int uid,unsigned int upri,const char* upass):name(username),id(uid),pri(upri)
  21.         {
  22.                 password = new char[strlen(upass)+1];
  23.                 strcpy(password,upass);
  24.         }
  25.                 normalu(const normalu& innor);
  26.                 virtual ~normalu();
  27.                 void show()
  28.                 {
  29.                         cout<<"name:"<<name<<endl;
  30.                         cout<<"id:"<<id<<endl;
  31.                 }
  32.                 virtual void checkpri(void)
  33.                 {
  34.                         if(pri>5)
  35.                         {
  36.                                 cout<<"User Is Normal User!\n";
  37.                                 show();
  38.                                 exit(0);
  39.                         }
  40.                         else
  41.                         {
  42.                                 cout<<"User Is Not Normal User!\n";
  43.                                 show();
  44.                                 cout<<"User Is Only register user!\n";
  45.                         }
  46.                 }
  47. };

  48. class vipu:public normalu
  49. {
  50.         public:
  51.                 vipu(string username,unsigned int uid,unsigned int upri,const char* upass):normalu(username,uid,upri,upass){}
  52.                 vipu(const normalu& innor):normalu(innor){}
  53.                 virtual void checkpri(void)
  54.                 {
  55.                         if (pri>10)
  56.                         {
  57.                                 cout<<"User Is Vip User\n";
  58.                                 show();
  59.                                 exit(0);
  60.                         }
  61.                         else
  62.                         {
  63.                                 cout<<"This User Is Not A Vip user!\n";
  64.                                 cout<<"Will Check This User Or Normaluser!\n";
  65.                         }
  66.                 }
  67.      virtual ~vipu(){};
  68. };

  69. normalu::normalu(const normalu& innor)
  70. {
  71.         int len = strlen(innor.password);
  72.         password = new char[len+1];
  73.         strcpy(password,innor.password);
  74.         id=innor.id;
  75.         name=innor.name;
  76.         pri=innor.pri;
  77. }
  78. normalu::~normalu()
  79. {
  80.          delete [] password;
  81. }
  82. 主函数
  83. /*************************************************************************
  84.   > File Name: main.cpp
  85.   > Author: gaopeng
  86.   > Mail: gaopp_200217@163.com
  87.   > Created Time: Wed 31 Aug 2016 04:52:20 AM CST
  88.  ************************************************************************/

  89. #include<iostream>
  90. #include"class.h"
  91. using namespace std;

  92. int main(void)
  93. {
  94.         normalu u1("gaopeng",1213,3,"test123");
  95.         vipu u2(u1);
  96.         normalu* pu;
  97.         pu = &u2;
  98.         pu->checkpri();
  99.         pu = &u1;
  100.         pu->checkpri();
  101. }

输出:
This User Is Not A Vip user!
Will Check This User Or Normaluser!
User Is Not Normal User!
name:gaopeng
id:1213
User Is Only register user!

这里演示上面说的大部分问题和实现,包括了析构函数

目录
打赏
0
0
0
0
91
分享
相关文章
|
2月前
|
C++学习之继承
通过继承,C++可以实现代码重用、扩展类的功能并支持多态性。理解继承的类型、重写与重载、多重继承及其相关问题,对于掌握C++面向对象编程至关重要。希望本文能为您的C++学习和开发提供实用的指导。
62 16
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
61 5
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
52 4
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
41 3
【C++】继承
C++中的继承是面向对象编程的核心特性之一,允许派生类继承基类的属性和方法,实现代码复用和类的层次结构。继承有三种类型:公有、私有和受保护继承,每种类型决定了派生类如何访问基类成员。此外,继承还涉及构造函数、析构函数、拷贝构造函数和赋值运算符的调用规则,以及解决多继承带来的二义性和数据冗余问题的虚拟继承。在设计类时,应谨慎选择继承和组合,以降低耦合度并提高代码的可维护性。
46 1
【C++】继承
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
116 11
|
5月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
79 1
|
5月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
69 1
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
37 0
|
5月前
|
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
63 0