首页 > 编程语言 >【C++基础】类的构造函数和析构函数

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

时间:2024-08-23 19:56:42浏览次数:11  
标签:int C++ 内存 MyClass 拷贝 和析构 data 构造函数

目录

构造函数(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. 正确使用深拷贝和浅拷贝

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

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

标签:int,C++,内存,MyClass,拷贝,和析构,data,构造函数
From: https://blog.csdn.net/m0_63956046/article/details/141401542

相关文章

  • 【C++】12.智能指针
    在上一篇博客【C++】11.异常中我们知道有些时候会造成内存空间的未释放从而导致内存泄漏,因此本篇博客的内容就是如何减少内存泄漏——智能指针。一、RAIIRAII(ResourceAcquisitionIsInitialization)是一种利用对象生命周期来控制程序资源的简单技术,因此又被称为资源获取......
  • C++ 使用 using 声明语句放开基类的重载函数
    #include<functional>#include<string>#include<iostream>#include<vector>#include<memory>#include<set>#include<map>usingnamespacestd;namespace{classAnimal{public:stringGe......
  • C++ STL源码个人学习与分析记录 ——空间配置器(allocator)
    STL源码个人学习与分析记录——空间配置器(allocator)1.为什么需要空间配置器?2.SGI-STL空间配置器的实现2.1一级空间配置器:malloc_alloc_template2.2二级空间配置器:default_alloc_template2.2.1.内存池技术2.2.2.自由链表(free-list)2.2.3Union2.3二级空间配置器的......
  • C++ STL源码个人学习与分析记录 ——Construct()与Destroy()
    STL源码个人学习与分析记录——Construct()与destroy()1.目前所使用的编译器1.1编译器:MinGWVersion:13.2.01.2MinGW的主要组件1.3写文初衷2.构造与析构工具:Construct()与Destory()函数的定义2.1Construct()函数的定义2.1.1“__cplusplus”的含义2.1.2编译组态2.1.3(void......
  • C++(time_t)
    目录1.数据类型:2.使用场景:3.常见函数:4.时间的存储和表示:5.示例:6.注意事项:在C++中,time_t是一种数据类型,用于存储日历时间。它定义在<ctime>头文件中。time_t本质上是一个算术类型,通常是整数或浮点数,用于表示从标准纪元(通常是1970年1月1日00:00:00UTC,称为“Unixepo......
  • 小白学习c++{复习篇}P11【2066】买图书(c++)详细讲解
    目录EXTRA##PT1-代码呈现awaPT2-问题填空qwqPT3-课后小结......
  • C++的动态数组vector番外之capacity
    今日诗词:爱他明月好,憔悴也相关。西风多少恨,吹不散眉弯。                    ——《临江仙·寒柳》【清】纳兰容若目录引言正文string中的和vector中的capacity有什么区别 vector扩容时内存分配的策略是什么?capacity在vect......
  • PHP8面向对象快速入门二 构造函数 析构函数 静态变量 静态方法
    在PHP中,构造函数是一个特殊的方法,用于在创建对象时初始化对象的状态。构造函数在对象实例化时自动调用,以设置初始值或执行必要的准备工作。它可以接受参数,用于初始化对象的属性。构造函数的特点自动调用:构造函数在创建对象时自动调用。你不需要显式调用构造函数,它会在实例......
  • 在C#中调用C++dll
        一、C++函数中的double**参数C++DLL中的接口如下:intgray2energy(double**data,constintlength,constdoublegamma);在C#中调用C++:方式1,通过指针的方式在C#也用double**对应C++中的double**[DllImport("xxxx.dll",CallingConvention=CallingConven......
  • 小朋友学C++-题集
    小朋友学C++参考c语言基础啊哈C语言书籍算法图解-python.pdfc语言教程在线C++/C/python编译器C语言基础==========================C语言精读100例!!!!C语言实验指导与习题解答小项目学习扫雷小游戏贪吃蛇小游戏学生成绩管理系统图书管理系统小说分析软件第一章,与......