virtual关键字

virtual关键字讲解

1、virtual关键字使用的位置
1.1 类成员函数
1.2 虚继承

2、概念
2.1 重载与重写
override(重写,覆盖)
(1)方法名、参数、返回值相同。
(2)子类方法不能缩小父类方法的访问权限。
(3)子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
(4)存在于父类和子类之间。
(5)方法被定义为final不能被重写。
(6)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

overload(重载,过载)
(1)参数类型、个数、顺序至少有一个不相同。
(2)不能重载只有返回值不同的方法名。
(3)针对于一个类而言。
(4)不能通过访问权限、返回类型、抛出的异常进行重载;
(5)方法的异常类型和数目不会对重载造成影响;

override应用中,最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。 除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。
override是在不同类之间的行为,overload是在同一个类中的行为。

https://www.cnblogs.com/yyxt/p/4243587.html

2.2 常见问题及解答
1.为什么构造函数不能是虚函数?
从C++之父Bjarne的回答我们应该知道C++为什么不支持构造函数是虚函数了,简单讲就是没有意义。
虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用。

网络上还有一个很普遍的解释是这样的:
虚函数相应一个指向vtable虚函数表的指针,但是这个指向vtable的指针事实上是存储在对象的内存空间的。
假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

本人对这个观点并不认同,这主要是因为用什么方式实现虚函数是编译器的事情,使用Vtable只是大多数编译器采用的一种手段,
表编译器实现不了虚构造函数,编译器之所以不支持虚构造函数主要原因就是没有必要,所以正好这种实现方式也不支持,巧合而已。

2.为什么析构函数不能重载?
虚析构函数没哥哥对象只有一个,且没有参数,无法重载。

3.考虑下,拷贝构造、赋值以及构造函数是否可以使用virtual关键字?
拷贝构造函数和拷贝赋值运算符可以是虚函数吗?
**构造函数(包括拷贝构造)没有虚函数一说。
子类构造函数可显示调用父类某一构造函数否则就默认会先调用父类无参构造函数,默认父类没有无参构造函数,则子类构造函数必须显示调用父类有参构造函数。
拷贝构造函数也需要显示调用父类拷贝构造函数,否则默认先调用父类无参构造函数。
析构函数可以是虚函数,以使得delete指向子类的父类指针时能够析够子类。
拷贝赋值函数可以为虚。
*************
构造函数,赋值操作符不应该是虚函数
在有继承层次类的时候,我们已经知道析构函数应该是虚函数,但很多人对构造函数和赋值操作符应不应该是虚函数并不清楚。

首先,构造函数不能是虚函数,因为构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。

对于赋值操作符,虽然可以在基类中将成员函数operator=定义成虚函数,但这样做并不会影响派生类中赋值操作符的使用。因为每个类有自己的赋值操作符。
每个类的赋值操作符都有一个和类本身类型相同的形参,该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。因此子类的赋值操作符和基类的赋值操作符并不是同一个。
但是,在这个子类中仍然有基类的那个操作符,但不是赋值操作符。

将赋值操作符设为虚函数容易让人混淆,因为虚函数必须在基类和派生类中具有相同的形参,基类赋值操作符有一个形参是自身类类型的引用,
如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的operator=。但是,对于派生类而言,这个操作符与赋值操作符是不同的。

因此,将赋值操作符设为虚函数很容易令人混淆,并且没有什么用处。

4. C++虚函数原理
类中的成员函数分为静态成员函数和非静态成员函数,而非静态成员函数又分为普通函数和虚函数。

Q:为什么使用虚函数。
A: 使用虚函数,可以获得良好的可扩展性。
在一个设计比较好的面向对象程序中,大多数函数都是与基类的接口进行通信。
因为使用基类接口时,调用基类接口的程序不需要改变就可以适应新类。
如果用户想添加新能够,他就可以从基类继承并添加相关的新功能。

Q:简述C++虚函数作用及底层实现原理
A: 要点是要答出虚函数和虚函数表指针的作用。
虚函数是用来实现动态绑定的。
C++中虚函数使用虚函数表和虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引本身以及父类的虚函数的地址,
假如子类重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的函数的地址(子类中可以不是虚函数,但是必须同名);
虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处),它指向对象所在类的虚函数表的地址;
在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。

虚函数表是每个(有虚函数的)类对应一个。虚函数表指针是每个对象一个。
虚函数表里只能存放虚函数,不能存放普通函数。
如果一个函数不是虚函数,那么对它的调用(即该函数的地址)在编译阶段就会确定。调用虚函数的话(它的地址)要运行时才能确定。
虚函数的函数入口是动态绑定的。在运行时,程序根据基类指针指向的实际对象, 来调用该对象对应版本的函数。
(用该对象的虚函数表指针找到其虚函数表,进而调用不同的函数。)(只有是虚函数的情况下才会这样做(用虚函数表指针取查虚函数表)。非虚函数只直接就调用自己的。)

 

1.为什么使用虚基类函数?(什么情况下要用虚析构函数?)
在存在类继承并且析构函数汇中需要析构某些资源时,析构函数需要是虚函数。否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄漏。
Base *ptr = new Derived();

2.一个对象访问普通成员函数和虚函数那个更快?
访问普通成员函数更快,因为普通成员函数的地址在编译阶段就已确定,因此在访问时直接调用对应地址的函数;
而虚函数在调用时,需要首先在虚函数表中寻找虚函数所在地址,因此相比普通成员函数速度更慢一些。

3.内联函数、构造函数、静态成员函数都不可以是虚函数。

4.构造函数中可以调用虚函数吗?
可以,但是没有意义,起不到动态绑定的效果。父类构造函数中调用的仍然是父类版本的构造函数,子类中调用的仍然是子类版本的构造函数。

5. 简述C++中虚继承的作用及底层实现原理?
虚继承用于解决多继承条件下的菱形继承问题,底层实现原理与编译器相关,一般通过虚基类指针实现,即各对象中只保存一份父类的对象,多继承时通过虚基类指针引用该公共对象,从而避免菱形继承中的二义性问题。

C++虚函数实现多态原理
1. 前言
C++中虚函数的作用主要是实现了多态的机制。
关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。
比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

2.虚函数表
对C++了解的人都应该知道虚函数(Virtual Funcition)是通过一张虚函数表(Virtual Table)来实现的。简称V-Table。
在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其能真实反应实际的函数。
这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,
这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

***编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。
***这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

3. 一般继承(无虚函数覆盖)
4. 一般继承(有虚函数覆盖)
5. 多重继承(无虚函数覆盖)
注意:子类并没有覆盖父类的函数
对于子类实例中的虚函数表,每个父类都有自己的虚表;子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明的顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
6. 多重继承(有虚函数覆盖)
对于子类实例中的虚函数表,父类虚函数表中重写函数位置被替换成了子类的函数指针。这样,我们就可以任一静态类型来指向子类,并调用了子类的相应函数。
其他子类成员放在父类虚表后面。
7. 安全性
1).通过父类型的指针访问子类自己的虚函数。
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。
虽然可以看到在子类的父类虚表中有继承的虚函数,但是我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive();
b1->f1();//编译出错
任何妄图使用父类指针想调用子类的未覆盖父类的成员函数的行为都会被编译视为非法,所以,这样的程序根本无法通过编译。
但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
2).访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来
访问这些non-public的虚函数,这是很容易做到的。


内存检查工具:
valgrind 一个强大开源的程序检测工具
https://blog.csdn.net/ttomqq/article/details/81937561

转载于:https://www.cnblogs.com/gzjgl/p/11115026.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值