拷贝与控制
拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,
则此构造函数是拷贝构造函数
合成拷贝构造函数
当你没有显式定义拷贝构造函数时,编译器会自动创建一个默认的合成拷贝构造函数。合成拷贝构造函数用于创建一个对象,该对象与给定的对象具有相同的成员变量值。
合成拷贝构造函数使用了另一个同类型的对象作为参数,通过将该对象的成员变量值复制到新创建的对象中来完成拷贝。它通常用于对象的初始化、对象作为函数参数传递、对象作为函数返回值等场景。
#include <iostream>
class MyClass {
public:
int value;
// 合成拷贝构造函数
// 其它成员函数和数据成员
};
int main() {
MyClass obj1;
obj1.value = 10;
MyClass obj2(obj1); // 使用合成拷贝构造函数创建obj2,并将obj1的值复制给obj2
std::cout << obj2.value << std::endl; // 输出 10
return 0;
}
如果数组元素时类类型,则使用元素的拷贝构造函数来进行拷贝。
拷贝初始化
拷贝初始化的语法形式有两种:
- 使用等号(=)进行拷贝初始化:
ClassName obj1 = obj2;
- 拷贝初始化
将对象作为函数参数进行拷贝初始化:
void func(ClassName obj);
下面是一个示例,展示了拷贝初始化的用法:
#include <iostream>
class MyClass {
public:
int value;
MyClass(int val) : value(val) {}
// 拷贝构造函数
MyClass(const MyClass& other) : value(other.value) {}
// 其它成员函数和数据成员
};
void func(MyClass obj) {
std::cout << "Value inside func: " << obj.value << std::endl;
}
int main() {
MyClass obj1(10);
MyClass obj2 = obj1; // 拷贝初始化
std::cout << "Value of obj2: " << obj2.value << std::endl;
func(obj1); // 将对象作为函数参数进行拷贝初始化
return 0;
}
参数和返回值
在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。当一个函数具有非引用的返回类型时,
返回值会被用来初始化调用方的结果。
拷贝初始化的限制
C++11中对拷贝初始化进行了限制,具体如下:
-
如果被拷贝的对象是一个右值,那么拷贝初始化是允许的,也就是说,可以把一个右值赋值给一个左值。
-
如果被拷贝的对象是一个左值,那么拷贝初始化只有在以下情况下才是允许的:
a. 左值的类型和右值的类型相同。
b. 左值的类型可以从右值的类型中隐式转换得到。
c. 左值的类型具有移动构造函数,且被拷贝的对象是一个将要被销毁的临时对象。
如果不满足上述任何一种情况,编译器将会报错。
vector<int>v1(10);//正确 直接初始化 vector<int>v2=10;//错误 接受大小参数的构造函数是explicit的 void f(vector<int>);// f的参数进行拷贝初始化 f(10);//错误 不能用一个explicit的构造函数拷贝一个实参 f(vector<int>(10));// 正确 从一个int 直接构造一个临时vector
拷贝赋值运算符
重载赋值运算符是指自定义一个类对象如何进行赋值操作。在C++中,赋值运算符通常写成类似于“operator=()”的形式。具体实现如下:
class MyClass {
public:
MyClass& operator=(const MyClass& other) {
// 检查自我赋值
if (this == &other) {
return *this;
}
// 进行赋值操作
// ...
return *this;
}
};
在上面的代码中,重载了一个“operator=()”函数,它接受一个类型为MyClass的常引用参数other,在函数中,我们首先检查了自我赋值的情况(即,检查this指针是否等于&other),如果是则直接返回自身指针;否则进行赋值操作,并返回自身指针。
合成拷贝赋值运算符
如果一个类没有自定义赋值运算符,那么编译器会自动为其合成一个默认的赋值运算符。它执行的操作是逐个对数据成员进行赋值操作,例如:
class MyClass {
public:
int x;
int y;
};
MyClass a = {1, 2};
MyClass b = a; // 默认的赋值运算符将a的值逐个赋给b
在上面的代码中,b将会被赋值为{x=1, y=2},与a的值相同。
需要注意的是,C++合成的赋值运算符只是对数据成员进行逐个赋值操作,不会进行任何动态内存分配或资源管理操作。如果一个类需要进行动态内存分配或资源管理,那么就需要自定义赋值运算符来确保正确的释放和分配资源。
析构函数
析构函数是一种特殊的成员函数,用于在对象生命周期结束时释放对象所占用的资源。析构函数的名称与类名相同,前面加上一个波浪号(~)。
在C++中,析构函数的作用是释放对象所占用的内存,销毁对象的成员变量,关闭对象所打开的文件等。
析构函数的语法如下:
class MyClass {
public:
// 构造函数
MyClass();
// 析构函数
~MyClass();
};
MyClass::MyClass() {
// 构造函数的实现
}
MyClass::~MyClass() {
// 析构函数的实现
}
在上面的代码中,我们定义了一个名为MyClass的类,它包括一个构造函数和一个析构函数。析构函数使用了波浪号(~)符号来标识,它没有参数和返回值。
需要注意的是,当一个对象被销毁时,析构函数会自动被调用。因此,我们可以在析构函数中释放对象所占用的资源,确保程序的正确性。同时,我们也应该避免在析构函数中抛出异常,因为这可能会导致程序崩溃。
在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构
函数是隐式的。成员销毁时发生什么完全依赖于成语的类型。隐式销毁一个内置指针类型的
成员不会delete它所指向的对象。
什么时候会调用析构函数
无论何时一个对象被销毁,就会自动调用其析构函数
1、变量在离开其作用域时被销毁
2、当一个对象被销毁时,其成员被销毁
3、容器被销毁时,其元素被销毁
4、对于动态分配的对象,当对指向它的指针应用delete运算符被销毁
5、对于临时对象,当创建它的完整表达式结束时被销毁