c++类和对象(2)
1. 类的6个默认成员函数
在C++中,如果一个类没有显式定义某些成员函数,编译器会默认为这个类生成六个默认成员函数。以下是这六个默认成员函数:
默认构造函数(Default Constructor)
如果类没有定义任何构造函数,编译器会提供一个默认构造函数,这个默认构造函数是一个无参构造函数,它的作用是创建一个对象,并为对象的成员变量提供默认值。
析构函数(Destructor)
如果类没有定义析构函数,编译器会提供一个默认的析构函数,默认析构函数是一个空函数体,用于在对象生命周期结束时释放资源。
拷贝构造函数(Copy Constructor)
用于通过一个同类型的已存在对象来初始化一个正在创建的对象。默认的拷贝构造函数执行成员的逐位复制。
拷贝赋值运算符(Copy Assignment Operator)
用于将一个对象赋值给同类型的另一个已存在对象。默认的拷贝赋值运算符执行成员的逐位赋值。
移动构造函数(Move Constructor) (C++11及以后)
当需要通过一个临时对象来初始化同类型的另一个对象时,移动构造函数会被调用。默认的移动构造函数“窃取”临时对象的资源。
移动赋值运算符(Move Assignment Operator) (C++11及以后)
用于将一个临时对象赋值给同类型的另一个已存在对象。默认的移动赋值运算符也是“窃取”临时对象的资源。
2. 构造函数
构造函数是类的一个特殊成员函数,它的主要作用是在创建类的对象时初始化对象的成员变量。构造函数具有以下特点:
- 函数名称:构造函数的名称必须与类名相同。
- 参数:构造函数可以没有参数,也可以有多个参数,用于提供初始化成员变量的值。
- 返回类型:构造函数没有返回类型,即使是void也不可以。
- 作用:在对象创建时自动调用,用于初始化对象的成员变量。
默认构造函数
如果类没有定义任何构造函数,编译器会提供一个默认构造函数。如果定义了其他构造函数,但仍然需要一个无参的构造函数,则必须显式定义默认构造函数。
class MyClass {
public:
MyClass() {
// 默认构造函数的实现
}
};
参数化构造函数
允许在创建对象时传递参数,以初始化对象的成员变量。
class MyClass {
public:
int x;
MyClass(int val) : x(val) {
// 参数化构造函数的实现
}
};
转换构造函数
允许通过一个不同类型的值来创建对象。
class MyClass {
public:
MyClass(int val) {
// 转换构造函数的实现
}
};
初始化列表
构造函数可以使用初始化列表来初始化成员变量,这通常比在函数体内赋值更高效。
class MyClass {
public:
int x;
MyClass() : x(10) {
// 使用初始化列表
}
};
3. 析构函数
析构函数是类的一个特殊成员函数,它的作用是在对象生命周期结束时执行必要的清理工作。析构函数具有以下特点:
- 函数名称:析构函数的名称是在类名前加上波浪号(~)。
- 参数:析构函数没有参数,因此不能被重载。
- 返回类型:析构函数没有返回类型,即使是void也不可以。
- 作用:在对象被销毁时自动调用,用于释放对象所占用的资源,如动态分配的内存、打开的文件句柄、网络连接等。
规则
- 每个类只能有一个析构函数。
- 如果类没有显式定义析构函数,编译器会自动生成一个默认的析构函数,该默认析构函数是一个空函数体。
- 如果类中包含指向动态分配内存的指针成员,则通常需要定义一个自定义的析构函数来释放这些内存。
class MyClass {
public:
MyClass() {
// 构造函数的实现,可能包含动态内存分配
}
~MyClass() {
// 析构函数的实现,释放动态分配的内存
}
private:
int* data; // 指向动态分配内存的指针
};
int main() {
MyClass obj; // 创建对象,调用构造函数
// ... 使用对象 ...
return 0; // 程序结束,obj对象生命周期结束,调用析构函数
}
4. 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它用于创建一个新对象作为另一个同类对象的副本。拷贝构造函数具有以下特点:
- 函数名称:拷贝构造函数的名称与类名相同,且有一个参数,该参数是对类类型对象的引用。
- 参数:拷贝构造函数的参数必须是类类型的引用,通常使用常量引用,以避免修改原对象,并允许传递临时对象。
- 返回类型:拷贝构造函数没有返回类型,即使是void也不可以。
- 作用:当需要通过已存在的对象来初始化一个新对象时,拷贝构造函数会被调用。
class MyClass {
public:
MyClass(const MyClass& other) {
// 拷贝构造函数的实现
// 通常用于复制成员变量
}
// 其他成员函数和成员变量...
};
默认拷贝构造函数
如果类没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。默认拷贝构造函数执行成员的逐位复制(浅拷贝)。对于包含指针成员的类,默认拷贝构造函数可能会导致问题,因为它只会复制指针的值,而不是指针指向的数据。
自定义拷贝构造函数
在某些情况下,需要自定义拷贝构造函数来执行深拷贝,以确保每个对象都有自己独立的副本。
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 自定义拷贝构造函数
MyClass(const MyClass& other) : data(new int(*other.data)) {
// 执行深拷贝
}
~MyClass() {
delete data; // 释放动态分配的内存
}
};
5. 赋值运算符重载
在C++中,赋值运算符 =
可以被重载以定制对象之间的赋值行为。当你定义了一个类,并且希望对象能够使用 =
运算符进行赋值时,你可能需要重载赋值运算符。
-
参数类型:赋值运算符重载函数应该有一个参数,该参数是类类型的引用。通常使用常量引用,以允许传递临时对象,并防止在赋值过程中修改右操作数。
-
返回类型:赋值运算符重载函数应该返回一个指向当前对象的引用,通常是一个
*this
指针。 -
函数名称:赋值运算符重载的函数名是
operator=
。 -
成员函数:赋值运算符必须是类的成员函数。
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 赋值运算符重载
MyClass& operator=(const MyClass& other) {
if (this != &other) { // 避免自我赋值
delete data; // 删除旧值
data = new int(*other.data); // 深拷贝
}
return *this; // 返回当前对象的引用
}
~MyClass() {
delete data; // 析构函数中释放动态分配的内存
}
};
注意事项:
- 自我赋值检查:在赋值运算符的实现中,检查自我赋值是非常重要的,以避免在释放内存后使用悬空指针。
- 资源管理:如果类管理动态分配的资源,赋值运算符应该正确地管理这些资源,避免内存泄漏和悬挂指针。
- 返回值:赋值运算符应该返回当前对象的引用,这允许链式赋值,例如
a = b = c;
。
6. const成员函数
在C++中,const
成员函数是指那些承诺不修改调用对象的数据成员的函数。当你声明一个成员函数为const
时,你告诉编译器这个函数不会改变对象的任何成员变量(非静态成员变量)。这是通过在函数声明和定义后面加上const
关键字来实现的。
class MyClass {
public:
void normalFunction() {
// 可以修改成员变量
}
void constFunction() const {
// 不能修改成员变量
}
};
注意事项
- 在
const
成员函数内部,你不能修改任何非静态成员变量。 - 你可以修改静态成员变量和局部变量,因为这些变量不与对象的状态直接相关。
- 你可以在
const
成员函数内部调用其他非const
成员函数,但前提是这些函数不修改任何非静态成员变量。
7. 取地址及const取地址操作符重载
在C++中,你可以重载几种特殊的成员操作符,包括取地址操作符 &
和 const
取地址操作符 const &
。虽然这些操作符通常不需要重载,因为编译器默认提供的版本就已经足够使用,但在某些特殊情况下,你可能需要自定义它们的行为。
重载取地址操作符 &
class MyClass {
public:
// 重载取地址操作符
MyClass* operator&() {
// 返回当前对象的地址
return this;
}
};
重载const取地址操作符 const &
class MyClass {
public:
// 重载const取地址操作符
const MyClass* operator&() const {
// 返回当前对象的const地址
return this;
}
};
注意事项
- 重载取地址操作符通常不是一个好主意,因为这可能会导致混淆和意外的行为。通常,编译器提供的默认行为是正确的,即返回对象的实际地址。
- 如果你确实重载了这些操作符,它们必须返回指向类类型的指针。对于
const
版本,返回类型应该是指向const
类型的指针。 - 重载这些操作符的返回类型不能是引用类型,因为地址操作符的预期行为是返回一个指针。
- 重载这些操作符的目的是为了改变它们的行为,但通常不建议这样做,除非你有非常特殊的需求。