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!

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

相关文章
|
23天前
|
编译器 C++ 开发者
【C++】继承
C++中的继承是面向对象编程的核心特性之一,允许派生类继承基类的属性和方法,实现代码复用和类的层次结构。继承有三种类型:公有、私有和受保护继承,每种类型决定了派生类如何访问基类成员。此外,继承还涉及构造函数、析构函数、拷贝构造函数和赋值运算符的调用规则,以及解决多继承带来的二义性和数据冗余问题的虚拟继承。在设计类时,应谨慎选择继承和组合,以降低耦合度并提高代码的可维护性。
31 1
【C++】继承
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
90 11
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
59 1
|
2月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
45 1
|
2月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
21 0
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
37 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
39 0
|
3月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
3月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
4月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数