C成员函数
如果用C语言做一个类似面向对象的类
typedef struct Actress {
int height;
int weight;
int age;
void (*desc)(struct Actress*);
};
void profile(Actress* obj) {
("height:%d weight:%d age:%d", obj->height, obj->weight, obj->age);
printf}
int main() {
// 初始化
;
Actress a.height = 168;
a.weight = 50;
a.age = 20;
a.desc = profile;
a
// 成员函数调用
.desc(&a);
areturn 0;
}
想要达到面向对象中数据和操作封装在一起的效果,只能给struct里面添加函数指针,然后给函数指针赋值。然而函数指针是有空间成本的,这样写每个实例化的对象都会有一个指针大小(比如\(FSIZE=8B\)),如果要实例化\(N\)个对象,每个对象有\(M\)个成员函数,那么就要占用\(FSIZE\cdot N\cdot M\)的内存
C++成员函数
静态绑定
class Actress {
public:
int height;
int weight;
int age;
void desc() {
("height:%d weight:%d age:%d", obj->height, obj->weight, obj->age);
printf}
};
在C++中,类和操作的封装只是对于程序员而言的。而编译器编译之后,得到的还是面向过程的代码。编译器会帮成员函数增加一个额外的类指针参数,运行期间传入对象实际的指针。类的数据(成员变量)和操作(成员函数)其实还是分离的
在类不含有虚函数的情况下,编译器在编译期就会把函数地址确定下来,运行期间直接调用这个地址的函数即可。这种函数调用方式也就是所谓的静态绑定
动态绑定/延迟绑定
class Rect {
public:
(int h, int w): height(h), width(w) {}
Rectvirtual void desc() {
("Rect height:%d width:%d", height, width);
printf}
int height;
int width;
};
class Square: public Rect {
public:
(int a): Rect(a,a) {}
Squarevirtual void desc() {
("Square height:%d width:%d", height, width);
printf}
};
int main() {
(10);
Square s.desc(); // Square height:10 width: 10
s* r1 = &s;
Rect& r2 = s;
Rect->desc(); // Square height:10 width: 10
r1.desc(); // Square height:10 width: 10
r2return 0;
}
用父类指针/引用指向子类,最终调用desc函数还是调用子类的
这个现象称之为动态绑定/延迟绑定
NO VIRTUAL
倘若父类Rect中的desc函数前面的virtual去掉,Rect*/Rect&最终将调用父类的desc函数:虽然指针指向的还是子类的内存空间,但是类的数据(成员变量)和操作(成员函数)其实是分离的。仅从对象的内存布局来看,只能看到成员变量,看不到成员函数,调用哪个函数是编译期就固定了的,编译期只能识别父类的desc
虚表
C++具体多态的实现一般是编译器厂商自由发挥的,但使用虚表指针来实现多态几乎是最常见的做法(基本上已经是最好的多态实现方式)
含有虚函数的类编译期间,编译器会自动给这种类在起始位置追加一个虚表指针(称之为vptr)。vptr指向一个虚表(称之为vtable或vtbl),虚表中存储了实际函数地址
top offset
用于多继承中,在调用虚函数之前,对this指针进行调整,保证即使是Base*,也能调用到正确的Derive,这个技巧就是所谓的thunk
typeinfo
RTTI:运行阶段类型识别(Runtime Type Identification)