【C++基础】类的构造函数和析构函数

目录

构造函数(Constructor)

定义

种类

1.默认构造函数

2.带参数的构造函数

3.浅拷贝构造函数

4.深拷贝构造函数

深拷贝和浅拷贝的区别

5.移动构造函数

析构函数(Destructor)

构造函数与析构函数的调用时机

构造函数:

析构函数:

构造函数和析构函数的最佳实践

避免在析构函数中抛出异常:

使用 noexcept 标记移动构造函数:

正确使用深拷贝和浅拷贝:


如需咨询请添加个人微信: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;  // 释放内存
    }
};

构造函数与析构函数的调用时机

  1. 构造函数

    • 构造函数在对象创建时调用。可以通过直接定义对象或使用 new 操作符动态分配对象时触发。

    • 如果对象是局部变量,当程序进入定义对象的块时,构造函数被调用。

    • 如果对象是全局变量或静态成员变量,构造函数在程序启动或第一次访问变量时调用。

  2. 析构函数

    • 析构函数在对象的生命周期结束时调用。对于局部对象,当程序离开定义对象的块时,析构函数自动调用。

    • 对于动态分配的对象,当调用 delete 操作符时,析构函数被触发。

    • 如果对象是全局变量或静态成员变量,析构函数在程序结束时调用。

构造函数和析构函数的最佳实践

  1. 避免在析构函数中抛出异常

    • 析构函数通常用于清理资源,抛出异常可能导致未定义行为,特别是在栈展开(stack unwinding)过程中。

  2. 使用 noexcept 标记移动构造函数

    • 如果移动构造函数不会抛出异常,使用 noexcept 来标记,这样可以让标准库容器(如 std::vector)在需要重新分配内存时更高效地使用移动语义。

  3. 正确使用深拷贝和浅拷贝

    • 对于动态分配的资源,如果对象之间不应共享资源,应该使用深拷贝。

    • 在需要转移资源所有权的场景下,使用移动构造函数。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值