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!

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

相关文章
|
17天前
|
C++
C++一分钟之-虚函数与抽象类
【6月更文挑战第21天】在C++中,虚函数与抽象类是多态的基础,增进类间耦合与灵活性。虚函数实现动态绑定,抽象类定义不可实例化的接口。关键点包括:记得使用`virtual`,避免滥用虚函数,确保派生类实现纯虚函数。抽象类不能直接实例化,派生类必须实现所有纯虚函数。通过实例代码学习和实践,能更好地掌握这些概念以优化代码设计。
23 2
|
7天前
|
编译器 C++
【C++】详解C++的继承
【C++】详解C++的继承
|
5天前
|
存储 编译器 数据安全/隐私保护
|
12天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
13 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
12天前
|
C++
【C++】学习笔记——继承_2
【C++】学习笔记——继承_2
15 1
|
14天前
|
编译器 C++
C++中的继承
C++中的继承
18 1
|
17天前
|
C++
C++一分钟之-继承与多态概念
【6月更文挑战第21天】**C++的继承与多态概述:** - 继承允许类从基类复用代码,增强代码结构和重用性。 - 多态通过虚函数实现,使不同类对象能以同一类型处理。 - 关键点包括访问权限、构造/析构、菱形问题、虚函数与动态绑定。 - 示例代码展示如何创建派生类和调用虚函数。 - 注意构造函数初始化、空指针检查和避免切片问题。 - 应用这些概念能提升程序设计和维护效率。
22 2
|
5天前
|
存储 编译器 C++
C++基础知识(六:继承)
多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。
|
11天前
|
程序员 编译器 C++
【c++】继承
【c++】继承
9 0
|
12天前
|
安全 Java 程序员
【C++航海王:追寻罗杰的编程之路】继承你学会了么?
【C++航海王:追寻罗杰的编程之路】继承你学会了么?
10 0