虚函数

虚函数是实现动态绑定的函数。

虚函数必须是非静态的成员函数,虚函数经过派生后据可以实现运行过程中的多态。

什么函数可以是虚函数呢

1.一般成员函数

2.构造函数不能使虚函数

3.析构函数可以是虚函数

注意事项:如果使用了抽象基类,那么在抽象基类里,虚析构函数就变成了纯虚析构虚函数,而纯虚析构函数必须定义,否则编译器会报错,这时候纯虚析构函数的定义就和普通的析构函数一样定义就行。

虚函数的声明

virtual 函数类型 函数名(形参表);

虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候

派生类中可以对基类中的成员函数进行覆盖

虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。

virtual关键字

派生类可以不显式地用virtual声明虚函数,这时候系统就会用以下规则来判断派生类的一个成员函数是不是虚函数

1.该函数是否与基类的虚函数有相同的名称,参数个数以及对应参数类型

2.该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针,引用行的返回值

(从函数的名称,参数,返回值来确定,是否都有const

一般习惯于在派生类的函数也是用virtual关键字,以增加程序的可读性

用法一:

#include<iostream>
using namespace std;
class A
{
public:virtual void display(){cout<<"A"<<endl;}
};
class B:public A
{
public:void display(){cout<<"B"<<endl;}
};
void Display(A *p)
{p->display();
}
int main()
{B b;Display(&b);
}

这段代码打印结果是 B,但是我们把A类里面的virtual去掉后就输出A。当没有virtual的时候,在调用display的时候,编译器会自然会调用A类的方法,如果有virtual后,display就变成了虚方法,编译器在编译的时候会去找这个dispaly具体是谁的方法。就实现了多态的效果。virtual相当于告诉编译器,先不要急着确定函数的对象,等到实际运行时候在确定。

如何实现多态呢?

每一个带有virtual的类的对象,都比它本身不带virtual的类的对象占的空间要大,因为带virtual的类的最头上会加上一个隐藏的指针,它指向一张表——虚函数表,这张表上是这个类里所有的有函数的地址.比如在执行p->display()函数的时候,访问指针p指向的对象里面的那个虚函数表,然后调用那个display函数。

#include<iostream>
using namespace std;
class A
{int i;
public :A():i(10){}virtual void f(){cout<<"A::f()"<<i<<endl;}
};
class B:public A
{int j;
public:B():j(20){}virtual void f(){cout<<"B::f()"<<j<<endl;}
};
int main()
{A a;B b;A *p=&b;p->f();a=b;p=&a;p->f();return 0;
}

输出结果


我们用A类型的指针指向对象b,然后执行f函数,会执行b的,因为虚函数表没有改变,所以当我们把b的值赋给a的时候,由于虚函数表没有改变,我们在执行
p->f()的时候,执行的是A类的f函数

int main()
{A a;B b;A *p=&a;int* r=(int*)&a;int* t=(int*)&b;*r = *t;p->f();return 0;

虚函数表的地址位于类的起始地址紧接着的地址
如果我们把主函数改成这样

输出结果会变成这样

也就是说赋值的过程,不会改变虚函数表的地址
但是如果是 A *a,  B* b,然后 b=a,这样的话,执行 b->f()就会执行A::f了

void Display(A *p)
{p->display();
}

这个函数原理跟上面那个指针是一样的

用法二:

#include<iostream>
using namespace std;
class Person
{
public:Person(){cout<<"Person构造"<<endl;}~Person(){cout<<"Person析构"<<endl;}
};
class Teacher : virtual public Person
{
public:Teacher(){cout<<"Teacher构造"<<endl;}~Teacher(){out<<"Teacher析构"<<endl;}
};
class Student : virtual public Person
{
public:Student(){cout<<"Student构造"<<endl;}~Student(){cout<<"Student析构"<<endl;}
};
class TS : public Teacher,  public Student
{
public:TS(){cout<<"TS构造"<<endl;}~TS(){cout<<"TS析构"<<endl;}
};
int main()
{TS ts;return 0;
}

输出结果

Person构造
Teacher构造
Student构造
TS构造
TS析构
Student析构
Teacher析构
Person析构

当Teacher类和Student类没有虚继承Person类的时候,也就是把virtual去掉时候终端输出的结果为:

Person构造
Teacher构造
Person构造
Student构造
TS构造
TS析构
Student析构
Person析构
Teacher析构
Person析构

大家可以很清楚的看到这个结果明显不是我们所期望的。我们在构造TS的时候需要先构造他的基类,也就是Teacher类和Student类。而Teacher类和Student类由都继承于Person类。这样就导致了构造TS的时候实例化了两个Person类。同样的道理,析构的时候也是析构了两次Person类,这是非常危险的,也就引发出了virtual的第三种用法,虚析构。


#include<iostream>
using namespace std;
class Person
{
public:Person(){name = new char[16];cout<<"Person构造"<<endl;}virtual  ~Person(){delete []name;cout<<"Person析构"<<endl;}
private:char *name;
};
class Teacher :virtual public Person
{
public:Teacher(){cout<<"Teacher构造"<<endl;}~Teacher(){cout<<"Teacher析构"<<endl;}
};
class Student :virtual public Person
{
public:Student(){cout<<"Student构造"<<endl;}~Student(){cout<<"Student析构"<<endl;}
};
class TS : public Teacher,public Student
{
public:TS(){cout<<"TS构造"<<endl;}~TS(){cout<<"TS析构"<<endl;}
};
int main()
{Person *p = new TS();delete p;return 0;
}

这段代码的运行结果为:

Person构造
Teacher构造
Student构造
TS构造
TS析构
Student析构
Teacher析构
Person析构

但是当我们把Person类中析构前面的virtual去掉之后的运行结果为:

Person构造
Teacher构造
Student构造
TS构造
Person析构
程序崩溃

很明显这个结果不是我们想要的程序,崩溃造成的后果是不可预计的,所以我们一定要注意在基类的析构函数前面加上virtual,使其变成虚析构在C++程序中使用虚函数,虚继承和虚析构是很好的习惯 可以避免许多的问题。

纯虚函数

纯虚函数式一个在基类中声明的虚函数,它在该类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本

例如我们定义了一个图形的类,把他作为基类,它的派生类可以是三角形,正方形等等,图形可以求面积,求面积首先要直到图形式什么图形,所以在基类中无法确定求面积的成员函数的具体算法,但是在它的派生类里就可以实现了。

语法

virtual 函数名(参数)= 0;

含有纯虚函数的类叫做抽象类,抽象类没有对象,它虽然不能实例化,但是它可以规定对外接口的统一性

简单来讲就是,我们在实现多态的时候,多态函数在找方法的时候,不会去抽象类里找方法。

C++ 中虚函数的用法相关推荐

  1. 【C++基础之十一】虚函数的用法

    虚函数的作用和意义,就不进行说明了,这里主要讨论下虚函数的用法. 1.典型的虚函数用法 可以看到,只有标识为virtual的函数才会产生多态的效果,而且是编译多态.它只能借助指针或者引用来达到多态的效 ...

  2. C++中虚函数、虚指针和虚表详解

    关于虚函数的背景知识 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 存在虚函数的类都有一个一维的虚函数表叫做虚表.每一个类的对象都有一个指向虚表开始的虚指针.虚表是和类对应的 ...

  3. 浅述numpy中argsort()函数的用法

    浅述python中argsort()函数的用法 由于想使用python用训练好的caffemodel来对很多图片进行批处理分类,学习过程中,碰到了argsort函数,因此去查了相关文献,也自己在pyt ...

  4. C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

    C++ 在继承中虚函数.纯虚函数.普通函数,三者的区别 1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的 ...

  5. C++中substr函数的用法

    C++中substr函数的用法 #include<string> #include<iostream> using namespace std; main() { string ...

  6. numpy中where函数的用法

    numpy中where函数的用法 numpy.where(condition,x,y)--若满足condition,输出x,否则输出y. 举例: 例1:一维数组 >>> a = np ...

  7. delphi中move函数的用法

    delphi中move函数的用法 -------------------------------------------------------------------------------- 20 ...

  8. SQL中object_id函数的用法

    SQL中object_id函数的用法 收藏  int object_id('objectname'); 此方法返回数据库对象标识号. 其中,参数objectname 表示要使用的对象,其数据类型为nc ...

  9. python remove函数_python中remove函数的用法是什么?

    python中remove函数的用法是什么? python中remove函数的用法: 描述 remove() 函数用于移除列表中某个值的第一个匹配项. 语法 remove()方法语法: list.re ...

最新文章

  1. thinkphp3.1 mysql5.6_Thinkphp3.1 跨库连接数据库。
  2. python选择排序 时间复杂度
  3. 怎样用springboot开发cs_SpringBoot如何进行学习!
  4. Spring Boot + JPA + Oracle 自增长字段实现示例
  5. 史上最强春节红包战:互联网竞争缩影下的百亿争斗
  6. Python打包文件夹(zip/tar/tar.gz)
  7. linux系统怎么改输入法,linux系统输入法怎么切换
  8. 泰迪杯数据分析比赛2018年B题解答
  9. JAVA获取word书签内容_Java 操作Word书签(一):添加、删除、读取书签
  10. 第三阶段应用层——1.9 数码相册—在LCD上显示BMP图片
  11. 《JavaScript百炼成仙》 全书知识点整理
  12. PandoraBox Openwrt 上面设置DNS
  13. 域名访问限制不严格漏洞 修复
  14. SPARC架构下的反汇编(一)——SPARC简介
  15. 阿里云对象存储OSS文件上传
  16. 重装系统服务器不识别u盘,电脑使用u盘重装系统无法识别怎么办?
  17. 哪些数字适合作为域名主体?
  18. 华为rh2288v2服务器系统,华为RH2288H V2服务器外部简介_华为 FusionServer RH2288 V2_服务器评测与技术-中关村在线...
  19. Windows绘制点与线
  20. OpenCV4二维码识别测试

热门文章

  1. launch是html文件么,launchctl
  2. 计算机报名上传照片要求,浙江省2016年9月全国计算机考试报名须知:上传照片...
  3. pack_padded_sequence和pad_packed_sequence详解
  4. 74HC595模块原理
  5. 工作1年多 感慨外包和超小型自研公司
  6. 【Verilog基础】Verilog实现加扰器(Scrambler )与解扰器(Descrambler)
  7. Windows 8 Beta 建行网银问题解决方案
  8. 【二】MATLAB矩阵处理
  9. Visual Studio 控制台程序世界杯足球C语言版(附源码)
  10. 【Unity】unity 日历制作