C++构造函数与析构函数

前言

C++中的构造函数和析构函数,分别用于实例化对象的初始化工作以及对象资源的回收工作。


0x00 为什么要有构造函数和析构函数?

在一个类中,通常会有该类成员变量的接口以供外部访问。实例化一个类对象以后,要使用接口函数(Set函数)来设置类对象的值。

class Date
{
private:
    int nYear;
    int nMonth;
    int nDay;

    char* szFormat; // 格式化的日期字符串
public:
    void SetYear(int nYear);
    void SetMonth(int nMonth);
    void SetDay(int nDay);
    void SetDate(int nYear, int nMonth, int nDay);

    // get接口省略...

    char* GetDate(); // 得到格式化的日期
};

int main(int argc, char* argv[])
{
    Date date;
    date.SetDate(2022, 2, 27); // 使用接口函数来设置对象的值

    return 0;
}

这里有一个问题,就是实例化对象时,对象没有初始值。用接口来设置初值会感觉很不自然。并且格式化日期指针没有值。

如果按照C语言的想法,会提供一个Init函数用来实现对象的初始化。那么在实例化对象时,必须先调用Init方法用于初始化该对象。Init方法必须在实例化以后立即调用。

void Init(int nYear, int nMonth, int nDay)
{
   this->nYear = nYear;
   this->nMonth = nMonth;
   this->nDay = nDay;
   szFormat = (char*)maloloc(strlen("1970-01-01") + 1);
}

char* GetDate()
{
    // format过程省略
    return szFormat;
}

该对象还有个保存堆空间的指针变量,为此还需要为该对象提供一个释放对象资源方法Destroy函数。

void Destroy()
{
    if (szFormat != nullptr)
    {
        free(szFormat);
        szFormat = nullptr;
    }
}

因此,正常使用该类的步骤应该是:

Date date;
date.Init(2022, 2, 27);
// do something ...
data.Destroy();

现在的问题是,如果使用者忘了调用或者没有调用Init函数和Destroy函数则可能会有内存泄漏。放在其他项目中可能会有意想不到的结果。

为此C++提供了构造函数和析构函数,分别用于实例化对象的初始化以及对象资源的回收。能够完美解决上面的问题。

0x01 构造函数

在实例化对象时会自动调用构造函数。

1.与类名同名,无返回值。
2.允许重载。
3.不允许显式调用。因为如果构造函数中有申请内存,显式调用会重复申请内存,原来申请的内存地址将会被覆盖。例如上面的Init函数的重复调用。但vs中,可以使用[对象.类名::构造函数]来显式调用。

class VectorInt2D  //类
{
public:
    VectorInt2D()
    {
        Init();
    }
    VectorInt2D(int x) // 构造函数可以重载
    {
        Init(x);
    }
    VectorInt2D(int x, int y) // 构造函数可以重载
    {
        Init(x, y);
    }
    void Init(int x = 0, int y = 0)
    {
        uint32_t nLen = strlen("VectorInt2D") + 1;
        m_pszClassName = new char[nLen];
        strcpy_s(m_pszClassName, nLen, "VectorInt2D");

        this->x = x;
        this->y = y;
    }
    ~VectorInt2D()
    {
        if (m_pszClassName != nullptr)
        {
            delete m_pszClassName;
            m_pszClassName = nullptr;
        }
    }

    char* GetClassName()
    {
        return m_pszClassName;
    }

private:
    char* m_pszClassName;
    int  x;
    int  y;
};

int main(int argc, char* argv[])
{
    VectorInt2D pos1(2, 1);  //实例化对象自动调用构造
    pos1.VectorInt2D::VectorInt2D(); // 非标准

    //pos1.VectorInt2D(); //error 不允许显式调用

    return 0;
}

0x02 析构函数

在对象出作用域时调用。

1.函数名为类名前~,无返回值。
2.不允许带参数,不允许重载。
3.可以显示调用。向上面Destroy函数的调用,可以通过写代码来控制资源的合理释放,因此可以显式调用。

int main(int argc, char* argv[])
{
    VectorInt2D pos1(2, 1); 
    pos1.~VectorInt2D(); // 显示调用析构函数
    return 0;
}

0x03 构造函数与析构函数的失败问题

可以在构造函数中调用析构函数,因此构造函数中如果申请内存失败,可以直接调用析构函数。但是构造函数没有返回值,如何让外部知道构造函数是否成功?


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值