杂项
1. 类的大小(影响因素)
类对象的大小(sizeof(T)
)主要受以下因素影响:
- 非静态数据成员的数量与类型(不同类型有不同对齐与大小);
- 对齐与填充(编译器的对齐策略会在成员之间插入填充字节);
- 是否包含虚函数(通常对象会包含一个指向虚函数表的指针
vptr
);
不影响对象大小的因素:
- 成员函数的个数(成员函数存在于类的代码区,而非对象实例中);
- 静态数据成员(静态成员属于类而非对象实例);
- 访问控制(public/protected/private 不改变对象布局)。
注意:空类在 C++ 中至少占用 1 字节以保证 distinct address(不同对象有不同地址)。
2. 数据成员规则
- 普通数据成员可为内置类型、数组、指针、引用或其它类类型;
enum
、const
、static
等修饰不会改变类型本身的内存占用(但static
成员不算作对象的一部分);- 结构体或类不能直接递归包含自身的对象成员,但可包含指向自身的指针或引用。
示例:
struct Node { int value; Node* next; }; // 允许指针递归,但不能写成 Node child;
3. 对象实例化与访问
- 对象是类的实例,任何对象都有确定的静态类型;
- 一个类型可以实例化零个或多个对象;
- 访问对象的常见方式:直接调用成员、通过指针或引用访问、通过消息(多态调用)等。
示例:
struct S { void f(){} };
S obj; obj.f();
S* p = &obj; p->f();
S& r = obj; r.f();
面向对象风格强调通过接口(消息)与对象交互,具体实现由对象自身决定(可能导致不同的运行时行为)。
4. 常见基本类型与指针大小(平台相关)
不同平台与编译器下基本类型大小可能不同,下列为常见值(仅作参考):
char
: 1 字节short
: 通常 2 字节int
: 通常 4 字节long
: 32 位系统通常 4 字节,64 位系统视实现可能为 8 字节long long
: 通常 8 字节float
: 4 字节double
: 8 字节long double
: 平台相关(通常 8、12 或 16 字节)- 指针 : 平台相关(32 位系统通常 4 字节,64 位系统通常 8 字节)
要获得精确大小,请在目标平台上使用 sizeof
运算符测试。
5. 虚机制(动态绑定)
静态绑定(early binding):在编译期决定调用哪个函数实现。
动态绑定(late binding):在运行期根据对象的动态类型决定调用哪个函数实现,C++ 通过虚函数机制实现动态绑定。
虚函数的基本规则
- 虚函数必须是非静态成员函数;构造函数与拷贝构造函数不能是虚的;析构函数可以且在有派生类时应声明为
virtual
; - 虚函数可被
const
修饰;访问控制(public/protected/private)对虚函数无特殊限制; - 在派生类中覆盖基类虚函数时,
virtual
关键字可以省略,但建议使用override
明确意图并让编译器检查。
示例:
struct Base { virtual ~Base() = default; virtual void f() { std::cout<<"Base\n"; } };
struct Derived : Base { void f() override { std::cout<<"Derived\n"; } };
虚函数表(vtable)与 vptr
- 实现细节(依赖于编译器):包含虚函数的类通常有一张虚函数表(vtable),对象实例包含一个指向该表的指针(vptr);
- vtable 存放指向具体虚函数实现的指针,运行时通过 vptr 查表并间接调用实现;
- vtable/vptr 的存在会影响对象的大小(通常增加一个指针的大小)。
调用过程(概念性描述):
- 编译期根据表达式的静态类型查找匹配的函数名与签名;
- 若该函数为虚函数,生成间接调用代码,运行时使用对象的 vptr 查表并跳转到实际实现。
示例(概念):
// p->f() 在编译期变为类似: (*p->vptr)[index](p, ...);
注意事项
- 即使某个虚函数在静态类型中未被实际调用,它仍必须存在以便编译通过(即静态类型需要声明该成员);
- 虚继承、虚基类等会使对象布局更复杂并带来额外开销;仅在必要时使用多重继承与虚继承。
如果你希望,我将继续按顺序处理下一个文件 oop8-9.md
。
评论区 - 15_Miscellaneous