class 是一种特殊的 struct
在内存中 class 依旧可以看作变量的集合
class 和 struct 遵循相同的内存对齐原则
class 中的成员函数和成员变量是分开存放的
- 每个对象有独立的成员变量
- 所有对象共享类中的成员函数
值得思考的问题
对象内存布局初探
#include <iostream>
#include <string>
using namespace std;
class A
{
int i;
int j;
char c;
double d;
public:
void print()
{
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
A a;
cout << "sizeof(A) = " << sizeof(A) << endl; // 24 bytes
cout << "sizeof(a) = " << sizeof(a) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl; // 24 bytes
a.print();
B* p = reinterpret_cast<B*>(&a);
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3;
a.print();
p->i = 100;
p->j = 200;
p->c = 'C';
p->d = 3.14;
a.print();
return 0;
}
上面的代码中定义了 class A 和 struct B,class A 和 struct B 具有相同的成员变量,class A 多了一个 print 函数
第 34 行 - 36 行,打印 class A 和 struct B 所占用的内存空间
第 40 行,将 class A 类型强转为 struct B,然后对里面的成员变量进行赋值,看 class 和 struct 内存布局是否一致
程序运行结果如下图所示:
通过打印可以看出成员函数不占据类的存储空间,具有相同成员变量的 class 和 struct 在内存空间中排布位置是一样的,遵循相同的内存对齐原则。所占用的内存空间是一样的。通过强制类型转换,我们对 class A 里 private 访问权限的成员变量的值进行了修改,说明了访问权限关键字在运行的时候无效
运行时的对象退化为结构体的形式
所有成员变量在内存中依次排布
成员变量间可能存在内存间隙
可以通过内存地址直接访问成员变量
访问权限关键字在运行时失效
类中的成员函数位于代码段中
调用成员函数时对象地址作为参数隐式传递
成员函数通过对象地址访问成员变量
C++ 语法隐藏了对象地址的传递过程
对象本质分析
demo.h
#ifndef __DEMO_H
#define __DEMO_H
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* d);
int Demo_GetJ(Demo* d);
int Demo_Add(Demo* d, int value);
void Demo_Free(Demo* d);
#endif
demo.c
#include "demo.h"
#include <stdlib.h>
struct ClassDemo
{
int mI;
int mJ;
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if(ret)
{
ret->mI = i;
ret->mJ= j;
}
return ret;
}
int Demo_GetI(Demo* d)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mI;
}
return ret;
}
int Demo_GetJ(Demo* d)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mJ;
}
return ret;
}
int Demo_Add(Demo* d, int value)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mI + obj->mJ + value;
}
return ret;
}
void Demo_Free(Demo* d)
{
free(d);
}
main.c
#include "demo.h"
#include <stdio.h>
int main(void)
{
Demo* d = Demo_Create(1, 2);
if(d)
{
printf("i = %d\n", Demo_GetI(d));
printf("j = %d\n", Demo_GetJ(d));
printf("i + j + %d = %d\n", 3, Demo_Add(d, 3));
Demo_Free(d);
}
return 0;
}
我们用 C 语言模拟了 C++ 中 class 的部分特性,上面的代码体现了封装的特性,用户只知道成员函数的声明,不知道这个类里的有什么成员变量和成员函数的内部实现
在使用成员函数的时候,需要我们手动将对象的地址传递进去,这样函数能够知道使用的是哪个对象。在 C++ 中,一个类对象调用成员函数的时候,编译器会将这个对象的地址传递到成员函数中去
程序运行结果如下图所示:
继承对象模型
在 C++ 编译器的内部类可以理解为结构体
子类是由父类成员叠加子类新成员得到的
C++ 多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储成员函数地址的数据结构
虚函数表是由编译器自动生成与维护的
virtual 成员函数会被编译器放入虚函数表中
存在虚函数时,每个对象都有一个指向虚函数表的指针
继承对象模型初探
#include <iostream>
#include <string>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
public:
virtual void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo
{
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
void* p;
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
d.print();
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
return 0;
}
Demo 类中存在一个虚函数,所以编译器会给这个类的内存开始处塞一个指向虚函数表的指针Derived 类继承自 Demo 类,新增一个成员 mk
第 52 行,我们将 Derived 类强转为 Test 类,后续再对它的成员变量赋值,是验证 Derived 类的内存布局和 Test 类的内存布局是否相同
程序运行结果如下图所示:
通过打印可以看出,Derived 类的内存布局和 Test 类的内存布局是一样的,当类中存在虚函数时,那么编译器会往这个类的内存开始处塞一个指向虚函数表的指针,子类是由父类成员叠加子类新成员得到的
多态本质分析
demo.h
#ifndef __DEMO_H
#define __DEMO_H
typedef void Demo;
typedef void Derived;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* d);
int Demo_GetJ(Demo* d);
int Demo_Add(Demo* d, int value);
void Demo_Free(Demo* d);
Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* d);
int Derived_Add(Derived* d, int value);
#endif
demo.c
#include "demo.h"
#include <stdlib.h>
typedef struct
{
int (*pAirtualAdd)(Demo*, int);
} vtable;
struct ClassDemo
{
vtable* vptlb;
int mI;
int mJ;
};
struct ClassDerived
{
struct ClassDemo demo;
int mK;
};
static int Demo_VirtualAdd(Demo* d, int value);
static int Derived_VirtualAdd(Demo* d, int value);
static vtable demo_table =
{
Demo_VirtualAdd
};
static vtable derived_table =
{
Derived_VirtualAdd
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if(ret)
{
ret->vptlb = &demo_table;
ret->mI = i;
ret->mJ= j;
}
return ret;
}
int Demo_GetI(Demo* d)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mI;
}
return ret;
}
int Demo_GetJ(Demo* d)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mJ;
}
return ret;
}
static int Demo_VirtualAdd(Demo* d, int value)
{
int ret = -1;
if(d)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
ret = obj->mI + obj->mJ + value;
}
return ret;
}
int Demo_Add(Demo* d, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)d;
return obj->vptlb->pAirtualAdd(d, value);
}
void Demo_Free(Demo* d)
{
free(d);
}
Derived* Derived_Create(int i, int j, int k)
{
struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
if(ret)
{
ret->demo.vptlb = &derived_table;
ret->demo.mI = i;
ret->demo.mJ = j;
ret->mK = k;
}
return ret;
}
int Derived_GetK(Derived* d)
{
int ret = -1;
if(d)
{
struct ClassDerived* obj = (struct ClassDerived*)d;
ret = obj->mK;
}
return ret;
}
static int Derived_VirtualAdd(Demo* d, int value)
{
int ret = -1;
if(d)
{
struct ClassDerived* obj = (struct ClassDerived*)d;
ret = obj->mK + value;
}
return ret;
}
int Derived_Add(Derived* d, int value)
{
struct ClassDerived* obj = (struct ClassDerived*)d;
return obj->demo.vptlb->pAirtualAdd(d, value);
}
main.c
#include "demo.h"
#include <stdio.h>
int Add(Demo* d, int value)
{
int ret = Demo_Add(d, value);
return ret;
}
int main(void)
{
Demo* demo = Demo_Create(1, 22);
Derived* derived = Derived_Create(1, 22, 333);
if(demo && derived)
{
printf("%d\n", Add(demo, 10));
printf("%d\n", Add(derived, 10));
Demo_Free(demo);
Demo_Free(derived);
}
return 0;
}
上面的代码使用 C 语言实现了 C++ 的继承和多态特性,Demo 类为父类,Derived 类为子类,Demo 类里的 Add 函数为虚函数,所以我们需要为 Demo 类生成一个虚函数表,虚函数表是一个数据结构,里面存放着虚函数的地址,并且我们需要为 Demo 类的内存开始处塞一个虚函数指针,这个虚函数指针指向虚函数表,我们在创建 Demo 类对象的时候,会将 Demo 类的虚函数指针指向 Demo 类的虚函数表;由于 Derived 类继承自 Demo 类,所以 Derived 类里的 Add 函数也为虚函数,我们需要手动为 Derived 类创建一个虚函数表,创建 Derived 类对象的时候,将 Derived 类的虚函数指针指向 Derived 类的虚函数表。这样 Add 函数就具有了多态的特性,调用 Add 函数时,会根据实际的对象类型来调用
程序运行结果如下图所示:
Add 函数具有了多态特性
小结
C++ 中的类对象在内存布局上与结构体相同
成员变量和成员函数在内存中分开存放
访问权限关键字在运行时失效
调用成员函数时对象成员作为参数隐式传递
继承的本质就是父子间成员变量的叠加
C++ 中的多态就是通过虚函数表实现的
虚函数表是由编译器自动生成与维护的
虚函数表的调用效率低于普通函数