【C++】复制构造函数与析构函数

继续介绍两个 C++ 特有的,针对类/结构体的语法。

上一篇文章,我们建造的学生类如下:

struct Student
{
private:
    int age;
    string name;

public:
    Student(int age, string name) : age(age), name(name){};
    Student(int age)
    {
        this->age = age;
        this->name = "Student";
    }
    Student operator+(Student anoStu);
    void operator<<(ostream& os) const;
    void printStudent();

    friend void operator<<(ostream& os, Student& stu);
};

具体实现我就不写了。

析构函数

析构函数是类的成员函数之一,当类被销毁时会运行这个函数,语法如下:

~ClassName()

比如我们写个学生类的析构函数:

~Student()
{
    cout << "This student has been destroied" << endl;
}

这样,在 main 函数结束的时候,能看到析构函数被调用的打印
【C++】复制构造函数与析构函数_第1张图片
就目前来看,析构函数似乎… …没有作用?
你只要明白一点就是,C++里任何和现代编程语言不太一样的设定,大概率就是由指针导致的。
我们修改类的定义,在类中新增一个 char 指针,存储游戏名。出于测试的考虑,我们删除了多余的方法,但保留打印方法方便调试。这个类的声明如下:

struct Student
{
private:
    int age;
    string name;
    char *gameName;

public:
    Student(int age, string name, char *gameName) : age(age), name(name), gameName(gameName){};
    Student(int age)
    {
        this->age = age;
        this->name = "Student";
        
        this->gameName = new char[20];
        strcpy(this->gameName, "default game");
    }
    ~Student()
    {
        cout << "This student has been destroied" << endl;
    }

    friend ostream &operator<<(ostream &os, Student &stu);
};

事实上,如果仅仅是这样的话,分配给 gameName 的地址将不会被释放。因为局部变量本身只是一个指针,指针销毁但分配给变量的内存没有销毁。这种动态分配内存的操作,就需要在析构函数中使用 delete 释放掉内存。如下:

~Student()
{
    delete[] this->gameName;
    cout << "This student has been destroied" << endl;
}

注意使用的是 delete[] ,因为 gameName 是一个 char 数组。

现代操作系统的内存管理比较智能,而且内存还大,这样的内存泄露也许根本看不出来。但是在一些比如嵌入式的设备上,这种内存泄露会随频率的上升而越发明显。

复制构造函数

我们再看一个例子:
首先,我们修改了构造与析构函数,使得调用的时候可以打印出来析构的对象:

Student(int age, string name, char *gameName)
{
    this->age = age;
    this->name = name;
        
    auto len = strlen(gameName);
    this->gameName = new char[len + 1];
    strcpy(this->gameName,gameName);
}
~Student()
{
    cout << "This student "<< this->name << " with gameName = " << this->gameName <<" has been destroied" << endl;
    delete[] this->gameName;
}

接下来我们修改了部分 main 的代码:

void printStudentByValue(Student stu)
{
    cout << "== PASS BY VALUE ==" << endl;
    cout << stu;
}

int main()
{
    using std::endl;{
        Student wenZhi{18, "WenZhi", "Tricolor Lovestory1"};
        Student moXiaoju = Student(18, "MoXiaoju", "Tricolor Lovestory2");
        Student qiuCheng = {18, "QiuCheng", "Tricolor Lovestory3"};

        cout << "BASIC:" << wenZhi << endl;
        cout << "BASIC:" << moXiaoju << endl;
        cout << "BASIC:" << qiuCheng << endl;
        printStudentByValue(wenZhi);
        cout << "SECOND" << wenZhi << endl;
    }

    cout << "End of prog" << endl;
}

首先定义了一个函数,此函数按值传递 Student,但只是打印出来。
其次,我们定义了三个 Student ,按值传递其中一个后,再把它打印出来。

这段代码的运行结果有很多种,取决于你的编译器。在我的电脑上gcc version 9.2.0 (tdm64-1),程序将运行成功,无异常报错,但 printStudentByValue 后面的代码将不会运行且控制台返回 -1
【C++】复制构造函数与析构函数_第2张图片
事实上,只要把按值传递改成引用传递,这问题就解决了。

由于按值传递会调用 Student 的复制构造函数,而我们没有编写复制构造函数,所以C++会帮我们生成一个。而这个默认的复制构造,原理也很简单,将每个变量的值机械地复制一份

类似于:

Student(const Student& stu)
{
    this->age = stu.age;
    this->name = stu.name;
    this->gameName = stu.gameName;
}

如果你够细心,你会发现 gameName 的处理很不恰当,复制一份指针相当于让两个指针指向同一块内存,而按值传递创建的临时变量离开函数的栈会自动销毁,这意味着要调用析构函数,销毁指针指向的内存,于是,原来的 Student ,也就是被复制的 gameName 指向的内存也被销毁了。

我们可以显式提供一个复制构造函数,并处理下这段流程:

Student(const Student& stu)
{
    this->age = stu.age;
    this->name = stu.name;

    auto len = strlen(stu.gameName);
    this->gameName = new char[len + 1];
    strcpy(this->gameName,stu.gameName);
}

这样代码就运行正常了。

你可能感兴趣的:(c++)