面向对象(一):类与对象基础
原理
对象内存布局
对象仅存储非静态成员变量和vptr(有虚函数时)。其余内容均不在对象内:
| 内容 | 占对象空间? | 存储位置 |
|---|---|---|
| 非静态成员变量 | 是 | 对象内,按声明顺序 |
| vptr(有虚函数时) | 是(8B) | 对象偏移 0 处 |
| 构造函数/析构函数 | 否 | .text |
| 普通成员函数 | 否 | .text |
| 静态成员变量 | 否 | .data / .bss |
| 静态成员函数 | 否 | .text |
内存对齐
规则:当前偏移必须能被成员类型大小整除,否则插入 padding。对象总大小也必须能被最大对齐值整除。
64 位 Linux 常见对齐:char=1,short=2,int/float=4,double/指针/vptr=8。
class Demo {
char a; // 1B, offset 0
int b; // 4B, offset 4 (pad 3)
double c; // 8B, offset 8
short d; // 2B, offset 16
virtual void f(); // vptr 8B, offset 0
// vptr(8) + a(1)+pad(3) + b(4) + c(8) + d(2)+pad(6) = 32
};
// 注意:vptr 始终在对象开头,因此它改变了整个布局this 指针的传递机制
每个非静态成员函数在调用时,编译器把对象地址作为第一个隐式参数传入:
- System V ABI (Linux/macOS):
this→ rdi 寄存器 - Microsoft x64 ABI (Windows):
this→ rcx 寄存器
在成员函数内部,this->x = val; 翻译为 mov [rdi + x_offset], esi。const 成员函数中的 this 类型为 const T* const——这就是 const 成员函数无法修改成员变量的底层原因。
构造函数的汇编层分解
; Widget w(42, "hello") 的底层过程:
; 1. 栈上分配空间
sub rsp, 32
; 2. 传递 this
lea rdi, [rsp] ; rdi = &w = this
; 3. 传递参数
mov esi, 42 ; 参数1
lea rdx, [rip+.Lstr] ; 参数2
; 4. 调用构造函数
call Widget::Widget(int, const char*)构造/析构顺序
构造顺序:基类 → 成员变量(按声明顺序)→ 构造函数体。初始化列表先于构造函数体执行。析构顺序是完全逆序。
const/引用成员必须在初始化列表中初始化,因为它们没有默认构造后的赋值语义。
浅拷贝 vs 深拷贝
编译器默认生成的拷贝构造函数执行浅拷贝——对指针成员仅复制指针值(地址),导致两个对象内的指针指向同一块堆内存。析构时发生 double-free。深拷贝为新对象分配独立内存并逐字节复制内容。
// 深拷贝核心思路
DeepString(const DeepString& other)
: buffer(new char[other.size + 1]) {
memcpy(buffer, other.buffer, other.size + 1);
}this 的隐式/显式使用规则
- 隐式:直接写成员名时,编译器自动补
this-> - 必须显式:参数名与成员名相同时;模板依赖名称(
Base<T>::中的成员);链式调用返回*this;将自身传给外部函数
template <typename T>
class Derived : public Base<T> {
void process() {
this->data = T{}; // 必须显式——两阶段查找规则
}
};语法
类定义与访问控制
class Widget {
public: // 外部可访问
Widget() : val(0) {}
int getVal() const { return val; }
private: // 仅类内部可访问
int val;
protected: // 派生类可访问
static int count; // 静态成员:所有对象共享
};构造函数
// 默认、带参、拷贝、委托、移动
Widget() = default; // 编译器生成
Widget(int v) : val(v) {} // 带参
Widget(const Widget& other) : val(other.val) {} // 拷贝
Widget(int a, int b) : Widget(a + b) {} // 委托(C++11)
Widget(Widget&& other) noexcept : val(other.val) { other.val = 0; } // 移动explicit / =default / =delete
explicit Widget(int v) : val(v) {} // 禁止隐式转换
Widget(const Widget&) = delete; // 禁止拷贝
Widget& operator=(const Widget&) = default; // 显式要求编译器生成析构函数
~Widget() { /* 清理资源 */ }
virtual ~Base() = default; // 基类的析构必须是虚函数static 成员
class Counter {
static int total;
static int getTotal() { return total; }
};
int Counter::total = 0; // 类外定义const / mutable
class Timer {
int getVal() const { cachedVal = calc(); return cachedVal; }
mutable int cachedVal; // const 方法中可修改
};实践
力扣题目:力扣去重排序(用类封装排序+去重),力扣数组题(struct/class 与比较)。
AI 自检:给 AI 以下代码,要求分析内存布局和构造函数调用顺序:
class A { int x; virtual void f(); };
class B : public A { double y; };
// 分析 sizeof(B) 的值和内部布局(含 vptr 位置)建议先阅读:03_指针与引用 — this 指针、成员指针、引用的底层理解;06_面向对象(二)继承 — 派生类构造/析构链和内存布局。