C++笔记之实现多态的所有方法
文章目录
- C++笔记之实现多态的所有方法
- 1.C++中多态是是什么?请用简洁准确的话描述
- 2.虚函数实现多态
- 2.1.虚函数(Virtual Functions)
- 2.2.纯虚函数(Pure Virtual Functions)
- 2.3.虚析构函数(Virtual Destructor)
- 2.4.虚函数表(Virtual Function Table)
- 3.函数指针(Function Pointers)
- 4.模板(Templates)
- 5.达到多态性的效果
- 5.1.接口类(Interface Classes)
- 5.2.可调用对象(Callable Objects)
- 6.C++中函数重载是不是一种实现多态的方法?
- 7.虚函数表
- 8.虚函数指针
- 9.抄自别的文章——C++中虚函数的实现
- 虚函数表(vtable)
- 虚函数指针(vptr)
- 多态的工作机制
- 注意事项
1.C++中多态是是什么?请用简洁准确的话描述
2.虚函数实现多态
在C++中,实现多态性(polymorphism)的主要方法是通过使用虚函数(virtual functions)。下面是C++中实现多态性的方法:
2.1.虚函数(Virtual Functions)
在基类中声明一个虚函数,在派生类中可以对该函数进行重写。通过使用指向基类的指针或引用来调用该函数时,实际上会根据对象的实际类型调用相应的派生类函数。这是C++中最常见的多态性实现方式。
class Base {
public:virtual void polymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() override {// Derived class implementation}
};
2.2.纯虚函数(Pure Virtual Functions)
纯虚函数是在基类中声明的虚函数,但没有提供实际实现。派生类必须重写这些纯虚函数才能被实例化。类包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能被继承。
class AbstractBase {
public:virtual void pureVirtualFunction() = 0; // Pure virtual function
};class Derived : public AbstractBase {
public:void pureVirtualFunction() override {// Derived class implementation}
};
2.3.虚析构函数(Virtual Destructor)
如果基类需要被其他类继承,而且基类中有使用了new关键字动态分配内存的成员,那么必须在基类中将析构函数声明为虚函数,以确保在删除指向派生类对象的基类指针时能正确调用派生类的析构函数,避免内存泄漏。
class Base {
public:virtual ~Base() {// Base class destructor}
};class Derived : public Base {
public:~Derived() override {// Derived class destructor}
};
2.4.虚函数表(Virtual Function Table)
C++编译器通过在包含虚函数的类中插入一个虚函数表来实现多态性。该表包含了类的虚函数地址,允许在运行时动态调用正确的函数实现。
这些是C++中实现多态性的主要方法。虚函数和纯虚函数是最常见和推荐的实现方式。
除了使用虚函数,C++中还有另外两种实现多态性的方法:
3.函数指针(Function Pointers)
使用函数指针可以在运行时实现多态性。通过定义一个指向函数的指针,然后在派生类中赋值为相应的函数,可以实现动态调用不同的函数实现。
class Base {
public:void nonPolymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() {// Derived class implementation}
};int main() {Base* ptr;Derived derivedObj;ptr = &derivedObj;// Function pointer for polymorphic behaviorvoid (Base::*functionPtr)() = &Base::nonPolymorphicFunction;(ptr->*functionPtr)(); // Calls Base::nonPolymorphicFunction()functionPtr = &Base::polymorphicFunction;(ptr->*functionPtr)(); // Calls Derived::polymorphicFunction()return 0;
}
4.模板(Templates)
C++的模板也可以实现多态性,特别是通过模板类和模板函数。使用模板参数,可以在编译时根据不同的参数类型生成不同的代码,从而实现多态性。
template <typename T>
class PolymorphicClass {
public:void polymorphicFunction() {// Common implementation for all types}
};template <>
void PolymorphicClass<int>::polymorphicFunction() {// Implementation specific to int
}int main() {PolymorphicClass<float> floatObj;PolymorphicClass<int> intObj;floatObj.polymorphicFunction(); // Calls the common implementationintObj.polymorphicFunction(); // Calls the specialization for intreturn 0;
}
虽然虚函数是实现多态性的最常见和推荐方法,但是这两种方法也提供了额外的灵活性,特别是在特定情况下可以使用函数指针来实现更高度定制化的多态性。模板也可以在编译时优化代码,提高性能。但这些方法相对较少使用,主要是因为虚函数在C++中提供了更为直接和方便的多态性实现。
5.达到多态性的效果
除了前面提到的虚函数、函数指针、模板和虚拟继承,C++中没有其他内置的语言特性来直接实现多态性。这些是C++中主要的方法来实现多态行为。
然而,C++是一门非常灵活的语言,允许通过各种技术和设计模式来实现更高级的多态性。下面列举了一些可能的方法,虽然它们不是直接的语言特性,但仍然可以达到多态性的效果:
5.1.接口类(Interface Classes)
C++中没有内置的接口类概念,但可以通过定义只包含纯虚函数的抽象类来模拟接口。其他类可以继承这个接口类并实现其纯虚函数,从而实现多态性。
class Interface {
public:virtual void doSomething() = 0;
};class ConcreteClass : public Interface {
public:void doSomething() override {// Implementation in ConcreteClass}
};
5.2.可调用对象(Callable Objects)
C++中的函数对象(Functor)或Lambda表达式可以作为可调用对象,使得可以在运行时动态决定调用哪个函数,从而实现多态性。
#include <functional>class Base {
public:virtual void polymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() override {// Derived class implementation}
};int main() {Base* ptr = new Derived;// Using std::function as a callable object for polymorphic behaviorstd::function<void(Base*)> func = [](Base* obj) { obj->polymorphicFunction(); };func(ptr); // Calls Derived::polymorphicFunction()return 0;
}
这些方法都是在C++中模拟多态性的常见手段。请注意,这些方法可能需要更多的代码和额外的管理,相比于虚函数的直接性和简洁性,可能并不是首选方案。因此,虚函数仍然是实现多态性的最常用和推荐的方式。
6.C++中函数重载是不是一种实现多态的方法?
在严格意义上,C++中的函数重载并不被认为是一种实现多态的方法。
函数重载是一种在同一个作用域内定义多个同名函数,但参数列表不同的机制。当调用这个函数时,编译器会根据传递的参数类型或数量来决定调用哪个具体的函数。函数重载主要用于提供一种方便的方式,让程序员能够使用相同的函数名处理不同类型或不同数量的数据,以增强代码的可读性和灵活性。
虽然函数重载使得代码更具灵活性和可读性,但它并没有实现多态性。多态性涉及到在运行时选择调用哪个函数的能力,这通常通过虚函数来实现,允许根据对象的实际类型来调用适当的函数。
在函数重载中,函数调用在编译时就已经确定了,不会根据对象的实际类型在运行时决定调用哪个函数。相比之下,使用虚函数时,函数调用是在运行时动态绑定的,允许在处理基类指针或引用时调用派生类中的函数实现,实现了真正的多态性。
总结来说,函数重载和多态性是不同的概念。函数重载是一种静态多态,而多态性通常指动态多态,它通过虚函数实现,允许在运行时根据对象的实际类型来选择调用适当的函数。
7.虚函数表
虚函数表(Virtual Function Table),通常简称为虚表,是C++中实现多态性的关键机制之一。它是一个用于存储虚函数地址的表格,每个具有虚函数的类都会在内存中维护一个虚函数表。
虚函数表的工作原理如下:
- 虚函数表是一个包含指向虚函数的指针的数组。每个虚函数在表中占据一个槽位。
- 对于每个包含虚函数的类,编译器会在类的布局中插入一个指向其虚函数表的指针,通常称为虚函数指针(vptr)。
- 当一个对象被创建时,它的虚函数指针被初始化为指向该类的虚函数表。
- 调用虚函数时,实际上是通过对象的虚函数指针查找虚函数表,然后调用相应的虚函数。
这个机制使得基类指针或引用可以在运行时动态地调用派生类中适当的虚函数,从而实现多态性。
以下是一个简化的示例来说明虚函数表的概念:
class Base {
public:virtual void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derivedObj;basePtr = &derivedObj;basePtr->show(); // Calls Derived::show() through the virtual function tablereturn 0;
}
在这个示例中,Base
类和 Derived
类都有一个虚函数 show()
。当通过 basePtr->show()
调用虚函数时,实际上会根据对象 derivedObj
的实际类型在虚函数表中查找正确的虚函数地址,然后调用 Derived::show()
。这就是虚函数表的工作方式。
虚函数表的内部结构在不同的编译器和平台上可能会有所不同。我无法在这里直接为您提供一个完整的虚函数表的打印输出,因为虚函数表的具体结构可能会受到编译器、类的层次结构以及继承关系等因素的影响。
但是,我可以向您展示一个概念性的虚函数表结构,帮助您理解它的大致原理。请注意,这只是一个示意图,实际虚函数表的结构可能会更加复杂。
假设我们有一个类 Base
和一个派生类 Derived
,它们都有一个虚函数 show()
。
虚函数表示意图:Base类虚函数表
+---------------------+
| 指向 Base::show() |
+---------------------+Derived类虚函数表(继承自Base类)
+---------------------+
| 指向 Derived::show()|
+---------------------+
在这个示意图中,每个类都有一个指向其虚函数的指针,这个指针构成了虚函数表的一部分。当一个类没有虚函数时,它的虚函数表可能为空。派生类会继承基类的虚函数表,并在需要时进行覆盖。这样,通过对象的虚函数指针,可以在运行时查找到正确的虚函数实现。
请注意,虚函数表的实际结构会受到编译器优化和内存布局等因素的影响,因此它的具体形式可能会因编译器而异。
8.虚函数指针
虚函数指针的具体样子会受到编译器和平台的影响,因此无法直接为您提供精确的打印输出。虚函数指针通常是一个指向虚函数表的指针,用于在运行时查找和调用适当的虚函数。
以下是一个简化的示例来说明虚函数指针的概念:
#include <iostream>class Base {
public:virtual void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derivedObj;basePtr = &derivedObj;// 虚函数指针的示意,这是一个虚函数表的指针void** vptr = reinterpret_cast<void**>(basePtr);// 第一个槽位通常是指向虚函数表本身的指针,后面是实际的虚函数指针// 注意:实际虚函数指针的内容可能因编译器和平台而异void* firstSlot = *vptr;void* showFunctionPtr = *(vptr + 1);std::cout << "Address of virtual function table: " << firstSlot << std::endl;std::cout << "Address of show() function: " << showFunctionPtr << std::endl;return 0;
}
请注意,这个示例中展示的内容是概念性的,实际虚函数指针的结构会更加复杂,并且受到编译器和平台的影响。虚函数指针通常位于对象的内存布局的开头部分,并指向类的虚函数表。在运行时,通过虚函数指针查找虚函数表,然后找到正确的虚函数地址进行调用。
9.抄自别的文章——C++中虚函数的实现
C++ 中虚函数的实现涉及到虚函数表(vtable)和虚函数指针(vptr),这是实现多态性的关键。
虚函数表和虚函数指针允许程序在运行时确定要调用的函数,而不是在编译时确定。
虚函数表(vtable)
对于每个拥有虚函数的类,编译器会生成一个虚函数表,通常在类的内部,作为类的一部分。这个虚函数表是一个指向函数指针的数组,其中包含了该类中每个虚函数的地址。虚函数表的第一个函数指针通常指向类的析构函数。接下来是按照虚函数在类中声明的顺序排列的函数指针。子类将继承父类的虚函数表,并可以在其末尾添加自己的虚函数指针。
虚函数指针(vptr)
对于每个类的对象,编译器会生成一个虚函数指针(通常称为vptr),该指针指向对象所属类的虚函数表。这个vptr通常位于对象的内存布局的开头。当调用虚函数时,实际上是通过对象的vptr找到正确的虚函数表,然后在虚函数表中查找要调用的函数的地址,最后执行该函数。
多态的工作机制
当通过基类指针或引用调用虚函数时,程序首先访问对象的vptr,然后从虚函数表中找到要调用的函数。这个机制允许在运行时根据对象的实际类型而不是指针或引用的静态类型来调用正确的函数,实现了多态性。
注意事项
构造函数不能是虚函数,因为在对象的构造过程中,虚函数表可能尚未完全设置。而析构函数可以为虚函数。C++标准并未规定虚函数表和虚函数指针的实现方式,因此不同编译器可能有不同的底层实现。但在大多数情况下,它们遵循上述概念。