《C++探幽:构造函数、析构函数、拷贝构造函数详解》

《C++探幽:构造函数、析构函数详解》

文章目录

  • 《C++探幽:构造函数、析构函数详解》
    • 一、构造函数(Constructor function)
      • :red_circle:1.构造函数的特点
      • :red_circle:2.基本语法
      • :red_circle:3.初始化列表
      • :red_circle:4.构造函数重载
      • :red_circle:5.拷贝构造函数
      • :red_circle:6.构造函数的可访问性
    • 二、析构函数(Destructor)
      • :red_circle:1.析构函数的特点
      • :red_circle:2.析构函数的用法
      • :red_circle:3.析构函数的调用时机
      • :red_circle:4.默认析构函数
      • :red_circle:5.注意细节
        • **避免资源泄漏**
        • **避免多次释放**
        • **禁止对象拷贝**
        • **析构函数的顺序**

[作者的个人Gitee>](友人A (friend-a188881041351) - Gitee.com)

每日一言:“**存在是一场无尽的对话,我们既是提问者,也是答案。”


一、构造函数(Constructor function)

在C++中,构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的状态。它与类同名,没有返回值(包括void)。构造函数在对象生命周期的开始阶段被调用,确保对象在使用之前处于一个有效的状态。

1.构造函数的特点

  1. 函数名与类名相同
  2. 无返回值(不需要不能写void)。
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有显式定义的构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不会生成
  6. 无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。

2.基本语法

class ClassName {
public:
    ClassName();  // 默认构造函数
    ClassName(int param);  // 带参数的构造函数
};
  • 默认构造函数:没有参数的构造函数
  • 带参数的构造函数:用于初始化对象的成员变量。

3.初始化列表

  • 初始化列表用于在构造函数中初始化成员变量,尤其是那些没有默认构造函数的成员变量(如常量成员、引用成员等)。

  • 语法:

  • class MyClass {
    public:
        int a;
        const int b;
        MyClass(int x, int y) : a(x), b(y) { }  // 初始化列表
    };
    
  • 初始化列表的顺序是按照成员变量在类声明中的顺序,而不是初始化列表中写的顺序。因此,初始化列表的顺序最好与成员变量声明的顺序一致,以避免混淆。

4.构造函数重载

  • 类可以定义多个构造函数,只要其参数列表不同(参数类型、参数个数或参数顺序不同)。

  • 示例:

  • class MyClass {
    public:
        MyClass() { }  // 默认构造函数
        MyClass(int a) { }  // 带一个参数的构造函数
        MyClass(int a, int b) { }  // 带两个参数的构造函数
    };
    

5.拷贝构造函数

  • 拷贝构造函数是一种特殊的构造函数,编译器会自动生成一个默认的拷贝构造函数,他会逐成员复制对象的内容。

  • 注意如果类中包含指针或动态分配的资源,需要自定义拷贝构造函数以避免浅拷贝的问题(多个对象共享同一资源)。

  • 示例:

  • class MyClass {
    public:
        MyClass(const MyClass& other) {
            data = new int[10];
            std::copy(other.data, other.data + 10, data);
        }
    private:
        int* data;
    };
    

6.构造函数的可访问性

  • 构造函数可以是publicprotectedprivate
  • 如果构造函数是private,则无法直接通过new或对象声明来创建对象,通常用于实现单例模式或限制对象的创建方式。

二、析构函数(Destructor)

1.析构函数的特点

  1. 析构函数名是在类名前加上字符~。
  2. 无参数无返回值 (这里跟构造类似,也不需要加void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 跟构造函数类似,编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。
  6. 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。如果默认生成的析构就可以用,也就不需要显示写析构。但是有资源申请时,一定要自己写析构,否则会造成资源泄漏
  8. 一个局部域的多个对象,C++规定后定义的先析构。

2.析构函数的用法

  • class MyClass {
    public:
        ~MyClass() {  // 析构函数
            // 清理资源的代码
        }
    };
    
  • 析构函数可以被自动调用,也可以显式调用(但不推荐显式调用)。

3.析构函数的调用时机

  • 对象生命周期结束时
    • 对于局部对象(栈对象),当函数返回时,析构函数会被调用。
    • 对于动态分配的对象(堆对象),当使用delete时,析构函数会被调用。
    • 对于全局对象,程序结束时,析构函数会被调用。
    • 对于类的成员对象,当包含它的对象被析构时,成员对象的析构函数也会被调用。

4.默认析构函数

  • 如果用户没有定义析构函数,编译器会自动生成一个默认析构函数。
  • 默认析构函数是无操作的(即什么也不做),但它会递归地调用基类和成员对象的析构函数。

5.注意细节

  • 资源释放

    • 析构函数的主要职责是释放对象占用的资源,如动态分配的内存、文件句柄、网络连接等。
    • 如果类中没有动态资源,析构函数可以是空的,但仍然建议显式定义,以提高代码的可读性
  • 避免资源泄漏
    • 在析构函数中,确保释放所有动态分配的资源,避免内存泄漏。

    • 如果类中包含指针成员变量,需要在析构函数中使用deletedelete[]释放内存。

    • class MyClass {
      public:
          ~MyClass() {
              delete[] data;  // 释放动态分配的内存
          }
      private:
          int* data;
      };
      
  • 避免多次释放
    • 如果对象被多次析构(例如,同一个指针被多次delete),可能会导致程序崩溃。

    • 确保析构函数只被调用一次,或者在析构函数中将指针置为nullptr,以避免多次释放。

    • ~MyClass() {
          delete[] data;
          data = nullptr;  // 防止多次释放
      }
      
  • 禁止对象拷贝
    • 如果类不需要拷贝操作,可以通过将拷贝构造函数和拷贝赋值运算符声明为private,并定义一个空的析构函数。

    • 这样可以防止类对象被拷贝,同时确保析构函数的正确性。

    • class NonCopyable {
      public:
          NonCopyable() { }
          ~NonCopyable() { }
      private:
          NonCopyable(const NonCopyable&);
          NonCopyable& operator=(const NonCopyable&);
      };
      
  • 析构函数的顺序
    • 对象的析构顺序与构造顺序相反:
      • 成员变量按照声明的顺序被析构。
      • 基类的析构函数在派生类的析构函数之后被调用。

如有错误,恳请指正。

你可能感兴趣的:(c++,开发语言,笔记)