c++虚函数

C++之虚函数
静态联编和动态联编

在说虚函数之前,先讲下静态和动态联编。
联编:以函数重载为例,C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数
静态联编:编译器在编译过程中进行的联编,通过对象来调用函数,一定是静态绑定。
动态联编:编译器在运行中完成程序的选择,三个条件:1.必须通过指针来调用2.指针向上转型3.调用的虚函数只要符合这三个条件,就会进行动态联编,也就是虚机制。通过指针或者引用来调用虚函数的话,满足这三个条件就会发生动态绑定。
 

//父类A,子类B,虚函数vfunc1()
B b;
A a=(A)b;
a.vfunc1();//通过对象来调用,一定是静态绑定

A*pa=new B;//子类指针转为父类指针(向上转型),父类指针指向子类的对象
pa->vfunc1();//这就是动态绑定了!满足三个条件。

虚函数

首先要明白什么是虚函数,函数名称前面加上virtual这个函数就称为虚函数,常用在继承关系里,子类想要修改父类的某个函数,就将父类对应的函数声明为虚的
虚函数的作用主要是为了实现多态的机制,使得基类的指针或者引用可以指向基类或者子类的对象,从而可以调用基类或者子类的虚函数,从而达到不同的对象调用同一个接口产生不同的响应。所谓多态,就是同一个操作作用与不同的对象会产生不同的响应,精髓就是“一个接口,多种方法”,函数重载就是一种多态,一个函数名对应着不同的方法。这是一种静态多态,还有一种动态多态,就是虚函数。
非虚函数:普通函数,你不希望子类重新定义(重写)它
虚函数:你希望子类重新定义它,并且它已经有默认定义
纯虚函数:你希望子类一定重新定义它,你对他没有默认定义。
如下程序:

class Shape{
public:
   virtual void draw()const=0;//纯虚函数
   virtual void error(const std::string&msg);//虚函数
   int objecttID()const;//非虚函数
   ...
}
class Rectangle:public Shape{...};

虚函数的访问

静态联编:虚函数通过对象名来调用。此时调用哪个类的函数取决于定义对象名的类型,对象类型是基类,就调用基类的函数,对象类型是子类就调用子类的函数。

动态联编:编译器根据指针或者引用所指向的对象的类型来决定调用哪一个函数,和指针,引用本身的类型无关。而使用指针或者引用访问非虚函数时,编译器根据指针本身的类型来决定要调用哪个函数,而不是根据指针指向的对象的类型。

构造函数不能是虚函数

构造函数不能是虚函数
1.如果构造函数是虚函数,那就要存放在虚函数表中,而对象的隐藏成员保存的指针指向虚函数表,但是此时还没有产生对象,没法通过指针找到这个虚函数表。
2.首先在继承关系中,构造函数执行顺序是:先调用基类的构造函数,再调用子类的构造函数。如果构造函数声明为虚函数,将出现只调用自己本身的构造函数这种情况,因为声明为虚函数,就是为了通过基类的指针来调用基类或者子类的成员函数,只调用一个最想要的虚函数,而继承调用两个,这矛盾了


析构函数需要是虚函数

析构函数应当是虚函数,除非这个类不用做基类。其实一般而言即使不用作基类,也可以给类定义一个虚析构函数。
原因:
假设两个类Employee是基类,Singer是子类。

Employee *pe=new Singer;//基类指针指向派生类对象
...
delete pe;

1.防止内存泄漏:如果析构函数未声明为虚函数,则delete只会调用~Employee(),这会释放子类对象中的基类部分指向的内存,**但是不会释放子类中新的成员指向的内存,**即不会调用子类的析构函数,这样子类申请的部分内存得不到释放从而产生内存泄漏。如果析构函数声明为虚函数,那编译器就会实行动态绑定,如果删除基类指针,就会调用该指针指向的派生类析构函数,从而派生类析构函数又自动调用基类的析构函数,从而整个派生类的对象都被释放。
 

vptr和vtbl

编译器处理虚函数的方式:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针,每个对象都只有一个这种指针,如下图所示,这种数组就是虚函数表(vtbl),无论虚函数有多少个,虚函数增加减少只是虚函数表大小改变。vtbl中存储的是为类对象进行声明的虚函数的地址。另外,如果派生类提供了虚函数的新定义,该虚函数表将保存新的函数的地址,如果派生类没有重新定义虚函数,vtbl将保存函数原来版本的地址。每个类都有一个虚函数表。

关于this 
虚函数的另外一种作用。
通过子类的对象来调用父类的方法,这里其实会进行隐式向上类型转换,使得基类指针或者引用可以指向基类或者派生类对象。

 补充:哪些函数不能是虚函数

1、普通函数(非成员函数):普通函数只能重载,不能被重写,声明为虚函数没什么意思。、、
2、静态成员函数:对每一个类来说只有一份代码,所有的对象共享这一份代码,它不属于某个具体对象。没有动态绑定的必要性。
3、构造函数:上述。
4、友元函数:不是成员函数,没有继承,
5、内联函数:声明为虚函数也没有意义,设置虚函数是为了多态,内联是为了直接运行代码,减  少函数调用的代价,即使虚函数是内联的,编译器也不会把这样的函数内联展开,而是当作普通函数处理。
6、赋值操作符重载函数:这个比较简单,重载要求形参,形参中的类型是什么就会用哪个类,即使声明为虚函数也没有意义。


管理虚方法:override和final

override:指出要重写一个虚函数:将其放在参数列表后面
final:禁止派生类重写特定的虚函数,也将其放在参数列表后面。
从下面这个程序开始

class Action {
public:
	Action(int i=0):a(i){}
	int val()const { return a; }
	virtual void f(char ch)const {
		cout << val() << ch << "\n";
	}
private:
	int a;
};
class Bingo :public Action {
public:
	Bingo(int i=0):Action(i){}
	virtual void f( char *ch)const  {//如果不小心把想要重写的函数参数和父类参数不匹配了,那子类对象就会隐藏父类的f(char ch)
		cout << val() << ch << "!\n";
	}
	virtual void f( char *ch)const override {//报错,但是如果加上override指出我要在子类重写这个虚函数,声明和父类方法不匹配的话就会报错。
		cout << val() << ch << "!\n";
	}
};
int main() {
	Bingo b(10);
	//const char *a = "123";
	b.f("1");//错误,
	delete a;
	return 0;
}

        

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值