C++ 各种构造函数详解

564 阅读5分钟

个人主页:二郎腿 (erlangtui.top)

一、默认构造函数

  • 默认构造函数(Default Constructor):默认构造函数没有参数,用于创建对象时不提供任何初始值。
  • 它可以使用默认值或者对成员变量执行适当的初始化操作。
  • 如果没有显式定义构造函数,则编译器会为类生成一个默认构造函数
// 默认构造函数
MyClass() : data(nullptr), size(0)
{
    name = "default construct";
    std::cout << name << std::endl;
}

二、带参构造函数

  • 带参数构造函数(Parameterized Constructor):带参数构造函数接受一个或多个参数,并使用这些参数来初始化对象的成员变量。
  • 它可以根据传入的参数执行特定的初始化操作,以定制化地创建对象。
// 带参构造函数
MyClass(int *newData, int newSize, std::string newName) : size(newSize)
{
    data = new int[size];
    for (int i = 0; i < size; i++)
    {
        data[i] = newData[i];
    }
    name = "paras construct";
    std::cout << name << std::endl;
}

三、拷贝构造函数

  • 拷贝构造函数(Copy Constructor):拷贝构造函数用于创建新对象,该对象是以现有对象作为参数进行复制而来的。
  • 它通过将一个对象的值复制到另一个新对象中来创建副本
  • 通常情况下,拷贝构造函数的参数是一个同类的引用或常量引用
  • 如果没有显式定义构造函数,则编译器会为类生成一个默认构造函数
// 拷贝构造函数
MyClass(const MyClass &other) : size(other.size)
{
    data = new int[size];
    for (int i = 0; i < size; i++)
    {
        data[i] = other.data[i];
    }
    name = "copy construct";
    std::cout << name << std::endl;
}

四、移动构造函数

  • 移动构造函数(Move Constructor):移动构造函数用于创建新对象,该对象获取了临时对象的资源所有权
  • 移动构造函数通过从源对象“移动”数据,而不是复制数据,来创建新对象
  • 移动构造函数通常通过右值引用作为参数来实现高效的资源转移。
// 移动构造函数
MyClass(MyClass &&other) noexcept : data(other.data), size(other.size)
{
    other.data = nullptr;
    other.size = 0;
    name = "move construct";
    std::cout << "move construct" << std::endl;
}

五、拷贝赋值函数

  • 拷贝赋值函数(Copy Assignment Operator):拷贝赋值函数用于将一个对象的值复制给另一个已存在的对象。
  • 通过重载赋值操作符(=),它允许对象之间进行赋值操作。
  • 通常情况下,拷贝赋值函数的参数是一个同类的引用或常量引用
  • 如果没有显式定义构造函数,则编译器会为类生成一个默认构造函数
// 拷贝赋值函数
MyClass &operator=(const MyClass &other)
{
    if (this != &other)
    {
        delete[] data;
        size = other.size;
        data = new int[size];
        for (int i = 0; i < size; i++)
        {
            data[i] = other.data[i];
        }
    }
    name = "copy assign";
    std::cout << "copy assign" << std::endl;
    return *this;
}

六、移动赋值函数

  • 移动赋值函数(Move Assignment Operator):移动赋值函数用于将一个对象的临时值转移到另一个已存在的对象中。
  • 通过重载赋值操作符(=)和右值引用参数,它允许对象之间进行资源的高效转移。
// 移动赋值函数
MyClass &operator=(MyClass &&other) noexcept
{
    if (this != &other)
    {
        delete[] data;
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
    }
    name = "move assign";
    std::cout << "move assign" << std::endl;
    return *this;
}

七、注意点

  • 执行拷贝构造函数还是执行拷贝赋值函数的区别点在于是否创建新的对象:
    • 如果需要创建新的对象,则是拷贝构造函数
    • 如果已经存在新的对象,则是拷贝赋值函数
  • 执行移动构造函数还是执行移动赋值函数的区别点同上;

八、完整示例

#include <iostream>

class MyClass
{
public:
    // 默认构造函数
    MyClass() : data(nullptr), size(0)
    {
        name = "default construct";
        std::cout << name << std::endl;
    }

    // 带参构造函数
    MyClass(int *newData, int newSize, std::string newName) : size(newSize)
    {
        data = new int[size];
        for (int i = 0; i < size; i++)
        {
            data[i] = newData[i];
        }
        name = "paras construct";
        std::cout << name << std::endl;
    }

    // 拷贝构造函数
    MyClass(const MyClass &other) : size(other.size)
    {
        data = new int[size];
        for (int i = 0; i < size; i++)
        {
            data[i] = other.data[i];
        }
        name = "copy construct";
        std::cout << name << std::endl;
    }

    // 移动构造函数
    MyClass(MyClass &&other) noexcept : data(other.data), size(other.size)
    {
        other.data = nullptr;
        other.size = 0;
        name = "move construct";
        std::cout << "move construct" << std::endl;
    }

    // 拷贝赋值函数
    MyClass &operator=(const MyClass &other)
    {
        if (this != &other)
        {
            delete[] data;
            size = other.size;
            data = new int[size];
            for (int i = 0; i < size; i++)
            {
                data[i] = other.data[i];
            }
        }
        name = "copy assign";
        std::cout << "copy assign" << std::endl;
        return *this;
    }

    // 移动赋值函数
    MyClass &operator=(MyClass &&other) noexcept
    {
        if (this != &other)
        {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        name = "move assign";
        std::cout << "move assign" << std::endl;
        return *this;
    }

    ~MyClass()
    {
        std::cout << std::boolalpha;
        std::cout << "~MyClass: " << name << "  data is null: " << (data == nullptr) << std::endl;
        delete[] data;
    }

private:
    int *data;
    int size;
    std::string name;
};

int main(int argc, char **argv)
{
    int data[3] = {1, 2, 3};
    // 执行默认构造函数
    MyClass x, y, z, a;
    // 执行带参构造函数
    MyClass b(data, 3, "b");
    // 执行拷贝构造函数
    MyClass c = a; // important
    // 执行拷贝构造函数
    MyClass d(a);
    // 执行移动构造函数
    MyClass e = std::move(x);

    std::cout << "-----------------" << std::endl;

    // 执行拷贝赋值函数
    y = b;
    // 执行移动赋值函数
    z = std::move(c);

    std::cout << "-----------------" << std::endl;

    return 0;
}

/*
output:

default construct
default construct
default construct
default construct
paras construct
copy construct
copy construct
move construct
-----------------
copy assign
move assign
-----------------
~MyClass: move construct  data is null: true
~MyClass: copy construct  data is null: false
~MyClass: copy construct  data is null: true
~MyClass: paras construct  data is null: false
~MyClass: default construct  data is null: true
~MyClass: move assign  data is null: false
~MyClass: copy assign  data is null: false
~MyClass: default construct  data is null: true

*/
  • 构造函数执行顺序:x、y、z、a、b、c、d、e;
  • 析构函数执行顺序相反,其中 y、z 分别执行了拷贝赋值和移动赋值,所以析构的时候打印出的 name 为 copy assign 和 move assign;