类与对象:深入理解默认成员函数
- 引言
- 1、默认成员函数概述
- 2、构造函数与析构函数
- 2.1 默认构造函数
- 2.2 析构函数
- 3、拷贝控制成员
- 3.1 拷贝构造函数
- 3.2 赋值运算符重载
- 4、移动语义(C++11)
- 4.1 移动构造函数
- 4.2 移动赋值运算符
- 5、三五法则与最佳实践
- 5.1 三五法则(Rule of Three/Five)
- 5.2 显式控制
- 5.3 建议
- 6、总结
引言
C语言是面向过程的语言,强调函数和结构化编程,而C++在兼容C语言的基础上,增加了面向对象编程、泛型编程等特性。
- 典型对比:
// C:过程式
typedef struct Stack
{int* a;int top;int capacity;
} Stack;void STInit(Stack* pst);
void STPush(Stack* pst, STDataType x);
// C++:面向对象
class Stack {
public:Stack() {}void push(int x){}
private:int* a_;int size_;int capcity_;
};
1、默认成员函数概述
- 在C++中,当定义一个类时,编译器会自动生成6个默认成员函数(C++11起):
- 默认构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符重载
- 移动构造函数(C++11)
- 移动赋值运算符(C++11)
- 这些函数在特定场景下会被隐式调用,理解它们的特性和行为对编写健壮的类至关重要。
2、构造函数与析构函数
2.1 默认构造函数
- 作用:初始化对象成员。
- 生成条件:当类中没有显示定义任何构造函数时。
- 特点:
- 对内置类型不做处理(不进行初始化)。
- 对自定义类型调用其默认构造函数。
- 示例:
class A {
public:int x; // 随机值std::string s; // 调用std::string类的默认构造函数
};int main() {A a;return 0;
}
[!WARNING]
当显示创建构造函数,编译器就不会生成默认构造函数。
class A {
public:A(int x, std::string s) {std::cout << "A" << std::endl;}int x;std::string s;
};int main() {A a;return 0;
}
2.2 析构函数
- 作用:释放对象资源。
- 生成条件:没有显示定义析构函数。
- 特点:
- 内置类型不做处理。
- 自定义类型调用其析构函数。
class A {
public:A() {std::cout << "A()" << std::endl;}~A() {std::cout << "~A()" << std::endl;}
private:int x;std::string s;
};int main() {A a; // A()// ~A()return 0;
}
[!CAUTION]
当类管理资源(动态内存、文件句柄等)时,必须手动定义。
class Stack { public:Stack(int n = 4) {a_ = (int*)malloc(sizeof(int) * n);top_ = 0;capacity_ = n;}~Stack() {free(a_);a_ = nullptr;top_ = capacity_ = 0;} private:int* a_;int capacity_;int top_; };
3、拷贝控制成员
3.1 拷贝构造函数
- 形式:
T(const T& val)
- 特点:
- 内置类型不做处理,直接进行值拷贝。
- 自定义类型调用其拷贝构造函数。
[!IMPORTANT]
如果不显示定义拷贝构造函数,编译器默认进行值拷贝。
class Stack { public:Stack(int n = 4) {a_ = (int*)malloc(sizeof(int) * n);top_ = 0;capacity_ = n;}~Stack() {free(a_);a_ = nullptr;top_ = capacity_ = 0;} private:int* a_;int capacity_;int top_; };int main() {Stack s1;Stack s2(s1);return 0; }
- 调试可观察到:
s1
对象和s2
对象内的数据数组具有相同的地址,表明是同一个。
3.2 赋值运算符重载
- 形式:
T& operator=(const T& val)
- 示例:
class Stack {
public:Stack(int n = 4) {a_ = (int*)malloc(sizeof(int) * n);top_ = 0;capacity_ = n;}Stack(const Stack &s) {a_ = (int*)malloc(sizeof(int) * capacity_);top_ = s.top_;capacity_ = s.capacity_;for (int i = 0; i < top_; ++i) {a_[i] = s.a_[i];}}Stack& operator=(const Stack &s) { // 赋值运算符重载if (this != &s) { // 杜绝自己赋值给自己free(a_); // 将原数组释放,防止内存泄漏a_ = (int*)malloc(sizeof(int) * capacity_);top_ = s.top_;capacity_ = s.capacity_;for (int i = 0; i < top_; ++i) {a_[i] = s.a_[i];}}return *this;}~Stack() {free(a_);a_ = nullptr;top_ = capacity_ = 0;}
private:int* a_;int capacity_;int top_;
};
4、移动语义(C++11)
4.1 移动构造函数
- 形式:
T(T&&)
。 - 关键:转移资源所有权而非拷贝。
- 标记:使用
noexcept
保证异常安全。
class Vector {
public:Vector(int n = 4) {data_ = new int[n];size_ = 0;capacity_ = n;}Vector(Vector&& v) noexcept: data_(v.data_), size_(v.size_), capacity_(v.capacity_) {v.data_ = nullptr;v.size_ = 0;v.capacity_ = 0;}~Vector() {delete[] data_;size_ = capacity_ = 0;}
private:int* data_;int size_;int capacity_;
};
4.2 移动赋值运算符
- 形式:
T& operator=(T&&)
。
Vector& operator=(Vector&& v) noexcept {if (this != &v) {data_ = v.data_;size_ = v.size_;capacity_ = v.capacity_;v.data_ = nullptr;v.size_ = 0;v.capacity_ = 0;}return *this;
}
- 生成规则:
- 没有自定义拷贝控制成员时。
- 类未声明移动操作。
- 析构函数未显示定义。
5、三五法则与最佳实践
5.1 三五法则(Rule of Three/Five)
- 需要自定义析构函数 ⇒ \Rightarrow ⇒必须处理拷贝(三法则)。
- C++11后扩展为五法则(包含移动操作)。
5.2 显式控制
=default
:显示要求编译器生成默认版本。=delete
:禁用特定成员函数。
5.3 建议
- 优先使用移动语义。
- 使用智能指针管理资源。
- 默认使用
=default
保持代码简洁。
6、总结
成员函数 | 默认行为 | 自定义场景 |
---|---|---|
默认构造函数 | 内置类型不做处理,自定义类型调用其默认构造函数 | 需要特定初始化逻辑 |
析构函数 | 内置类型不做处理,自定义类型调用其默认析构函数 | 管理资源释放 |
拷贝构造函数 | 浅拷贝 | 需要深拷贝或禁止拷贝 |
赋值运算符重载 | 浅拷贝 | 同拷贝构造函数 |
移动构造函数 | 转移资源(C++11) | 优化资源转移 |
移动赋值运算符 | 转移资源(C++11) | 同移动构造函数 |