【C++】构造函数与析构函数的使用与区别

在C++中,构造函数和析构函数是类的两个特殊成员函数,它们分别在对象创建时和销毁时自动调用。下面是它们的一些基本使用方法和区别:

构造函数

  • 定义 :构造函数是一种特殊的成员函数,它具有与类相同的名称,并且没有返回类型(甚至不是void)。当创建类的新对象时,会自动调用构造函数。
  • 作用:通常用来初始化对象的状态,即为对象的数据成员设置初始值或执行其他必要的设置。
  • 类型
    • 默认构造函数:不带任何参数的构造函数。
    • 参数化构造函数:带有参数的构造函数,允许用户传递参数以自定义对象的初始化。
    • 拷贝构造函数:用于使用同一类中的另一个对象来初始化新创建的对象。其形参通常是该类的一个引用。
cpp 复制代码
class MyClass {
public:
    // 默认构造函数
    MyClass() { /* 初始化代码 */ }

    // 参数化构造函数
    MyClass(int a, int b) { /* 使用a和b进行初始化 */ }

    // 拷贝构造函数
    MyClass(const MyClass &other) { /* 复制other的内容到当前对象 */ }
};

析构函数

  • 定义 :析构函数也是一个特殊的成员函数,它的名字是在类名前加上波浪号(~),并且同样没有返回类型。当对象的生命周期结束时(例如,对象离开作用域、被删除等),会自动调用析构函数。
  • 作用:主要用于清理工作,如释放对象创建时分配的资源(比如动态内存)。
cpp 复制代码
class MyClass {
public:
    ~MyClass() { /* 清理代码 */ }
};

主要区别

  1. 调用时机:构造函数在对象创建时立即调用;而析构函数则在对象即将被销毁之前调用。
  2. 用途:构造函数主要用于初始化对象;析构函数主要用于清理资源。
  3. 数量限制:一个类可以有多个构造函数(重载),但只能有一个析构函数(不允许重载)。
  4. 继承性:如果子类有自己的构造函数,则必须显式地调用基类的构造函数;而对于析构函数来说,即使子类没有定义自己的析构函数,基类的析构函数也会被自动调用。

执行顺序

在C++中,构造函数和析构函数的调用顺序是非常重要的,尤其是在处理继承关系时。此外,析构函数的作用是清理资源,因此它通常是必须执行的,以确保程序不会泄漏内存或其他资源。

构造函数调用顺序

当创建一个对象时(无论是通过直接实例化还是使用new运算符),构造函数按照以下顺序被调用:

  1. 基类构造函数:首先调用最顶层基类的构造函数。
  2. 成员变量初始化:接着按照成员变量在类中声明的顺序来初始化它们。
  3. 派生类构造函数:最后调用当前类的构造函数体。

例如,在以下代码中:

cpp 复制代码
class Base {
public:
    Base() { std::cout << "Base constructor\n"; }
};

class Member {
public:
    Member() { std::cout << "Member constructor\n"; }
};

class Derived : public Base {
    Member m;
public:
    Derived() { std::cout << "Derived constructor\n"; }
};

int main() {
    Derived d;  // 创建Derived对象
}

输出将是:

cpp 复制代码
Base constructor
Member constructor
Derived constructor

析构函数调用顺序

当对象销毁时(如离开作用域或通过delete操作符删除),析构函数的调用顺序与构造函数相反:

  1. 派生类析构函数:首先调用派生类的析构函数。
  2. 成员变量清理:接着按照成员变量声明的逆序进行清理。
  3. 基类析构函数:最后调用基类的析构函数。

对于上面的例子,如果我们在main函数末尾添加d对象的生命周期结束,则输出会是:

cpp 复制代码
Derived destructor
Member destructor
Base destructor

析构函数是否必须执行

  • 自动管理的对象:对于局部变量或全局变量等由编译器自动管理的对象,当这些对象离开其作用域时,析构函数会自动被调用。
  • 动态分配的对象 :对于通过new运算符动态分配的对象,只有当使用delete释放该对象时,才会调用析构函数。如果不释放这样的对象(即发生了内存泄漏),则析构函数不会被执行。

因此,为了确保资源得到正确释放,尤其是对于动态分配的对象,必须显式地调用delete来触发析构函数的执行。这也是为什么推荐使用智能指针(如std::unique_ptrstd::shared_ptr)的原因之一,因为它们能够自动管理内存并保证析构函数被适时调用。

相关推荐
永不停转8 分钟前
C++宏定义中可变参数列表__VA_ARGS__ 及 QT 提供的宏 QT_OVERLOADED_MACRO
c++·qt
程序员JerrySUN25 分钟前
驱动开发硬核特训 · Day 1
java·linux·运维·开发语言·c++·驱动开发
Vitalia38 分钟前
⭐算法OJ⭐数据流的中位数【最小堆】Find Median from Data Stream
数据结构·c++·算法·最小堆
Rainbow Sea1 小时前
自定义实现C++拓展pytorch功能
c++·pytorch·python
仟濹1 小时前
【C/C++】双指针与前缀和
c语言·c++·算法
我言秋日胜春朝★1 小时前
【C++11】特殊类的设计 && 单例模式 && 类型转换
开发语言·c++·单例模式
矛取矛求1 小时前
STL c++ list——模拟实现
c++
阿巴~阿巴~2 小时前
二分 —— 基本算法刷题路程
数据结构·c++·算法
徽京人2 小时前
第八届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组 购物单
c++·职场和发展·蓝桥杯
半桔2 小时前
哈希表(开散列)的实现
数据结构·c++·面试·散列表·哈希