美文网首页程序员
动态内存和类

动态内存和类

作者: 骑猪满天飞 | 来源:发表于2020-12-16 10:44 被阅读0次

析构函数

如果类对象成员指向一块new分配的内存,则需要编写一个析构函数来释放内存。当删除类对象时,C++会释放对象本身占用的内存,但并不能自动释放对象成员所指向的内存,所以需要编写相应的析构函数来释放对象成员所指的内存。

例如以下类声明:

class Cowboy
{
private:
    char* name;
public:
    Cow();
    Cow(const char* nm, double wt);
    ~Cow();
    
};

若构造函数中为name分配了内存,析构函数需要释放name所指的内存。

复制构造函数

下面用一个存在问题的类来说明复制构造函数的作用。

假设存在如下类:

class Cowboy
{
private:
    static int num_cows;
    char* name;
    double weight;

public:
    Cow();
    Cow(const char* nm, double wt);
    ~Cow();
    void showCow();
};

其中num_cows为静态类成员,它的特点是无论创建了多少对象,程序都只创建一个静态类变量副本,所以可以用它来记录创建的类对象数量。

类方法实现如下:

#include <cstring>
#include <iostream>
#include "Cow.h"
using namespace std;

int Cow::num_cows = 0;

Cow::Cow() {
    name = nullptr;
    weight = 0;
    num_cows++;
    cout << num_cows << ": \"" << name << "\" created" << endl;
}

Cow::Cow(const char* nm, double wt) {
    int len;
    
    len = strlen(nm);
    name = new char[len + 1];
    strcpy_s(name,len+1, nm);
    weight = wt;
    num_cows++;
    cout << num_cows << ": \"" << name << "\" created" << endl;
}

void Cow::showCow() {
    cout << "Cow name: " << name << ", Cow weight: " << weight << endl;
}

Cow::~Cow() {
    cout << "\"" << name << "\" deleted ";
    --num_cows;
    cout << num_cows << " left" << endl;
    delete[] name;
}

void call1(Cow& c) {
    cout << "Cow passed by reference:" << endl;
    c.showCow();
}
void call2(Cow c) {
    cout << "Cow passed by value:" << endl;
    c.showCow();
}

call1和call2为两个测试函数,call1使用引用传参调用类对象,call2使用值传递。

现在让我们来测试这个类:

void CowTest() {
    
    Cow Bob("Bob",  120);
    Cow Alice("Alice", 150);
    Cow Tom("Tom",  120);

    cout << "Bob: ";
    Bob.showCow();
    cout << "Alice: ";
    Alice.showCow();
    cout << "Tom: ";
    Tom.showCow();

    cout << endl;
    call1(Bob);
    cout << "Bob: ";
    Bob.showCow();

    cout << endl;
    call2(Alice);
    cout << "Alice: ";
    Alice.showCow();

    cout << endl;
    cout << "Initialize one object to another" << endl;
    Cow Aim = Tom;
    cout << "Aim: ";
    Aim.showCow();

    cout << endl;
    cout << "Assign one object to another" << endl;
    Cow Wu;
    Wu = Bob;
    cout << "Wu: ";
    Wu.showCow();
}
error.png

我们可以发现明显的两个问题:

  • 在创建Aim时,没有调用我们编写的构造函数和默认构造函数。
  • 当调用call2函数时出现了一次未知的析构函数调用,使得再次访问Alice时出现了乱码。

出现问题1的原因:

Cow Aim = Tom;
上述句将转化为:
Cow Aim = Cow(Tom);
对应的构造函数形式为Cow(const Cow&) -- 这就是复制构造函数。
当我们没有声明复制构造函数时,C++将会提供默认的复制构造函数

出现问题2的原因:

