本文主要从表现形式上总结虚函数,蛮浅显的,想要了解本质的话请看来自up主 bitshelf分享的视频
为什么要引入虚虚函数
前面我们在c++ 多重继承提到基类的指针可以指向子类
我们看如下代码。
#include<iostream>
using namespace std;
class Father{
public:
void print()
{
printf("Father's function!");
}
};
class Son:public Father
{
public:
void print()
{
printf("Son's function!");
}
};
int main()
{
Father f1;
Son s1;
Father* f = &s1;
f->print();//执行从father里继承来的函数
cout<<endl<<endl;
//强制类型转换,把 f 从Father* 转换成 Son* 型
((Son*)f)->print();//执行从son写的函数来的函数
cout<<endl<<endl;
return 0;
}
仔细观察,f是基类型的指针,但是是指向的却是自己子类,所以只能访问子类从基类里继承过来的函数‘print()’(这也体现了子类只是隐藏了父类的函数,没有覆盖),但是我们进行强制类型转换后却是执行子类的‘print()’,这是通过指针实现一种多肽,但是这样无法动态的实现(即为指向不同对象的时候,指向不同的操作),因为我们还需要强制类型转换,为此,我们引入虚函数。
背后的原理
虚函数,从表面形式上看,对比如上的方法,我们就是不用强制类型转换了,但是这背后工作原理,蛮复杂的,大家可以参考这个视频。
强调一点的是,一般的成员函数是不会占对象空间的,但是虚函数不一样,因为有虚函数的类存在隐藏的成员虚函数表和虚函数指针表,所以会占用存储他们地址的空间
虚函数的意义
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定,对于其他的普通的函数就是静态连接啦
虚函数的定义和使用
定义
virtual <类型><函数名>(<参数列表>);
注意事项
- 父类有虚函数,子类在重写虚函数的时候可以不用写 关键字virtual ,应为虚函数的特性可以继承。
- 如果子类无需实现父类虚函数的功能,我们应该为写一个同名的空的虚函数,添加虚函数路径。
- 我们在类体系中访问虚函数的时候,我们应该用基类的指针(引用)去访问。如果和一般访问的话,就无法体现虚函数的特性。
- 虚函数一定是该类的成员函数,不能是该类的友元函数。当然他可以是其他类的友元函数。
- 严格区分函数的重载和虚函数的概念,虚函数的函数名,参数(包括顺序)和返回值是严格一致的,参数不一致的话就会丢失虚函数的特性,编译器就会当他是函数的重载。
- 如果一个派生类的没有在定义的基类虚函数,当我们用指向基类的指针或者引用去调用时,运行的一定是距离被指向的基类的最近基类的的虚函数。【就近原则】
- 强调一下构造函数不可以是虚函数,但是析构函数是可以的。而且在写构造函数的时候我们不能调用子类的虚函数,只能调用本类的虚函数。(在此时子类的构造函数还没有执行呢)。
虚函数的使用
虚化后的亚瑟。。。。。 _
虚化后的函数,我们就可以用父类的指针(引用)去调用虚函数,详细可以见后面的实例。
纯虚函数和抽象类
纯虚函数
我们可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
定义的时候无需写函数体要加一个’=0‘
virtual <类型><函数名>(<参数列表>)=0;
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
纯虚函数没有函数体,构造函数和析构函数是不能调用的,但是其他的成员函数是可以的。
抽象类
所谓抽象类就是有虚函数的类
虚函数的作用
抽象类主要是为类体系的继承层次提供一个公共的基类,反应这个类体系的公有特征,其他类可以从这继承和实现接口。
注意
- 抽象类只能作为来基类派生新类 ,抽象类不能有自己的对象。
- 抽象类不能作为参数类型,或者是返回值类型,强制转换类型。
- 可以声明抽象类的指针和引用,用来指向他的派生类。
- 抽象类的派生类必须有提供纯虚函数的代码(空的也行)。
- 如果子类没有提供纯虚函数的代码,那么该子类也是抽象类,也无法有自己的对象。
- 成员函数可以调用纯虚函数,但是我们很少这样做,构造函数和析构函数是不能调用的,因为没有为纯虚函数定义代码。
虚析构函数是为了释放更加彻底
虚析构函数的作用:
为了避免内存泄漏,而且是当子类中会有指针成员变量时才会使用到。即虚析构函数使得在删除指向子类对象的基类指针时,可以调用子类的析构函数来实现释放子类中堆内存的目的,从而防止内存泄漏。
补充
- 如果基类的析构函数不加virtual关键字,那么就是普通析构函数
当基类中的析构函数没有声明为虚析构函数时,派生类开始从基类继承,基类的指针指向派生类的对象时,delete基类的指针时,只会调用基类的析构函数,不会调用派生类的析构函数。 - 如果基类的析构函数加virtual关键字,那么就是虚析构函数
当基类中的析构函数声明为虚析构函数时,派生类开始从基类继承,基类的指针指向派生类的对象时,delete基类的指针时,先调用派生类的析构函数,再调用基类中的析构函数。
注意
我们在基类把析构函数声明成虚的析构函数,那么派生都是虚函数啦。
结语
虚函数的讲解就到这里了,明天总结一下c++opp学习笔记最后一章————模版。
最后放一个写的实例把
#include <iostream>
#include<iomanip>
using namespace std;
const float PI=3.14159;
class CShape //抽象类
{
public:
virtual double Area()=0; //求面积
};
class CRectangle:public CShape //派生类:矩形类
{
double w,h; //宽和高
public:
virtual double Area(){return w * h;}
friend istream& operator >>(istream& in,CRectangle& C)
{
cin>>C.w>>C.h;
return in;
}
};
class CCircle:public CShape //派生类:圆类
{
double r;
public:
//半径
virtual double Area(){return PI * r * r ;}
friend istream& operator >>(istream& in,CCircle& C)
{
cin>>C.r;
return in;
}
};
class CTriangle:public CShape //派生类:三角形类
{
double a,b; //高 底
public:
virtual double Area(){
return a*b/2;
}
friend istream& operator >>(istream& in,CTriangle& C)
{
cin>>C.a>>C.b;
return in;
}
};
class Square:public CShape //派生类:正方形类
{
double w; //宽
public:
virtual double Area(){return w*w;}
friend istream& operator >>(istream& in,Square& C)
{
cin>>C.w;
return in;
}
};
class Trapezoid:public CShape //派生类:梯形类
{
double d,c,h;
public:
virtual double Area(){return (d+c)*h/2.0;}
friend istream& operator >>(istream& in,Trapezoid& C)
{
cin>>C.d>>C.c>>C.h;
return in;
}
};
int main()
{
//赋值
CShape *p[5];
cout<<"请输入矩形,圆形 三角形,正方形,梯形 的各项参数\n " ;
CRectangle *pr=new CRectangle;
cin>>*pr;
p[0]=pr;
CCircle *pc=new CCircle;
cin>>*pc;
p[1]=pc;
CTriangle *pt=new CTriangle;
cin>>*pt;
p[2]=pt;
Square *ps=new Square;
cin>>*ps;
p[3]=ps;
Trapezoid *ptr= new Trapezoid;
cin>>*ptr;
p[4]=ptr;
cout<<"矩形,圆形 三角形,正方形,梯形分别为面积为\n " ;
//调用虚函数
for(int i=0;i<5;i++)
{
cout<<p[i]->Area()<<" \n";
}
return 0;
}