C++学习:虚函数,虚继承,纯虚函数(virtual)虚析构函数
- 虚函数
- 纯虚函数
- 虚析构函数
- 虚继承
简介
在java这种高级语言中,有abstract和interface这两个关键字.代表的是抽象类和接口,但是在C++这门语言中,并没有专属的关键字来表示抽象类或者接口,但是它却也有接口和抽象类这样的概念.所以在这里引入了vitural这个关键字,虚构的,虚有的意思.
因为C++在编译的时候的专有特性,它在编译的时候,一般的内部成员函数都是静态加载的,因此也就在一般情况下不可能出现动态调用的过程,也就是我门在java中说的,父类出现的地方子类都可以出现.而vitural这个关键字的出现,多数使用的场景下是为了,能够让够选择性的去调用继承来自父类并且重写过的方法.
因此在本篇博客也就引入了虚构这样的一种概念.
提示:
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041/article/details/73255812
虚构函数:
为什么要有虚构函数??先看一组代码:
/*
* ===========================================================================
*
* Filename: NomalFunctionExtends.cpp
* Description: 正常的继承,父类为一般成员方法
* Version: 1.0
* Created: 2017年06月22日 21时42分43秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
using namespace::std;
class Car{
public:
Car(){
cout<<"Car consstructor"<<endl;
}
~Car(){
cout<<"Car destructor"<<endl;
}
void start() const{
cout<<"car start"<<endl;
}
void stop() const{
cout<<"cat stop"<<endl;
}
};
class Benz : public Car{
public:
Benz(){
cout<<"Benz constructor"<<endl;
}
~Benz(){
cout<<"Benz destructor"<<endl;
}
void start() const{
cout<<"Benz start"<<endl;
}
void stop() const{
cout<<"Benz stop"<<endl;
}
};
class Baoma:public Car{
public:
Baoma(){
cout<<"Baoma constructor"<<endl;
}
~Baoma(){
cout<<"Baoma destructor"<<endl;
}
void start() const{
cout<<"Baoma constructor"<<endl;
}
void stop() const{
cout<<"Baoma destructor"<<endl;
}
private:
int speed;
};
void carFunction(Car *car){
car->start();
car->stop();
}
int main(int argc,char *argv[]){
Car *benz = new Benz();
cout<<sizeof(Benz)<<endl;
carFunction(benz);
Car *baoma = new Baoma();
cout<<sizeof(Baoma)<<endl;
carFunction(baoma);
delete benz;
delete baoma;
return 0;
}
然后看输出结果;
Car consstructor
Benz constructor
1 //内部没有成员变量,因此只有一个字节的空间
car start
cat stop
Car consstructor
Baoma constructor
4 //函数是不占用内存的,baoma中有一个int类型.所以sizeof为4
car start
cat stop
Car destructor
Car destructor
注意:
C++编译器不允许对象为零长度。试想一个长度为0的对象在内存中怎么存放?怎么获取它的地址?为了避免这种情况,C++强制给这种类插入一个缺省成员,长度为1。如果有自定义的变量,变量将取代这个缺省成员
Benz和Baoma都是继承自Car,根据里式替换原则,父类能够出现的地方,那么子类也一定能够出现.依赖抽象而不去依赖具体,在上述的函数调用过程中,我们传进去的是benz和baoma指针.但是在调用函数的时候,它并没有去调用子类的方法,这也就是一般成员函数的局限性,就是在编译的时候,一般性的函数已经被静态的编译进去,所以在调用的时候不能去选择动态调用.
加入vitural关键字修饰的函数,将父类函数变为虚函数,看看变化
/*
* ===========================================================================
*
* Filename: NomalFunctionExtends.cpp
* Description: 正常的继承,父类为一般成员方法
* Version: 1.0
* Created: 2017年06月22日 21时42分43秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
using namespace::std;
class Car{
public:
Car(){
cout<<"Car consstructor"<<endl;
}
~Car(){
cout<<"Car destructor"<<endl;
}
virtual void start() {
cout<<"car start"<<endl;
}
virtual void stop() {
cout<<"cat stop"<<endl;
}
};
class Benz : public Car{
public:
Benz(){
cout<<"Benz constructor"<<endl;
}
~Benz(){
cout<<"Benz destructor"<<endl;
}
//子类继承父类,如果是虚函数,可以写上vitural也可以不写
virtual void start() {
cout<<"Benz start"<<endl;
}
void stop() {
cout<<"Benz stop"<<endl;
}
};
class Baoma:public Car{
public:
Baoma(){
cout<<"Baoma constructor"<<endl;
}
~Baoma(){
cout<<"Baoma destructor"<<endl;
}
void start() {
cout<<"Baoma start"<<endl;
}
void stop() {
cout<<"Baoma stop"<<endl;
}
private:
int speed;
};
void carFunction(Car *car){
car->start();
car->stop();
}
int main(int argc,char *argv[]){
Car *benz = new Benz();
cout<<sizeof(Benz)<<endl;
carFunction(benz);
Car *baoma = new Baoma();
cout<<sizeof(Baoma)<<endl;
carFunction(baoma);
delete benz;
delete baoma;
return 0;
}
输出结果:
Car consstructor
Benz constructor
8
Benz start
Benz stop
Car consstructor
Baoma constructor
16
Baoma start
Baoma stop
Car destructor
Car destructor
从上面的输出结果中可以看到,加入了虚函数之后,调用不同指针对象指定函数的时候,这个时候都是去自动调用子类中的具体函数形式,而不是像一般函数的调用一样,只是去调用父类的函数.这就是virtural关键字的作用,因为一般函数调用编译的时候是静态编译的时候就已经决定了,加入了virtural的函数,一个类中函数的调用并不是在编译的时候决定下来的,而是在运行时候被确定的,这也就是虚函数.
虚函数就是由于在由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被 为“虚”函数。函数只能借助于指针或者引用来达到多态的效果
注意点:
1:C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
2:对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数
3:带有虚函数的对象自身确实插入了一些指针信息,而且这个指针信息并不随着虚函数的增加而增大,这也就是为什么上述增加了虚函数后,出现了size变大的现象
纯虚函数
在C++中,如果一个变量使用const进行修饰,那么就称为常量,如果一个函数被const修饰,就被称为常函数,那么如果一个虚函数被const进行修饰呢??类似
virtural void start() const;
在java中我们有一种类叫做抽象类,抽象类有一种特性,就是父类不能实例化,并且需要有abstract关键字进行修饰.但是在C++中同样也有抽象函数,那么在C++中抽象函数是怎么去表示的,由于没有抽象类型关键字,所以C++采用了virtural与const搭配的这种形式,构造出这样一种抽象函数与抽象类.
注意:
抽象类是不能够进行实例化的
/*
* ===========================================================================
*
* Filename: NomalFunctionExtends.cpp
* Description: 抽象类
* 抽象类的规定:
1:带有纯虚函数的类叫做抽象类(注意与接口区分)
* 2:抽象类只能作为其他类的基类,处于继承层次的上层
* 3:抽象类不能用作参数类型,函数返回类型,或者显示转换(抽象类不能够进行实例化)
* 4:可以定义抽象类的指针和引用,此指针指向它的派生类,
* 5:抽象类一般既包括成员函数,又包括数据成员变量
* Version: 1.0
* Created: 2017年06月22日 21时42分43秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
using namespace::std;
/* *
*将Car声明定义成一个抽象类
* */
class Car{
public:
Car(){
cout<<"Car consstructor"<<endl;
}
~Car(){
cout<<"Car destructor"<<endl;
}
virtual void start() const = 0; //抽象类必须包含纯虚函数,并且在基类中不能定义,只能声明
virtual void stop() const = 0;
};
class Benz : public Car{
public:
Benz(){
cout<<"Benz constructor"<<endl;
}
~Benz(){
cout<<"Benz destructor"<<endl;
}
//子类继承父类,如果是虚函数,可以写上vitural也可以不写,重写父类中的常虚函数
virtual void start() const{
cout<<"Benz start"<<endl;
}
void stop() const{
cout<<"Benz stop"<<endl;
}
};
class Baoma:public Car{
public:
Baoma(){
cout<<"Baoma constructor"<<endl;
}
~Baoma(){
cout<<"Baoma destructor"<<endl;
}
void start() const{
cout<<"Baoma start"<<endl;
}
void stop() const{
cout<<"Baoma stop"<<endl;
}
private:
int speed;
};
void carFunction(Car *car){
car->start();
car->stop();
}
int main(int argc,char *argv[]){
//抽象类是不能够进行实例化的,所以下面注释掉的函数是错误的
//Car *car = new Car();
Car *benz = new Benz(); //父类的指针指向子类的成员
cout<<sizeof(Benz)<<endl;
carFunction(benz);
Car *baoma = new Baoma();
cout<<sizeof(Baoma)<<endl;
carFunction(baoma);
delete benz;
delete baoma;
//delete car;
return 0;
}
输出的结果为:
Car consstructor
Benz constructor
8
Benz start
Benz stop
Car consstructor
Baoma constructor
16
Baoma start
Baoma stop
Car destructor
Car destructor
注意:
1:带有纯虚函数的类叫做抽象类(注意与接口区分)
2:抽象类只能作为其他类的基类,处于继承层次的上层
3:抽象类不能用作参数类型,函数返回类型,或者显示转换(抽象类不能够进行实例化)
4:可以定义抽象类的指针和引用,此指针指向它的派生类,
5:抽象类一般既包括成员函数,又包括数据成员变量
虚析构函数
在之前的博客中,我们只知道什么是析构函数,也就是在类被回收的时候,才会去调用的函数,它的作用就是在类回收的时候去做一些资源的释放,但是什么才是虚析构函数??跟虚函数有什么关系??
一个类中如果有虚函数的话,那么这个类的虚函数就支持动态执行函数过程.也就是说.在使用这个类的对象之前,并不确切的知道这个对象是指向哪个子类对象的,这也就造成了.在析构的时候,可能也并不知道析构的是谁的具体对象.
1:虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
2:如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。因为它会为类增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移植性。
所以基本的一条是:无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
3:抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数
如果细心的看官会注意到,上面所有执行后的结果中,都没有去析构Benz和Baoma的对象,而是仅仅析构的是基类Car的对象,如果资源没有去进行回收的话,这样的一种情况必然会导致内存泄露
对之前的代码进行修改.将父类的析构函数转换称虚析构函数
/*
* ===========================================================================
*
* Filename: virtualConstructor.cpp
* Description: 虚析构函数
* 抽象类的规定:
1:带有纯虚函数的类叫做抽象类(注意与接口区分)
* 2:抽象类只能作为其他类的基类,处于继承层次的上层
* 3:抽象类不能用作参数类型,函数返回类型,或者显示转换(抽象类不能够进行实例化)
* 4:可以定义抽象类的指针和引用,此指针指向它的派生类,
* 5:抽象类一般既包括成员函数,又包括数据成员变量
* Version: 1.0
* Created: 2017年06月22日 21时42分43秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
using namespace::std;
/* *
*将Car声明定义成一个抽象类
* */
class Car{
public:
Car(){
cout<<"Car consstructor"<<endl;
}
virtual ~Car(){ //将析构函数变成虚析构函数,便于子类的析构函数也同时能够回收
cout<<"Car destructor"<<endl;
}
virtual void start() const = 0; //抽象类必须包含纯虚函数,并且在基类中不能定义,只能声明
virtual void stop() const = 0;
};
class Benz : public Car{
public:
Benz(){
cout<<"Benz constructor"<<endl;
}
~Benz(){
cout<<"Benz destructor"<<endl;
}
//子类继承父类,如果是虚函数,可以写上vitural也可以不写,重写父类中的常虚函数
virtual void start() const{
cout<<"Benz start"<<endl;
}
void stop() const{
cout<<"Benz stop"<<endl;
}
};
class Baoma:public Car{
public:
Baoma(){
cout<<"Baoma constructor"<<endl;
}
~Baoma(){
cout<<"Baoma destructor"<<endl;
}
void start() const{
cout<<"Baoma start"<<endl;
}
void stop() const{
cout<<"Baoma stop"<<endl;
}
private:
int speed;
};
void carFunction(Car *car){
car->start();
car->stop();
}
int main(int argc,char *argv[]){
//抽象类是不能够进行实例化的,所以下面注释掉的函数是错误的
//Car *car = new Car();
Car *benz = new Benz(); //父类的指针指向子类的成员
cout<<sizeof(Benz)<<endl;
carFunction(benz);
Car *baoma = new Baoma();
cout<<sizeof(Baoma)<<endl;
carFunction(baoma);
delete benz;
delete baoma;
//delete car;
return 0;
}
输出结果为:
Car consstructor
Benz constructor
8
Benz start
Benz stop
Car consstructor
Baoma constructor
16
Baoma start
Baoma stop
Benz destructor
Car destructor
Baoma destructor
Car destructor
从上面的结果中可以看到,在对象析构的时候,同时也会分别去调用子类的析构函数,这也就是虚析构函数的意义
虚继承
虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的
简而言之:
虚继承就是为了解决多重继承中,共享数据成员的使用而生的.
在前一篇文章中,讲到怎么去解决多重继承中,父类中同时包含一种变量的处理方法,可以通过父类来进行区分的方式,但是也有一种更有效的解决方法,就是将共有的变量抽取出来,作为顶层父类来处理,对之前的多重继承案例进行更改优化
/*
* ===========================================================================
*
* Filename: MultiInheritance.cpp
* Description:
* 智能手机是继承了智能设备,现在的手机都能看电视,也继承了功能手机的打电话,发短信的功能
* Version: 1.0
* Created: 2017年06月20日 22时05分41秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace::std;
/* *
*
*创建一个顶层的Size类
* */
class Size{
public:
Size(){
cout<<"Size constructor"<<endl;
}
~Size(){
cout<<"size destructor"<<endl;
}
void setWidth(int width){
s_width = width;
}
void setHeight(int height){
s_height = height;
}
int getWidth() const{
return s_width;
}
int getHeight() const{
return s_height;
}
void print(){
cout<<"height:"<<s_height<<endl;
cout<<"width:"<<s_width<<endl;
}
private:
int s_height;
int s_width;
};
/* *
*继承顶层Size类
* */
class Tv:public Size{
public:
Tv(){
cout<<"Tv constructor"<<endl;
}
~Tv(){
cout<<"Tv destructor"<<endl;
}
void playVideo(){
cout<<"play video"<<endl;
}
private:
};
/* *
* 继承底层Size类
* */
class Phone:public Size{
public:
Phone(){
cout<<"phone constructor"<<endl;
}
~Phone(){
cout<<"phone destructor"<<endl;
}
void ring(){
cout<<"take ring"<<endl;
}
void receiverRing(){
cout<<"receive ring"<<endl;
}
void sendMessage(){
cout<<"sendMessage"<<endl;
}
private:
};
class SmartPhone:public Tv,public Phone{
public:
SmartPhone(){
cout<<"SmartPhone constructor"<<endl;
}
~SmartPhone(){
cout<<"SmartPhone destructor"<<endl;
}
void printAll(){
cout<<"height:"<<Size::getHeight()<<endl;
cout<<"width:"<<Size::getWidth()<<endl;
}
private:
};
int main(int argc ,char *argv[]){
SmartPhone smartPhone;
cout<<"Tv size:"<<sizeof(Tv)<<endl;
cout<<"Phone size"<<sizeof(Phone)<<endl;
//如果不去进行虚继承的话,这个时候会产生函数的二义性,也就是在编译的时候找不到具体执行的方法
smartPhone.setWidth(1280);
smartPhone.setHeight(720);
smartPhone.print();
//子类虚继承父类之后,可以避免这种二义性
smartPhone.ring();
smartPhone.receiverRing();
smartPhone.sendMessage();
smartPhone.playVideo();
smartPhone.printAll();
return 0;
}
上述代码在编译的时候是通不过的,因为没有virtual进行修饰的话,这个时候会产生函数的二义性,也就是函数在执行的时候,并不是去动态加载的.
报错信息如下所示:
MultiInheritance.cpp: In member function ‘void SmartPhone::printAll()’:
MultiInheritance.cpp:108:40: error: ‘Size’ is an ambiguous base of ‘SmartPhone’
cout<<"height:"<<Size::getHeight()<<endl;
^
MultiInheritance.cpp:109:38: error: ‘Size’ is an ambiguous base of ‘SmartPhone’
cout<<"width:"<<Size::getWidth()<<endl;
^
MultiInheritance.cpp: In function ‘int main(int, char**)’:
MultiInheritance.cpp:124:14: error: request for member ‘setWidth’ is ambiguous
smartPhone.setWidth(1280);
^
MultiInheritance.cpp:34:10: note: candidates are: void Size::setWidth(int)
void setWidth(int width){
^
MultiInheritance.cpp:34:10: note: void Size::setWidth(int)
MultiInheritance.cpp:125:14: error: request for member ‘setHeight’ is ambiguous
smartPhone.setHeight(720);
^
MultiInheritance.cpp:38:9: note: candidates are: void Size::setHeight(int)
void setHeight(int height){
^
MultiInheritance.cpp:38:9: note: void Size::setHeight(int)
MultiInheritance.cpp:126:14: error: request for member ‘print’ is ambiguous
smartPhone.print();
^
MultiInheritance.cpp:48:10: note: candidates are: void Size::print()
void print(){
^
MultiInheritance.cpp:48:10: note: void Size::print()
下面上一个完整代码:
/*
* ===========================================================================
*
* Filename: MultiInheritance.cpp
* Description:
* 智能手机是继承了智能设备,现在的手机都能看电视,也继承了功能手机的打电话,发短信的功能
* Version: 1.0
* Created: 2017年06月20日 22时05分41秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace::std;
/* *
*
*创建一个顶层的Size类
* */
class Size{
public:
Size(){
cout<<"Size constructor"<<endl;
}
~Size(){
cout<<"size destructor"<<endl;
}
void setWidth(int width){
s_width = width;
}
void setHeight(int height){
s_height = height;
}
int getWidth() const{
return s_width;
}
int getHeight() const{
return s_height;
}
void print(){
cout<<"height:"<<s_height<<endl;
cout<<"width:"<<s_width<<endl;
}
private:
int s_height;
int s_width;
};
/* *
*继承顶层Size类
* */
class Tv:virtual public Size{
public:
Tv(){
cout<<"Tv constructor"<<endl;
}
~Tv(){
cout<<"Tv destructor"<<endl;
}
void playVideo(){
cout<<"play video"<<endl;
}
private:
};
/* *
* 继承底层Size类
* */
class Phone:virtual public Size{
public:
Phone(){
cout<<"phone constructor"<<endl;
}
~Phone(){
cout<<"phone destructor"<<endl;
}
void ring(){
cout<<"take ring"<<endl;
}
void receiverRing(){
cout<<"receive ring"<<endl;
}
void sendMessage(){
cout<<"sendMessage"<<endl;
}
private:
};
class SmartPhone:public Tv,public Phone{
public:
SmartPhone(){
cout<<"SmartPhone constructor"<<endl;
}
~SmartPhone(){
cout<<"SmartPhone destructor"<<endl;
}
void printAll(){
cout<<"height:"<<Size::getHeight()<<endl;
cout<<"width:"<<Size::getWidth()<<endl;
}
private:
};
int main(int argc ,char *argv[]){
SmartPhone smartPhone;
cout<<"Tv size:"<<sizeof(Tv)<<endl;
cout<<"Phone size"<<sizeof(Phone)<<endl;
//如果不去进行虚继承的话,这个时候会产生函数的二义性,也就是在编译的时候找不到具体执行的方法
smartPhone.setWidth(1280);
smartPhone.setHeight(720);
smartPhone.print();
//子类虚继承父类之后,可以避免这种二义性
smartPhone.ring();
smartPhone.receiverRing();
smartPhone.sendMessage();
smartPhone.playVideo();
smartPhone.printAll();
return 0;
}
执行结果如下所示:
Size constructor
Tv constructor
phone constructor
SmartPhone constructor
Tv size:16
Phone size16
height:720
width:1280
take ring
receive ring
sendMessage
play video
height:720
width:1280
SmartPhone destructor
phone destructor
Tv destructor
size destructor
但是进行虚拟继承过后,子类在继承父亲的变量的时候.会同时也会生成一个虚继承函数表,这个时候会使类所占用的内存资源增加.
有兴趣的可以试试打印一下.在不进行虚拟进程的时候,Tv和Phone的size是8,但是进行虚拟继承之后,增加了一个指针的空间,8个字节,所以虚继承后显示出来的是16个字节