拷贝构造函数
拷贝构造函数是C++中的一个特殊构造函数,用于通过同类型的另一个对象来初始化当前对象。它主要用于对象的复制操作
应用场景
-
通过复制构造函数创建新对象:当一个对象被用作另一个对象的初始化时,拷贝构造函数会被调用。
MyClass obj1; MyClass obj2 = obj1; // 调用拷贝构造函数 MyClass obj3 (obj1); // 调用拷贝构造函数
-
将对象作为函数参数传递(按值传递时):当对象被传递给函数时,通常会调用拷贝构造函数。
void func(MyClass obj); MyClass obj1; func(obj1); // 调用拷贝构造函数,复制一份
-
返回对象时:当函数返回一个对象时,也会使用拷贝构造函数来创建返回值。
MyClass func() { MyClass obj; return obj; // 调用拷贝构造函数,复制一份 }
拷贝构造函数的定义
拷贝构造函数通常有以下形式:
ClassName(const ClassName& other);
其中:
ClassName
是类的名称。const ClassName& other
是常量引用参数,表示源对象。- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
- 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
为什么需要拷贝构造函数?
默认情况下,C++会为你生成一个拷贝构造函数,它执行逐成员复制(即按成员逐一赋值)。但是,默认拷贝构造函数可能不适用于某些场景,特别是当对象包含指向动态分配内存的指针时。如果你不自定义拷贝构造函数,编译器生成的拷贝构造函数将执行浅拷贝,即拷贝指针的值而不是拷贝它们指向的数据。这样可能导致“悬挂指针”和内存泄漏等问题。
浅拷贝:默认的拷贝构造函数会进行成员逐一复制。如果类包含指针成员,浅拷贝只会复制指针的值(即地址),而不会复制指针指向的内容。
深拷贝:在自定义的拷贝构造函数中,我们需要显式地为动态分配的内存进行复制,以确保每个对象拥有独立的资源。
浅拷贝会导致以下问题:
- 悬挂指针:多个对象共享同一块内存,当一个对象被销毁时,其他对象可能仍然持有指向已释放内存的指针。
- 内存泄漏:多个对象共享内存,但没有正确的释放机制,导致内存无法被回收。
- 双重释放:多个对象共享同一块内存,在销毁时可能尝试重复释放内存(即在第一次销毁后,可能重新分配使用),导致程序崩溃。
- 不正确的行为:多个对象共享同一内存,修改其中一个对象的值时,其他对象的值也会被意外修改,导致不期望的行为
编写拷贝构造函数的注意事项
-
避免自我赋值:在拷贝构造函数中,应该检查对象是否与自身进行赋值,以避免不必要的操作。
MyClass(const MyClass& other) { if (this != &other) { // 确保不是自我赋值 //... } }
-
处理资源管理:如果对象中包含动态分配的内存或资源,需要确保拷贝构造函数能够正确处理资源的分配和释放,防止资源泄露。
运算符重载
运算符重载是C++中的一项强大特性,它允许你为自定义类型定义或重新定义运算符的行为,使得你可以像使用内置类型一样使用自定义类型进行运算。运算符重载可以使代码更简洁、易读,同时也能提高代码的灵活性
运算符重载的基本语法
运算符重载的语法是通过成员函数或者非成员函数来实现的,具体形式如下:
-
成员函数形式:如果运算符的左操作数是类的对象,那么你可以在类内部定义一个成员函数来重载该运算符。相当于左操作数是
this
指向的调用函数的对象ReturnType operator 运算符(参数列表) { // 操作符重载的实现 }
-
非成员函数形式:如果左操作数不是类的对象(例如,如果左操作数是常量或是其他类的对象),可以通过非成员函数来重载运算符。
ReturnType operator 运算符(const 类类型& obj, 参数列表) { // 操作符重载的实现 }
示例:
class Date {
public:
Date(int year = 0,int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d) {
return _year == d._year && _month == d._month && _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
注意事项
- 不能通过链接其他符号来创建新的操作符,例如
operator@
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参
this
,限定为第一个形参
不能重载的操作符
成员访问运算符
.
(成员访问运算符):用于访问对象的成员,不能重载。->
(成员指针运算符):用于访问指针对象的成员,不能重载。
条件运算符(三目运算符)
?:
:这是条件运算符,它的行为已经定义好,不能被重载。
类型转换运算符
sizeof
:用于获取类型或对象大小的运算符,不能重载。
成员指针运算符
.*
和->*
:这两个运算符用于访问对象的成员指针,不能被重载。
作用域解析运算符
::
:用于限定作用域,不能被重载
const
成员函数
const
成员函数是 C++ 中的一种特殊成员函数,它承诺不会修改类的任何非const
成员变量。实际上,const
修饰的是该成员函数隐式的this
指针类型。使用const
成员函数的主要目的是保证对象的状态不会在该函数调用过程中发生改变。
声明和定义 const
成员函数
在 C++ 中,const
成员函数的声明和定义是通过在成员函数的末尾加上 const
来实现的。
class MyClass {
public:
MyClass(int value) : data(value) {}
// const 成员函数,保证不会修改对象的成员变量
void showData() const {
std::cout << "Data: " << data << std::endl;
}
private:
int data;
};
在这个例子中,showData()
是一个 const
成员函数,它不能修改 MyClass
对象的任何数据成员。
const
成员函数的隐式 this
指针
const
成员函数与普通成员函数的一个重要区别在于,它的this
指针是const
类型。这意味着,在const
成员函数中,你不能通过this
指针修改对象的非const
成员变量。
具体来说,const
成员函数的 this
指针的类型是 const MyClass*
,因此无法通过该指针来修改对象的状态。
class MyClass {
public:
MyClass(int value) : data(value) {}
// const 成员函数
void showData() const {
// data = 10; // 错误:不能修改 const 成员函数中的非 const 成员
}
private:
int data;
};
常见问题
-
const
对象可以调用非const
成员函数吗?
不可以,const
对象不能调用可能修改状态的非const
成员函数。 -
非
const
对象可以调用const
成员函数吗?
可以,非const
对象可以调用const
成员函数,因为const
成员函数保证不修改对象的状态。 -
const
成员函数内可以调用其它的非const
成员函数吗?
不可以,const
成员函数承诺不修改对象的状态,不能调用可能修改状态的非const
成员函数。 -
非
const
成员函数内可以调用其它的const
成员函数吗?
可以,非const
成员函数可以调用const
成员函数,因为const
成员函数不会修改对象状态。 -
什么时候会给成员函数加
const
?只要成员函数中不修改成员变量最好都加上
const