C++中的构造函数和析构函数,分别用于实例化对象的初始化工作以及对象资源的回收工作。
在一个类中,通常会有该类成员变量的接口以供外部访问。实例化一个类对象以后,要使用接口函数(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++提供了构造函数和析构函数,分别用于实例化对象的初始化以及对象资源的回收。能够完美解决上面的问题。
在实例化对象时会自动调用构造函数。
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; }
在对象出作用域时调用。
1.函数名为类名前~,无返回值。
2.不允许带参数,不允许重载。
3.可以显示调用。向上面Destroy函数的调用,可以通过写代码来控制资源的合理释放,因此可以显式调用。
int main(int argc, char* argv[]) { VectorInt2D pos1(2, 1); pos1.~VectorInt2D(); // 显示调用析构函数 return 0; }
可以在构造函数中调用析构函数,因此构造函数中如果申请内存失败,可以直接调用析构函数。但是构造函数没有返回值,如何让外部知道构造函数是否成功?