C++第三讲:类和对象(中)
这一章是C++ 面向对象最核心、最难、面试必考的内容。
一、6 个默认成员函数(必须背)
我们不写 ,编译器自动生成的函数,一共 6 个:
-
构造函数 ------ 初始化对象
-
析构函数 ------ 释放资源
-
拷贝构造 ------ 用一个对象初始化新对象
-
赋值重载 ------ 两个已存在对象互相赋值
-
普通取地址重载
-
const 取地址重载
重点:前 4 个必考,后两个几乎不用自己写。
二、构造函数(初始化)
1. 作用
代替Init(),对象创建时自动调用,完成初始化。
2. 特征
-
函数名 == 类名
-
无返回值(不写 void)
-
可重载
-
不写则编译器生成无参默认构造
-
无参、全缺省、编译器生成的,都叫默认构造,只能存在一个
3. 写法
cpp
class Date
{
public:
// 无参默认构造
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 带参构造
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 全缺省构造(和无参构造只能留一个)
// Date(int year=1, int month=1, int day=1) {}
private:
int _year;
int _month;
int _day;
};
4. 使用
cpp
Date d1; // 调用默认构造
Date d2(2025,5,1); // 调用带参构造
// Date d3(); ❌ 错误!这是函数声明,不是对象
5. 编译器默认构造行为
-
内置类型(int/char/ 指针):不初始化,值随机
-
自定义类型(类对象):调用它的默认构造
三、析构函数(资源清理)
1. 作用
代替Destroy(),对象生命周期结束时自动调用,释放资源。
2. 特征
-
名字:
~类名 -
无参、无返回值
-
一个类只能有一个,不能重载
-
不写则编译器自动生成
3. 什么时候必须自己写?
类里申请了动态内存(malloc/new),必须自己写析构释放!
比如 Stack、Queue、List。
4. 写法
cpp
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;
size_t _top;
size_t _capacity;
};
四、拷贝构造(重中之重!)
1. 作用
用一个已存在的对象 ,创建一个新的一模一样的对象。
2. 格式(必须背)
cpp
类名(const 类名& 另一个对象)
为什么必须用引用?
传值会引发无穷递归!
因为传参本身就要拷贝,拷贝又要调用拷贝构造。
3. 浅拷贝 vs 深拷贝
浅拷贝(编译器默认生成)
-
只拷贝值
-
指针拷贝后指向同一块内存
-
析构时会重复释放同一块地址 → 崩溃!
深拷贝(自己实现)
-
重新开一块同样大小的空间
-
把数据拷贝过去
-
两个对象指针指向不同内存,安全
4. Stack 深拷贝写法
cpp
Stack(const Stack& st)
{
// 1. 开新空间
_a = (int*)malloc(sizeof(int)*st._capacity);
// 2. 拷贝数据
memcpy(_a, st._a, sizeof(int)*st._top);
_top = st._top;
_capacity = st._capacity;
}
5. 调用拷贝构造的场景
-
Date d2(d1); -
Date d2 = d1; -
函数传值传参
-
函数传值返回
五、赋值运算符重载(= 号)
1. 作用
两个已经存在的对象之间互相赋值。
2. 和拷贝构造的区别
-
拷贝构造:创建新对象时初始化
-
赋值重载:两个老对象互相赋值
cpp
Date d1(2025,5,1);
Date d2 = d1; // 拷贝构造
Date d3;
d3 = d1; // 赋值重载
3. 必须重载为成员函数
格式:
cpp
Date& operator=(const Date& d)
{
if (this != &d) // 防止自己给自己赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; // 支持连续赋值 a=b=c
}
4. 浅拷贝问题
和拷贝构造一样:
-
有指针 / 资源 → 必须自己写深拷贝
-
无资源 → 用编译器默认的就行
六、运算符重载(通用规则)
1. 什么是运算符重载
让运算符支持自定义类型对象运算。
2. 规则
-
不能创造新运算符
-
优先级 / 结合性不变
-
作为成员函数时,参数个数 = 操作数 - 1(this 占了一个)
3. 不能重载的 5 个(必考)
cpp
. .* :: sizeof ?:
4. 前后置 ++ 区分
-
前置:
Date& operator++() -
后置:
Date operator++(int)(int 是占位参数)
5. <<和>> 必须重载为全局函数
因为cout要在左边,this会抢占左边位置。
通常配合友元使用。
七、const 成员函数(超级高频考点)
1. 写法
函数后面加 const:
cpp
void Print() const
2. 作用
修饰this 指针,让 this 变成:
cpp
const Date* const this
意思是:在函数里不能修改成员变量。
3. 使用规则
-
const 对象 只能调用 const 成员函数
-
非 const 对象 可以调用 const 函数(权限缩小)
八、取地址重载
-
普通:
Date* operator&() -
const:
const Date* operator&() const
99% 情况不用自己写,编译器默认生成的就够用。
九、一张表总结 4 大核心默认函数
| 函数 | 作用 | 何时需要自己实现 |
|---|---|---|
| 构造 | 初始化 | 想自定义初始值 |
| 析构 | 释放资源 | 有动态内存 |
| 拷贝构造 | 对象拷贝初始化 | 有动态内存(深拷贝) |
| 赋值重载 | 对象赋值 | 有动态内存(深拷贝) |
十、快速判断口诀(超好用)
-
没指针、没资源 → 全用编译器默认的,啥也不用写
-
有指针、有 malloc → 必须写:析构、拷贝构造、赋值重载
-
三法则:写其一,必写其二(析构 / 拷贝 / 赋值)