目录
如需咨询请添加个人微信:a15135158368
欢迎叨扰,多多交流
构造函数和析构函数是类的特殊成员函数,用于管理对象的生命周期。
构造函数(Constructor)
定义
构造函数 是一个在对象创建时自动调用的函数,用于初始化对象的成员变量。
构造函数的名称必须与类名相同,且没有返回类型(不包括 void
)。
种类
构造函数可以是默认构造函数、带参数的构造函数、拷贝构造函数或移动构造函数。
1.默认构造函数
定义:不接受任何参数的构造函数。
如果你没有为类定义任何构造函数,编译器会自动生成一个默认构造函数。
作用:用于在对象创建时进行默认初始化。
#include <iostream>
#include <string>
class MyClass
{
public:
int id;
std::string name;
// 默认构造函数
MyClass()
{
id = 0;
name = "Unnamed";
std::cout << "Default constructor called" << std::endl;
}
// 方法:显示对象的内容
void display() const
{
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
};
int main()
{
// 创建对象时调用默认构造函数
MyClass obj;
// 显示对象的内容
obj.display();
return 0;
}
2.带参数的构造函数
定义:接受参数的构造函数,用于根据提供的值初始化对象的成员变量。
作用:允许在对象创建时传递参数来初始化对象。
#include <iostream>
#include <string>
class MyClass
{
public:
int x; // 用于存储一个整数
std::string name; // 用于存储一个名称
// 带参数的构造函数
//【 x(val), name(str)】成员变量将传入的参数直接赋值给x
MyClass(int val, const std::string& str) : x(val), name(str)
{
std::cout << "Parameterized constructor called" << std::endl;
}
// 显示对象的内容
void display() const
{
std::cout << "x: " << x << ", Name: " << name << std::endl;
}
};
int main()
{
// 创建对象时传递参数,调用带参数的构造函数
MyClass obj(42, "Example");
// 显示对象的内容
obj.display();
return 0;
}
//---------------------------------------------------------------------------
// 结果:
// Parameterized constructor called
// x: 42, Name: Example
//---------------------------------------------------------------------------
3.浅拷贝构造函数
仅仅复制对象的表面,就是对象变量的值。
当指针和引用类型的成员变量,仅复制值,而不复制实际内存内容.
#include <iostream>
class MyClass {
public:
int* data;
// 构造函数:为指针分配内存并初始化数据
MyClass(int value) {
data = new int(value);
std::cout << "构造函数:分配内存并设置值" << *data << std::endl;
}
// 浅拷贝构造函数
MyClass(const MyClass& obj) {
data = obj.data; // 仅复制指针的值,两个对象将共享同一块内存
std::cout << "浅拷贝构造函数:复制指针,值为 " << *data << std::endl;
}
// 显示数据
void display() const {
std::cout << "Value: " << *data << std::endl;
}
// 析构函数:释放内存
~MyClass() {
delete data;
std::cout << "Destructor: Released memory" << std::endl;
}
};
int main()
{
// 创建一个 MyClass 对象
MyClass obj1(42);
obj1.display();
// 使用浅拷贝构造函数创建 obj2
MyClass obj2 = obj1;
obj2.display();
// 修改 obj1 的数据
*obj1.data = 100;
std::cout << "After modifying obj1:" << std::endl;
// 显示 obj1 和 obj2 的数据
obj1.display();
obj2.display();
// 在main结束时, obj1 和 obj2 的析构函数会自动被调用
return 0;
}
/*
* 结果为:
* 构造函数:分配内存并设置值42
Value: 42
浅拷贝构造函数:复制指针,值为 42
Value: 42
After modifying obj1:
Value: 100
Value: 100
Destructor: Released memory
free(): double free detected in tcache 2
已放弃
*/
4.深拷贝构造函数
不仅复制对象的成员变量值,还复制指针指向的实际内存内容。
每个对象都有自己独立的内存副本,彼此之间互不干扰。
#include <iostream>
class MyClass {
public:
int* data;
// 带参数的构造函数
MyClass(int value)
{
data = new int(value); // 分配内存并初始化数据
std::cout << "构造函数:分配内存并设置值为 " << *data << std::endl;
}
// 深拷贝构造函数
MyClass(const MyClass& obj)
{
data = new int(*obj.data); // 分配新内存并复制内容
std::cout << "深拷贝构造函数:分配新内存,值为 " << *data << std::endl;
}
// 显示数据的方法
void display() const
{
std::cout << "值: " << *data << std::endl;
}
// 析构函数
~MyClass() {
delete data; // 释放内存
std::cout << "析构函数:释放内存" << std::endl;
}
};
int main() {
// 创建一个 MyClass 对象
MyClass obj1(42);
obj1.display();
// 使用深拷贝构造函数创建 obj2
MyClass obj2 = obj1;
obj2.display();
// 修改 obj1 的数据
*obj1.data = 100;
std::cout << "修改 obj1 的数据后:" << std::endl;
// 显示 obj1 和 obj2 的数据
obj1.display();
obj2.display();
// 在main结束时, obj1 和 obj2 的析构函数会自动被调用
return 0;
}
/*结果:
* 构造函数:分配内存并设置值为 42
值: 42
深拷贝构造函数:分配新内存,值为 42
值: 42
修改 obj1 的数据后:
值: 100
值: 42
析构函数:释放内存
析构函数:释放内存
*/
深拷贝和浅拷贝的区别
内存管理:
-
浅拷贝:多个对象共享相同的内存地址。可能会导致悬空指针、重复释放内存等问题。
-
深拷贝:每个对象有自己独立的内存,操作更加安全,避免了上述问题。
性能:
-
浅拷贝:执行速度快,因为它仅复制指针值,不涉及额外的内存分配。
-
深拷贝:相对较慢,因为需要分配新内存并复制内容。
应用场景:
-
浅拷贝:适用于无需独立内存的简单对象拷贝场景。
-
深拷贝:适用于需要独立的内存副本、避免数据共享问题的场景。
赋值:
-
浅拷贝
通过直接复制对象的成员变量来实现。
当一个对象被复制到另一个对象时,如果该对象包含指针或引用类型的成员变量,浅拷贝会直接复制指针的地址,而不是指针所指向的数据。这样,两个对象的相应成员变量指向同一个内存位置。
class MyClass
{
public:
int* data;
MyClass(int value)
{
data = new int(value);
}
// 浅拷贝构造函数
MyClass(const MyClass& obj)
{
data = obj.data; // 仅复制指针的值
}
};
// 浅拷贝示例
MyClass obj1(10);
MyClass obj2 = obj1; // 浅拷贝,obj2.data 和 obj1.data 指向相同的内存位置
-
深拷贝:
深拷贝在赋值时,不仅复制指针的值,还会为指针指向的数据分配新的内存空间,并复制数据内容。
这意味着两个对象的相应成员变量指向不同的内存位置,彼此独立。
class MyClass
{
public:
int* data;
MyClass(int value)
{
data = new int(value);
}
// 深拷贝构造函数
MyClass(const MyClass& obj)
{
data = new int(*obj.data); // 分配新内存并复制内容
}
~MyClass()
{
delete data; // 释放内存
}
};
// 深拷贝示例
MyClass obj1(10);
MyClass obj2 = obj1; // 深拷贝,obj2.data 是一个新的内存位置,内容与 obj1.data 相同
5.移动构造函数
定义:用于从另一个临时对象“移动”资源到新对象,而不是复制。这通常涉及指针的转移而非深拷贝。
作用:提高程序效率,避免不必要的深拷贝,特别是在处理动态内存时。
#include <iostream>
class MyClass
{
public:
int* data;
// 带参数的构造函数
MyClass(int value)
{
data = new int(value); // 分配内存并初始化数据
std::cout << "构造函数:分配内存并设置值为 " << *data << std::endl;
}
// 移动构造函数
//noexcept 关键字:表明此函数不会抛出任何异常
//&&:意味着它可以绑定到一个即将被销毁的对象
MyClass(MyClass&& obj) noexcept : data(obj.data)
{
obj.data = nullptr; // 将原对象的指针置空,避免资源重复释放
std::cout << "移动构造函数:转移内存资源,原对象的指针已置空" << std::endl;
}
// 显示数据的方法
void display() const
{
if (data)
{
std::cout << "值: " << *data << std::endl;
}
else
{
std::cout << "数据为空(已被移动)" << std::endl;
}
}
// 析构函数
~MyClass()
{
if (data)
{
delete data; // 释放内存
std::cout << "析构函数:释放内存" << std::endl;
}
else
{
std::cout << "析构函数:内存已被转移,无需释放" << std::endl;
}
}
};
int main() {
// 创建一个 MyClass 对象
MyClass obj1(42);
obj1.display();
// 使用移动构造函数创建 obj2,将 obj1 的资源转移给 obj2
MyClass obj2 = std::move(obj1);
obj2.display();
// 再次尝试显示 obj1 的数据
obj1.display();
// 在 main 结束时,obj2 的析构函数会被调用
return 0;
}
/*
* 构造函数:分配内存并设置值为 42
值: 42
移动构造函数:转移内存资源,原对象的指针已置空
值: 42
数据为空(已被移动)
析构函数:释放内存
析构函数:内存已被转移,无需释放
*/
析构函数(Destructor)
析构函数 是在对象生命周期结束时自动调用的函数,用于清理资源。
析构函数的名称必须与类名相同,前面加上 ~
,且不接受任何参数,也没有返回类型。
定义和作用
-
定义:析构函数的名称是
~ClassName()
,不接受参数,没有返回类型。 -
作用:用于释放对象在其生命周期内分配的资源(如内存、文件句柄等),防止资源泄漏。
class MyClass {
public:
int* data;
MyClass(int val) : data(new int(val)) { // 动态分配内存
}
~MyClass() {
delete data; // 释放内存
}
};
构造函数与析构函数的调用时机
-
构造函数:
-
构造函数在对象创建时调用。可以通过直接定义对象或使用
new
操作符动态分配对象时触发。 -
如果对象是局部变量,当程序进入定义对象的块时,构造函数被调用。
-
如果对象是全局变量或静态成员变量,构造函数在程序启动或第一次访问变量时调用。
-
-
析构函数:
-
析构函数在对象的生命周期结束时调用。对于局部对象,当程序离开定义对象的块时,析构函数自动调用。
-
对于动态分配的对象,当调用
delete
操作符时,析构函数被触发。 -
如果对象是全局变量或静态成员变量,析构函数在程序结束时调用。
-
构造函数和析构函数的最佳实践
-
避免在析构函数中抛出异常:
-
析构函数通常用于清理资源,抛出异常可能导致未定义行为,特别是在栈展开(stack unwinding)过程中。
-
-
使用
noexcept
标记移动构造函数:-
如果移动构造函数不会抛出异常,使用
noexcept
来标记,这样可以让标准库容器(如std::vector
)在需要重新分配内存时更高效地使用移动语义。
-
-
正确使用深拷贝和浅拷贝:
-
对于动态分配的资源,如果对象之间不应共享资源,应该使用深拷贝。
-
在需要转移资源所有权的场景下,使用移动构造函数。
-