当我们使用值传递,传递Alice对象时,会先调用复制构造函数,创建临时对象副本,当函数执行完毕后将释放此副本。
在上述类实现中,我们没有定义复制构造函数,所以这里将使用默认的复制构造函数。默认复制构造函数逐个复制非静态成员(成员赋值也叫浅复制),复制的是成员的值。
这里浅复制就出现了问题,临时对象的name与Alice.name值相同,是一个地址。当call2函数执行完成,释放临时对象时,调用临时对象的析构函数,释放name指向的内存,导致再次查看Alice时出现了乱码。

所以解决上述两个问题的方法为创建一个复制构造函数,进行深复制。复制构造函数如下:

class Cow
{
private:
    static int num_cows;
    char* name;
    double weight;

public:
    ...
    Cow(const Cow& c);
    ...
};

Cow::Cow(const Cow& c) {
    int len;
    
    len = strlen(c.name);
    name = new char[len + 1];
    strcpy_s(name, len + 1, c.name);
    weight = c.weight;
    num_cows++;
    cout << num_cows << ": \"" << name << "\" created" << endl;
}

赋值符(=)重载

解决复制构造函数问题后,我们再次对上述类进行测试:

error2.png

这次类对象的创建与析构数量已经对应上了,值传递也未出现问题。但在最后释放对象时还是出现了问题。

按照最先创建的对象,最后释放的原则,可以发现Wu和Bob释放了同一块内存导致程序出现错误。

产生该问题的原因:

Cow Wu;
Wu = Bob;
C++中类的赋值时通过自动重载赋值符=实现的,出现该问题的原因与默认复制构造函数一样,使得Wu.name = Bob.name(name为char类型指针)。在调用析构函数释时,这两个对象释放了同一块内存。

解决此问题需要重载赋值运算符=

class Cow
{
private:
    static int num_cows;
    char* name;
    double weight;

public:
    ...
    Cow& operator=(const Cow& c);
    ...
};
Cow& Cow::operator=(const Cow& c) {
    int len;

    if (this == &c) {
        return *this;
    }
    delete[] name;
    len = strlen(c.name);
    name = new char[len + 1];
    strcpy_s(name, len + 1, c.name);
    weight = c.weight;
    return *this;
}
success.png

相关文章

  • C++学习笔记三

    类和动态内存分配 1. 动态内存和类 静态成员 注意: 静态数据成员在类中声明,在类外初始化, 但如果静态成员是c...

  • 《C++ Primer Plus》第12章学习笔记

    类和动态内存 1. 动态内存和类 本章先从一个错误的字符串类设计来揭示在C++类设计中可能存在的问题,特别是在使用...

  • 动态内存和类

    析构函数 如果类对象成员指向一块new分配的内存,则需要编写一个析构函数来释放内存。当删除类对象时,C++会释放对...

  • 类和动态内存分配

    本文对下面书的第12章的学习内容,做归纳总结,理清思路。C++ Primer Plus 当我们不适用系统栈提供的内...

  • 类和动态内存分配

    该部分分多部分完成,内容来源于书本,便于自己阅读。 主要内容包括: 1. 对类成员使用动态内存分配。 2. 隐式和...

  • 第十二章 类和动态内存分配(1)动态内存和类

    本章将介绍如何对类使用new和delete以及如何处理由于使用动态内存而引起的一些微妙的问题。也就是构造函数使用n...

  • 第12章:动态内存

    #1.动态内存与智能指针1.1 shared_ptr类1.2 直接管理内存1.3 shared_ptr和new结合...

  • 第11章:动态内存分配

    为什么使用动态内存分配 malloc和free calloc和realloc 使用动态分配的内存 常见的动态内存错...

  • C++中New关键字和命名空间

    动态内存分配 C++中的动态内存分配C++中通过new关键字进行动态内存申请C++中的动态内存申请是基于类型进行的...

  • freertos的动态内存分配之heap_1.c解析

    一、freertos的动态内存分配原理 首先,freertos的动态内存分配是一种“假”动态内存分配策略。所谓“假...

网友评论

    本文标题:动态内存和类

    本文链接:https://www.haomeiwen.com/subject/qudngktx.html