在上一篇博客,我们认识到C++通过类将数据和函数封装在一起,解决了C语言全局数据难以管理的问题。但“封装”并非千篇一律,类内部的数据成员类型将深刻影响类的行为、内存布局和资源管理策略。

本篇聚焦于一个最根本的分类维度——类中是否有指针成员,为后续学习拷贝构造、析构函数、运算符重载等核心机制奠定基础。

指针成员的存在性判断

区分“带指针”与“不带指针”的关键在于查看类的私有数据成员声明中是否有 *符号。

QQ20260726-155942

判断是否“带指针”是类设计的第一步,它直接决定了后续三大特殊成员函数(拷贝构造、拷贝赋值、析构)是否需要自定义实现。

注意:指针成员 ≠ 动态分配。指针可以指向栈上变量,但实践中带指针的类几乎总是伴随动态内存分配(new),因此资源管理问题随之而来。
易错:看到 char* 就认为必须深拷贝,但若它指向静态常量区则不必,不过这不是一般设计模式。

两种类型的对象在内存中长什么样?

不带指针的对象与带指针的对象在内存布局上有本质区别,这决定了对象拷贝时的行为差异。

QQ20260726-160555

不带指针的类:对象拷贝时直接复制所有字节(浅拷贝),两个对象完全独立。

带指针的类:默认拷贝只复制指针值(浅拷贝),导致两个指针指向同一块堆内存,析构时重复释放,引发崩溃。

指针成员带来的资源管理问题

带指针的类必须处理动态内存分配与释放,否则会出现严重的内存错误。

核心问题是 浅拷贝 vs 深拷贝

QQ20260726-161136

❌ 浅拷贝(默认)

1
2
3
4
String s1("Hello");
String s2 = s1; // 复制指针
// s1.str 和 s2.str 指向同一地址
// 析构时 double free!

✅ 深拷贝(自定义)

1
2
3
4
5
String(const String& other) {
str = new char[strlen(other.str)+1];
strcpy(str, other.str); // 复制内容
}
// s1 和 s2 各自拥有独立内存

带指针的类必须实现“三法则”(Rule of Three):如果类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么通常三个都需要自定义。