这篇博客会帮你把图里的知识点梳理清楚,从拷贝构造到深浅拷贝,再到运算符重载,一次性吃透!
一、6 个默认成员函数总览
C++ 中,一个类如果不写任何成员函数,编译器会自动生成 6 个默认成员函数:
-
构造函数:初始化对象
-
析构函数:清理资源
-
拷贝构造函数:用同类对象初始化新对象
-
赋值运算符重载:对象之间赋值
-
取地址重载:普通对象取地址(很少手动实现)
-
const 取地址重载:const 对象取地址(很少手动实现)
二、拷贝构造函数:从定义到底层原理
1. 定义与特点
-
定义:构造函数的第一个参数是自身类型的引用,且额外参数都有默认值,就是拷贝构造函数。它是一种特殊的构造函数。
-
特点:
-
必须是构造函数的重载,参数必须是自身类型的引用(不能传值,否则会无限递归调用)。
-
C++ 规定:自定义类型的传值传参、传值返回,都会调用拷贝构造。
-
若未显式实现,编译器会自动生成默认拷贝构造:
-
内置类型:直接按字节浅拷贝。
-
自定义类型成员:调用其拷贝构造函数。
-
-
2. 为什么参数必须是引用?
如果参数是值传递:
Date(Date d) { /* ... */ } // 错误写法
调用时 Date d2(d1); 会先调用拷贝构造,而值传递又会再次调用拷贝构造,形成无限递归,直接导致栈溢出。
三、深浅拷贝:你必须踩过的坑
1. 浅拷贝(默认拷贝构造的行为)
编译器自动生成的拷贝构造,只会按字节复制成员变量:
-
对于
Date类这种无资源管理的类,浅拷贝完全够用。 -
对于
Stack这种动态申请内存的类,浅拷贝会导致严重问题:-
两个对象的
_a指向同一块内存。 -
修改一个对象的数据,另一个对象也会被修改。
-
析构时,同一块内存会被释放两次,程序直接崩溃。
-
2. 深拷贝(手动实现的拷贝构造)
为每个对象独立申请资源,复制数据到新空间,让两个对象的资源完全独立:
Stack(const Stack& st)
{
_a = new int[st._capacity]; // 申请新内存
memcpy(_a, st._a, sizeof(int) * st._top); // 复制数据
_top = st._top;
_capacity = st._capacity;
}
- 这样两个对象的
_a指向不同的内存,修改互不影响,析构也不会重复释放。
3. 什么时候需要手动实现拷贝构造?
当类中存在需要手动管理的资源(如动态内存、文件句柄、网络连接)时,必须手动实现深拷贝。如果类的成员都是内置类型或无资源管理的自定义类型,默认拷贝构造就足够了。
四、传值返回与引用返回的区别
1. 传值返回
函数返回时会生成一个临时对象,调用拷贝构造。
Stack f2()
{
Stack st;
st.Push(1);
return st; // 调用拷贝构造生成临时对象
}
- 临时对象会在表达式结束后销毁,调用
f2().Top()是安全的。
2. 引用返回
直接返回对象的别名,不生成临时对象。
-
若返回的是函数局部对象的引用,会导致野引用:函数结束后局部对象已销毁,引用指向无效内存。
-
引用返回的前提:返回的对象在函数结束后依然存在(如成员变量、全局变量)。
五、运算符重载:让自定义类型像内置类型一样工作
1. 核心规则
-
运算符重载是特殊的函数,格式为
operator+运算符。 -
不能改变运算符的操作数个数、优先级和结合性。
-
不能创建新运算符,也不能重载
..*::sizeof?:这 5 个运算符。 -
重载的运算符必须至少有一个自定义类型参数,不能重载内置类型的运算符(如
int+int)。
2. 成员函数 vs 全局函数
-
成员函数重载 :第一个参数是隐藏的
this指针,操作数个数比运算符少 1 个(如二元运算符作为成员函数时,参数只有 1 个)。 -
全局函数重载:参数个数和运算符操作数个数一致,通常需要声明为友元,才能访问类的私有成员。
3. 示例:Date 类的 operator+ 重载
实现 Date + int,支持日期加天数:
Date Date::operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._month = 1;
tmp._year++;
}
}
return tmp;
}
-
实现时要处理月份进位、年份进位,确保日期合法。
-
支持链式调用,也可以重载
operator+=实现更高效的原地修改。
六、写在最后:拷贝构造与运算符重载的核心要点
-
拷贝构造:引用传参是必须的,深拷贝只在有资源管理时才需要。
-
深浅拷贝:本质区别是是否复制资源本身,而不是仅复制资源的地址。
-
运算符重载:让自定义类型更易用,但不要滥用,保持语义清晰。
-
传值返回会生成临时对象,引用返回要警惕野引用问题。
这些知识点是 C++ 面向对象的核心,理解它们才能写出安全、高效的自定义类!