1. 类的默认成员函数

2. 构造函数
构造函数是特殊的成员函数,其核心任务是初始化对象,而非开辟空间。
构造函数的特点:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时自动调用。
- 构造函数可以重载。
- 未显式定义时,编译器自动生成一个无参默认构造函数;用户显式定义后,编译器不再生成。
- 默认构造函数:无参构造、全缺省构造、编译器生成的构造。三者有且只能有一个存在(无参与全缺省同时存在会导致调用歧义)。不传实参即可调用的构造就是默认构造。
- 编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决。
说明: C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型,
如:int/char/double/指针等,⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。
代码示例:
cpp
#include <iostream>
using namespace std;
class Date {
public:
// 1. 无参构造函数(默认构造)
Date() {
_year = 2000;
_month = 1;
_day = 1;
}
// 2. 带参构造函数
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
// 3. 全缺省构造函数(也是默认构造)
// 注意:不能与无参构造同时存在,会产生调用歧义
Date(int year = 2024, int month = 6, int day = 15) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1; // 调用无参构造
Date d2(2025, 8, 11); // 调用带参构造
d1.Print();
d2.Print();
return 0;
}
3. 析构函数
析构函数与构造函数功能相反,对象销毁时会⾃动调⽤析构函数,完成对象中资源的清理⼯作。
析构函数的特点:
- 函数名:类名前加
~。 - ⽆参数、⽆返回值。
- ⼀个类只能有⼀个析构函数。未显式定义时,系统⽣成默认析构函数。
- 对象⽣命周期结束时,⾃动调⽤析构函数。
- 默认析构函数:对内置类型成员不做处理,对⾃定义类型成员调⽤其析构函数。
- 还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类
型成员⽆论什么情况都会⾃动调⽤析构函数。 - 如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数。但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏。
- ⼀个局部域的多个对象,C++规定后定义的先析构。
代码示例:
cpp
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() {
_data = new int[10]; // 申请资源
cout << "构造函数:申请资源" << endl;
}
~MyClass() {
delete[] _data; // 释放资源
_data = nullptr;
cout << "析构函数:释放资源" << endl;
}
private:
int* _data;
};
int main() {
MyClass obj; // 构造
// 离开作用域,自动调用析构函数释放资源
return 0;
}
4. 拷⻉构造函数
如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤,且任何额外的参数都有默认值,则此构造函数是拷⻉构造函数。
拷⻉构造的特点:
- 拷⻉构造函数是构造函数的一个重载。
- 拷⻉构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错(会引发无穷递归调用)。可以有多个参数,但第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
- C++规定自定义类型对象进行拷贝必须调用拷贝构造,因此传值传参和传值返回都会调用拷贝构造。
- 若未显式定义拷贝构造,编译器会自动生成。自动生成的拷贝构造对内置类型成员完成值拷贝/浅拷贝,对自定义类型成员调用其拷贝构造。
- 技巧:若类显式实现了析构并释放资源,则通常需要显式写拷贝构造(深拷贝),否则不需要。
- 传值返回会产⽣⼀个临时对象调⽤拷贝构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。
代码示例------浅拷贝问题与深拷贝:
cpp
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int size = 10) {
_size = size;
_data = new int[_size]; // 申请资源
cout << "构造" << endl;
}
// 拷贝构造(深拷贝)------ 必须显式实现,否则默认浅拷贝会导致重复释放
MyClass(const MyClass& other) {
_size = other._size;
_data = new int[_size]; // 重新申请资源
for (int i = 0; i < _size; i++) {
_data[i] = other._data[i]; // 拷贝数据
}
cout << "深拷贝构造" << endl;
}
~MyClass() {
delete[] _data;
_data = nullptr;
cout << "析构" << endl;
}
private:
int* _data;
int _size;
};
int main() {
MyClass obj1(20);
MyClass obj2(obj1); // 调用拷贝构造(深拷贝)
return 0;
}
5. 赋值运算符重载
5.1 运算符重载
- C++允许为类类型对象重载运算符,指定其新含义。使用运算符时,必须调用对应的重载函数,否则编译报错。
- 运算符重载是特殊函数,名为 operator 后接运算符符号,具有返回类型、参数列表和函数体。
- 参数个数与运算对象数量相同:一元运算符一个参数,二元运算符两个参数(左侧对象传第一个参数,右侧对象传第二个)。
- 若为成员函数,第一个运算对象隐式传给 this 指针,因此参数比运算对象少一个。
- 重载后运算符的优先级和结合性与内置类型保持一致。
- 不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
.* :: sizeof ?: .注意以上5个运算符不能重载。(选择题⾥⾯常考,⼤家要记⼀下)- 重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y);
- ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义。
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
- 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
5.2 赋值运算符重载
赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象之间的赋值。注意与拷贝构造函数区分:拷贝构造是在创建新对象时进行初始化。
赋值运算符重载的特点:
- 必须重载为成员函数。参数建议写成 const 当前类类型&,避免传值拷贝。
- 建议返回 当前类类型&,支持连续赋值并提高效率。
- 未显式实现时,编译器生成默认赋值运算符重载:对内置类型值拷贝(浅拷贝),对自定义类型调用其赋值重载。
- 技巧:若类显式实现了析构函数释放资源,则通常也需要显式实现赋值运算符重载。
代码示例:
cpp
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int size = 10) {
_size = size;
_data = new int[_size];
}
// 拷贝构造(深拷贝)
MyClass(const MyClass& other) {
_size = other._size;
_data = new int[_size];
for (int i = 0; i < _size; i++) {
_data[i] = other._data[i];
}
}
// 赋值运算符重载
MyClass& operator=(const MyClass& other) {
// 1. 检查自赋值
if (this != &other) {
// 2. 释放原有资源
delete[] _data;
// 3. 深拷贝
_size = other._size;
_data = new int[_size];
for (int i = 0; i < _size; i++) {
_data[i] = other._data[i];
}
}
return *this; // 4. 返回*this,支持连续赋值
}
~MyClass() {
delete[] _data;
}
private:
int* _data;
int _size;
};
int main() {
MyClass obj1(20);
MyClass obj2(30);
obj2 = obj1; // 调用赋值运算符重载
return 0;
}
6.取地址运算符重载
6.1const成员函数
- 声明方式:const 修饰符放在成员函数参数列表之后。
- 核心作用:const 修饰隐含的 this 指针,禁止在函数内修改类的任何成员。
6.2 取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,通常编译器自动生成的版本已足够使用,无需显式实现。
代码示例:
cpp
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int val = 0) { _val = val; }
// 普通取地址运算符重载
MyClass* operator&() {
cout << "普通 operator& 调用" << endl;
return this; // 直接返回当前对象的地址
}
// const 取地址运算符重载
const MyClass* operator&() const {
cout << "const operator& 调用" << endl;
return this; // 返回 const 对象的地址
}
void Print() const { cout << "val = " << _val << endl; }
private:
int _val;
};
int main() {
MyClass obj(10);
MyClass* p1 = &obj; // 调用普通 operator&
cout << "普通指针地址:" << p1 << endl;
const MyClass obj2(20);
const MyClass* p2 = &obj2; // 调用 const operator&
cout << "const 指针地址:" << p2 << endl;
return 0;
}
注意: 通常编译器自动生成的取地址运算符重载已足够。 除非有特殊需求(如不希望外部获取对象地址),才需要手动重载并返回
nullptr或其他值。