C++基础之关键字——virtual原理以及使用

virtual修饰

virtual可以修饰普通成员函数和析构函数;

不可以修饰构造函数,友元函数,static静态函数;

virtual具有继承性:父类中定义为virtual的函数在子类中重写的函数也自动成为虚函数。

如果一个基类中的函数是虚函数,其派生类不声明该函数为虚函数,那么该函数在派生类中仍然是一个虚函数。

原理

虚函数可以实现类的多态性,是在运行期进行函数调用,动态联编。

实现原理:虚函数表和虚表指针。

虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表,再找到对应的函数。

编译器为每个类对象增加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组中的指针,该指针称为虚表指针,这种函数地址数组称为虚函数表。即:每个类使用一个虚函数表,每个类对象用一个虚表指针。

基类对象包含一个虚表指针,指向基类中虚函数的地址表,其派生类也包含一个虚表指针,指向派生类中虚函数表。如果派生类重写了基类的虚方法,那么派生类将保存重写的虚函数的地址,而不是基类的虚函数地址。如果派生类没有重写基类的虚方法,那么派生类将会继承基类中的虚方法,而且派生类中的虚函数表会保存基类中的虚函数地址。

延伸:

虚表是存在虚函数的类都有一个一维的虚函数表,每一个类对象都有一个指向虚表开始的虚指针,它是在编译时,由编译器生成的。

虚表的内存结构是:类和虚函数表对应,类对象和虚函数指针对应。每一个含有虚函数的类中都有一个虚函数表,虚函数表中的每一项都是一个虚函数的地址,也就是,虚函数中的每一项都是一个虚函数的指针。

每一个类都有一个虚函数表,这个类的对象都有一个虚函数指针。

虚表中的第一项是指向类的类型的指针,也就是所谓的RTTI(Run-Time Type Information),在使用虚函数的时候,编译器根据指针的类型来确定对象的实际类型,从而调用正确的虚函数,在一些特殊的实现中,虚表的第二项是指向虚表本身的指针,这样可以方便地在运行时进行虚函数的查找和调用。

问题:

菱形继承(类D同时继承B和C,B和C又继承自A)体系下,虚表在各个类中的布局如何?如果类B和类C同时有一个成员变了m,m如何在D对象的内存地址上分布的?是否会相互覆盖?

在菱形继承体系下,每个类都有自己的虚表。类D继承了类B和类C,因此它会包含B和C的虚表。同时,B和C都继承自A,因此它们也会包含A的虚表。 在D对象的内存地址上,B和C的成员变量m会分别占用不同的内存空间,不会相互覆盖。这是因为在菱形继承体系下,虚继承(使用关键字virtual)会确保只有一份共享的基类子对象,从而避免了多次继承导致的内存浪费和数据冗余。

修饰普通成员函数

被修饰的函数称为虚函数,是c++中多态的一种。当父类的指针指向或者引用其子类的对象时,当使用指针或者引用调用函数的时候会根据具体的对象类型调用对应对象的函数。

调用对应对象的函数,需要两个条件:一是父类的函数是虚函数,二是子类重写父类的函数。

多态分为:编译时多态-通过重载实现;
                  运行时多态-通过虚函数实现;

#include <iostream>

class father {
public:
	void func1() {std::cout << "this is father func1" << std::endl;}
	virtual void func2() {std::cout << "this is father func2" << std::endl;
}

class son:public father {
public:
	void func1() {std::cout << "this is son func1" << std::endl;}
	void func2() {std::cout << "this is son func2" << std::endl;
}

int main() {
	father *f1 = new son();
	f1.func1();
	f1.func2();
	return 0;
}
// 结果:
this is father func1
this is son func2

使用virtual修饰的函数会根据实际对象的类型来调用,没有使用virtual修饰的根据指针的类型来调用。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。 

修饰析构函数

虚析构函数在调用时调用对象的析构函数,这样就不会出现像有的数据成员没有销毁导致内存泄露的问题或者程序直接崩溃

class GrandFather {
public:
	GrandFather() {std::cout << "construct grandfather" << std::endl;}
	~GrandFather() {std::cout << "destruct grandfather" << std::endl;}
};

class Father:public GrandFather{
public:
	Father() {std::cout << "construct father" << std::endl;}
	~Father() {std::cout << "destruct father" << std::endl;}
};

class Son:public Father{
public:
	Son() {std::cout << "construct son" << std::endl;}
	~Son() {std::cout << "destruct son" << std::endl;}
};

int main() {
	Father *f = new Son();
	delete f;
	return 0;
}
// 结果:
construct grandfather
construct father
construct son
destruct father
destruct grandfather

没有调用son的析构函数,当将Father或者GrandFather其中一个的析构函数修改为virtual后输出

construct grandfather
construct father
construct son
destruct son
destruct father
destruct grandfather

纯虚函数

纯虚函数的定义是在虚函数的后面加一个=0。定义了纯虚函数的类是一个抽象类。

纯虚函数的作用是在基类中声明虚函数,但在基类中不提供具体的实现。它要求任何派生类都要定义自己的实现方法,这样就可以保证所有派生类都有具体的实现。纯虚函数的目的在于,使派生类仅仅继承函数的接口,而不是具体的实现。这样,类可以无法为纯虚函数提供一个合理的默认实现。

virtual void func() = 0;

纯虚函数需要注意这几点:
1.定义了纯虚函数的类不能够实例化,也就是不能够创建对象
2.继承了含有纯虚函数的父类的子类如果没有实现纯虚函数也不能够实例化 

修饰继承性

一个类继承两个或者更多的父类,但是这些父类里又有一些有共同的父类

class GrandFather {
public:
	GrandFather() {std::cout << "construct grandfather" << std::endl;}
	~GrandFather() {std::cout << "destruct grandfather" << std::endl;}
};

class Father1:virtual public GrandFather{
public:
	Father1() {std::cout << "construct father1" << std::endl;}
	~Father1() {std::cout << "destruct father1" << std::endl;}
};

class Father2:virtual public GrandFather{
public:
	Father2() {std::cout << "construct father2" << std::endl;}
	~Father2() {std::cout << "destruct father2" << std::endl;}
};

class Son:public Father1, Father2{
public:
	Son() {std::cout << "construct son" << std::endl;}
	~Son() {std::cout << "destruct son" << std::endl;}
};

int main() {
	Father *f = new Son();
	delete f;
	return 0;
}
// 结果:
construct grandfather
construct father1
construct father2
construct son
destruct son
destruct father2
destruct father1
destruct grandfather

如果没有虚继承GrandFather的时候,结果如下,能会导致程序挂掉
construct grandfather
construct father1
construct grandfather
construct father2
construct son
destruct son
destruct father2
destruct grandfather
destruct father1
destruct grandfather

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值