在类方法声明如果包含了 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用户还是注册用户
输出:
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!
这里演示上面说的大部分问题和实现,包括了析构函数
虚函数一般用于使用相同的原型重新定义基类的函数,实现不同的功能
一般使用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用户还是注册用户
点击(此处)折叠或打开
- 头文件
- /*************************************************************************
- > File Name: class.h
- > Author: gaopeng
- > Mail: gaopp_200217@163.com
- > Created Time: Wed 31 Aug 2016 04:52:14 AM CST
- ************************************************************************/
-
- #include<iostream>
- #include<string.h>
- #include <stdlib.h>
- using namespace std;
-
-
- class normalu
- {
- private:
- string name;
- unsigned int id;
- char* password;
- public:
- unsigned int pri;
- normalu(string username,unsigned int uid,unsigned int upri,const char* upass):name(username),id(uid),pri(upri)
- {
- password = new char[strlen(upass)+1];
- strcpy(password,upass);
- }
- normalu(const normalu& innor);
- virtual ~normalu();
- void show()
- {
- cout<<"name:"<<name<<endl;
- cout<<"id:"<<id<<endl;
- }
- virtual void checkpri(void)
- {
- if(pri>5)
- {
- cout<<"User Is Normal User!\n";
- show();
- exit(0);
- }
- else
- {
- cout<<"User Is Not Normal User!\n";
- show();
- cout<<"User Is Only register user!\n";
- }
- }
- };
-
- class vipu:public normalu
- {
- public:
- vipu(string username,unsigned int uid,unsigned int upri,const char* upass):normalu(username,uid,upri,upass){}
- vipu(const normalu& innor):normalu(innor){}
- virtual void checkpri(void)
- {
- if (pri>10)
- {
- cout<<"User Is Vip User\n";
- show();
- exit(0);
- }
- else
- {
- cout<<"This User Is Not A Vip user!\n";
- cout<<"Will Check This User Or Normaluser!\n";
- }
- }
- virtual ~vipu(){};
- };
-
- normalu::normalu(const normalu& innor)
- {
- int len = strlen(innor.password);
- password = new char[len+1];
- strcpy(password,innor.password);
- id=innor.id;
- name=innor.name;
- pri=innor.pri;
- }
- normalu::~normalu()
- {
- delete [] password;
- }
- 主函数
- /*************************************************************************
- > File Name: main.cpp
- > Author: gaopeng
- > Mail: gaopp_200217@163.com
- > Created Time: Wed 31 Aug 2016 04:52:20 AM CST
- ************************************************************************/
-
- #include<iostream>
- #include"class.h"
- using namespace std;
-
- int main(void)
- {
- normalu u1("gaopeng",1213,3,"test123");
- vipu u2(u1);
- normalu* pu;
- pu = &u2;
- pu->checkpri();
- pu = &u1;
- pu->checkpri();
- }
输出:
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!
这里演示上面说的大部分问题和实现,包括了析构函数