C++知识点50——虚函数与纯虚函数(上)

一、虚函数

1.如果一个基类希望基类中的某些成员函数在子类中实现子类的自定义版本,就可以将该成员函数定义为virtual

2.当使用基类的指针或引用调用一个虚函数时,将会发生动态绑定(在运行时根据指针或引用的动态类型来决定调用对应动态类型中的虚函数),动态绑定发生时,编译器无法在编译时确定要调用哪个版本的虚函数。如果使用对象来调用成员函数,那么在编译期就能确定要调用的成员函数,因为此时类的静态类型和动态类型是一致的

3.智能指针也支持虚机制

所以,虚函数的实现基础:一是要有类的继承,二是基类指针或引用的动态类型和静态类型有可能不一致

4.如果基类中的成员函数是virtual的,子类经常但不总是会重写该成员函数,如果子类不重写虚函数,子类会直接继承该虚函数,在调用该虚函数时,也依然会发生动态绑定,只不过调用结果类似普通成员函数

5.如果基类中的成员函数是virtual,那么子类继承该基类后,对应的成员函数自动都是virtual的,可以使用virtual重新声明,也可以不使用

示例

class base2
{
public:
	base2(){cout<<__func__<<endl;}
	~base2(){cout<<__func__<<endl;}
	virtual void vfunc1(){cout<<__func__<<"in base2"<<endl;}
	virtual void vfunc2(){cout<<__func__<<"in base2"<<endl;}
	virtual void vfunc3(){cout<<__func__<<"in base2"<<endl;}
};

class derive2:public base2
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	virtual void vfunc1(){cout<<__func__<<"in derive2"<<endl;}
	void vfunc2(){cout<<__func__<<"in derive2"<<endl;}
};

int main(int argc, char const *argv[])
{
	base2 *bp=new derive2();
	shared_ptr<base2> sbp=make_shared<derive2>(derive2());
	bp->vfunc1();
	bp->vfunc2();
	bp->vfunc3();
	sbp->vfunc1();
	sbp->vfunc2();
	sbp->vfunc3();
	return 0;
}

上述代码通过通过普通指针和智能指针分别指向子类的对象,并通过虚机制分别调用不同版本的虚函数

 

6.如果子类要重写基类中的虚函数,那么,重写的虚函数的形参列表必须和基类保持一致,返回值一般情况下也要一致。除非该虚函数返回的是该类本身的指针或引用。如果定义了一个和基类虚函数名相同但是形参列表不同的函数,那么这两个函数没有任何关系,子类也没有重写基类中的虚函数。在C++11中,可以在子类中的虚成员函数的形参列表后使用override关键字来表明该成员函数是子类的自定义版本,此时,子类必须重写父类的虚函数,此时如果形参列表不同,编译器将报错

示例

class base2
{
public:
	base2(){cout<<__func__<<endl;}
	~base2(){cout<<__func__<<endl;}
	virtual void vfunc1(int a, double b){cout<<__func__<<"in base2"<<endl;}
	virtual void vfunc2(int a, double b){cout<<__func__<<"in base2"<<endl;}
};

class derive2:public base2
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	virtual void vfunc1(int a, double b){cout<<__func__<<"in derive2"<<endl;}
	void vfunc2(int a) {cout<<__func__<<"in derive2"<<endl;}
};

int main(int argc, char const *argv[])
{
	shared_ptr<base2> sbp=make_shared<derive2>(derive2());
	sbp->vfunc1(1, 2.3);
	sbp->vfunc2(1, 2.3);
	return 0;
}

因为没有加override,所以基类和子类中的虚函数即使同名,参数列表也可以不同,只不过子类没有重写父类的虚函数而已,两个类中的vfunc2没有任何关系

但是,如果给子类的vfunc2函数添加关键字override,则会报错,因为此时此类必须重写父类的虚函数,但是此时形参列表不同

void vfunc2(int a) override {cout<<__func__<<"in derive2"<<endl;}

 

7.可以将函数也声明为final,和class类似,如果一个函数是final的,那么,该函数不能在子类中重写

8.虚函数也可以由默认实参,只不过默认实参的是在基类中确定的(因为默认实参是在编译期确定的,是静态绑定,而虚函数是动态绑定,所以默认实参是在基类中确定的)

示例

class base2
{
public:
	base2(){cout<<__func__<<endl;}
	~base2(){cout<<__func__<<endl;}
	virtual void vfunc1(int a, double b=3.3){cout<<__func__<<"in base2"<<endl;cout<<b<<endl;}
	virtual void vfunc2(int a, double b) {cout<<__func__<<"in base2"<<endl;}
};

class derive2:public base2
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	virtual void vfunc1(int a, double b=4.4){cout<<__func__<<"in derive2"<<endl;cout<<b<<endl;}
	void vfunc2(int a, double b) {cout<<__func__<<"in derive2"<<endl;}
};

int main(int argc, char const *argv[])
{
	shared_ptr<base2> sbp=make_shared<derive2>(derive2());
	sbp->vfunc1(1, 2.3);
	sbp->vfunc1(1);
	sbp->vfunc2(1, 2.3);
	return 0;
}

可见,即使子类中虚函数的默认实参是4.4,打印出来的默认实参值依旧是基类中的默认实参值

当虚函数使用默认实参,即使基类和子类的默认实参保持一致,也无法保证基类的默认实参不变,一旦基类的默认实参发生变化,所有的子类也要跟着修改默认实参,比较麻烦,所以,当基类中的虚函数使用默认实参时,子类中的虚函数不要重新指定该默认实参

如果将基类中的vfunc2加上final关键字,那么子类将不能对final进行重写

virtual void vfunc2(int a, double b) final {cout<<__func__<<"in base2"<<endl;}

 

9.即使一个函数是虚函数,并且子类也对其进行了重写,也可以使用作用域运算符回避虚机制。

示例

class base2
{
public:
	base2(){cout<<__func__<<endl;}
	~base2(){cout<<__func__<<endl;}
	virtual void vfunc1(){cout<<__func__<<"in base2"<<endl;}
	virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};

class derive2:public base2
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	virtual void vfunc1(){cout<<__func__<<"in derive2"<<endl;}
	void vfunc2() {cout<<__func__<<"in derive2"<<endl;}
};

int main(int argc, char const *argv[])
{
	shared_ptr<base2> sbp=make_shared<derive2>(derive2());
	sbp->base2::vfunc1();
	sbp->vfunc1();
	sbp->vfunc2();
	return 0;
}

如果一个子类的虚函数需要调用基类中的对应的函数,此时如果不使用作用域运算符回避虚机制,将导致无限递归

class base2
{
public:
	base2(){cout<<__func__<<endl;}
	~base2(){cout<<__func__<<endl;}
	virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};

class derive2:public base2
{
public:
	derive2(){cout<<__func__<<endl;}
	~derive2(){cout<<__func__<<endl;}
	void vfunc2() {
		cout<<__func__<<"in derive2"<<endl;
		shared_ptr<base2> sbp=make_shared<derive2>(derive2());
		sbp->vfunc2();
	}
};

int main(int argc, char const *argv[])
{
	shared_ptr<base2> sbp=make_shared<derive2>(derive2());
	sbp->vfunc2();
	return 0;
}

第17行因为虚机制会不停的调用子类中的vfunc2,导致无限递归,修改方法就是将动态调用改为静态调用

 

参考

《C++ Primer》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值