最近因为同学的一个问题,我研究了一下 C++ 的多态性。
先讲讲什么是重载(overload)、覆写(override)和多态(polymorphism)。
重载是:同一个方法名,不同入参对应不同函数,实际是不同的方法签名。
覆写是:方法名和入参都相同,即方法签名相同,却对应不同函数。一般允许子类继承父类,覆写父类方法。
多态是:通过父类的方法签名,实际访问到了子类覆写的方法,这一现象叫多态。
C++ 支持静态联编的编译时多态,和动态联编的运行时多态。函数联编是指对一个函数的调用,是确定“函数引用的目标函数体”的过程,可以理解为确定 foo()
这里 foo
指向的真实函数体地址的过程。
C++ 的动态联编通过虚函数(virtual method / virtual function)实现。虚函数存在于继承关系中,在基类声明虚函数,子类覆写虚函数,使我们能够通过表面使用基类的该函数,实际访问到子类覆写后的函数。
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(){}
virtual void foo()=0; // 纯虚函数,声明该函数在 A 中不会被实现
virtual~A(){}
};
class B : public A {
public:
B() {}
virtual void foo();
virtual ~B(){}
};
void B::foo() {
cout << "Hello, virtual method." << endl;
}
int main() {
A* b = new B();
b->foo(); // Hello, virtual method.
return 0;
}
注意,必须使用指针访问,如果不通过指针访问会发生对象切片(object slicing)。
C++ 中虚函数是通过虚函数表(virtual table,v-table)来实现的。
每个有虚函数的类有一个虚函数表,包括纯虚函数和派生类中隐式声明的虚函数。虚函数表的入口指针在对象最开始的位置。虚函数表只存储虚函数“函数指针”的地址,不存放普通函数或是构造函数指针的地址。
//TODO 虚函数的内存分析这里不写了,看到前辈总结的已经很好,留个链接:
看下面的例子:
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(){}
virtual void foo()=0;
virtual~A(){}
};
class B : public A {
public:
B() {}
virtual void foo();
virtual ~B(){}
};
void B::foo() {
cout << "Hello, virtual method." << endl;
}
typedef void (*func)(A*);
int main() {
A* b = new B();
void*** vtable = (void***)(b);
cout << "虚函数表的地址 " << table << endl;
void** func1 = (void**)(*table + 0);
cout << "第一个虚函数的地址" << func1 << endl;
func p = (func)(*func1);
p(); // Hello, virtual method.
return 0;
}
其实上面的实例有问题,C++ 的对象中 this 的实现是在编译时改写原方法,在首位增加参数 _this,是指针指向对象。
可以理解为(但实际签名不是这样):
void B_foo(B* _this) {
cout << "Hello, " << _this->val << endl;
}
int main() {
A* b = new B();
B_foo(b);
return 0;
}
所以,当我们需要在虚函数中使用对象变量的时候,我们就要传入第一个参数:
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(){}
virtual void foo()=0;
virtual~A(){}
};
class B : public A {
public:
B(int a = 0) : a(a) {}
virtual void foo();
virtual ~B(){}
private:
int a;
};
void B::foo() {
cout << "Hello, " << this->a << endl;
}
typedef void (*func)(A*);
int main() {
A* b = new B();
void*** vtable = (void***)(b);
void** func1 = (void**)(*table + 0);
func p = (func)(*func1);
p(b); // Hello, 0
return 0;
}
现在是不是对虚函数、甚至 C++ 对象的实现有了一些认识?祝大家学习愉快。