一、什么是虚函数?
在基类中使用 virtual 关键字修饰的成员函数称为虚函数。
用法格式:
virtual 函数返回值类型 函数名 (参数表){
函数体;
}
虚函数是 C++ 中实现多态性的重要机制,它允许在派生类中重写基类中的函数。虚函数的核心特性在于,它使得通过基类指针或引用调用函数时,可以根据实际对象的类型选择相应的函数实现。
二、虚函数的作用
虚函数的作用,用专业术语来解释就是实现多态性(Polymorohism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。
具体而言,就像买火车票这件事。同样是买火车票,不同的人去买有不同的结果,学生买火车票半价,而军人优先购票等。
下面来看这样一段代码:
#include
using namespace std;
class A
{
public:
void print()
{
cout<<"This is A"< } }; class B : public A { public: void print() { cout<<"This is B"< } }; class C : public A { public: void print() { cout<<"This is C"< } }; int main() { //为了在以后便于区分,我这段main()代码叫做main1 A a; B b; C c; a.print(); b.print(); c.print() return 0; } 运行结果: This is A This is B This is C 通过class A、class B和class C的print()这个接口,可以看出这三个class因个体的差异而采用了不同的策略,但这并不是多态性行为。这里,a.print()、b.print() 和 c.print() 都是静态绑定,因此编译器直接根据对象的类型选择调用相应的 print() 方法,而不管是否存在派生类。 现在把main()处的代码改一改。 #include using namespace std; class A { public: void print() { cout<<"This is A"< } }; class B : public A { public: void print() { cout<<"This is B"< } }; class C : public A { public: void print() { cout<<"This is C"< } }; int main() { //为了在以后便于区分,我这段main()代码叫做main2 A a; B b; C c; A *p1 = &a; A *p2 = &b; A *p3 = &c; p1->print(); p2->print(); p3->print(); return 0; } 运行结果: This is A This is A This is A 问题来了,p2、p3明明指向的是class B、class C的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数。 #include using namespace std; class A { public: virtual void print() { cout<<"This is A"< } }; class B : public A { public: void print() { cout<<"This is B"< } }; class C : public A { public: void print() { cout<<"This is C"< } }; int main() { //为了在以后便于区分,我这段main()代码叫做main2 A a; B b; C c; A *p1 = &a; A *p2 = &b; A *p3 = &c; p1->print(); p2->print(); p3->print(); return 0; } 运行结果: This is A This is B This is C 毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了(语法上可加可不加,不加的话编译器会自动加上,但为了阅读方便和规范性,建议加上)。 现在重新运行main2的代码,这样输出的结果就是This is A、This is B和This is C了。 现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。 C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数(动态绑定)。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。 三、虚函数的底层实现 虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。 实现原理:虚函数表+虚表指针 编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。 举个例子:基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。看下面两种情况: 如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。 如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。 调用虚函数时,程序将查看存储在对象中的虚函数表地址,转向相应的虚函数表,使用类声明中定义的第几个虚函数,程序就使用数组的第几个函数地址,并执行该函数。 四、使用虚函数后的变化 (1) 对象将增加一个存储地址的空间(32位系统为4字节,64位为8字节)。 (2) 每个类编译器都创建一个虚函数地址表。 (3) 对每个函数调用都需要增加在表中查找地址的操作。 五、虚函数的注意事项 在使用虚函数时,有一些重要的注意事项,以确保代码的正确性、可维护性和性能。以下是一些关键点: 1. 基类析构函数应为虚函数 如果类包含虚函数,其析构函数也应声明为虚函数。这确保通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,避免资源泄漏。 2. 虚函数的重写 当派生类重写基类的虚函数时,使用 override 关键字可以增加可读性,并在编译时进行检查。如果派生类中的函数没有正确重写基类的虚函数,编译器会给出警告。 3. 性能开销 虚函数调用比普通函数调用的性能开销稍高,因为它涉及查找虚函数表。对于频繁调用的性能敏感函数,尽量使用非虚函数,或者考虑其他设计方案。 4. 多重继承中的虚函数 在多重继承中,虚函数可能会导致复杂性。应仔细设计类的继承结构,以避免潜在的二义性问题。使用虚基类可以帮助解决此类问题。 5. 指向虚函数的指针 在一些情况下,使用基类指针指向派生类对象时,应确保适当地处理对象的生命周期,避免内存泄漏或访问无效指针。
bty缩写的词(探究 “BTY” 缩写的词的背后含义)
如何折叠西装衬衫(超实用)出差党必看