微信公众号: 星点课堂
新浪微博:女儿叫老白
网易云课堂:女儿叫老白
网易云课堂免费课程:《C++跨平台开发中的编译错误》
网易云课堂免费课程:《C++老鸟日记》
----------------------------------------------------------------------------
6.1,6.2
引言:
----------------------------------------------------------------------------
如果您用过C语言编程,那么您一定知道malloc()和free()。这两个接口用来申请和释放内存。C++中也支持这两个接口,但它提供了一种更好的手段来管理内存。我们今天就来讨论一下。
正文:
----------------------------------------------------------------------------
在C++编码规范中,有一条非常重要的规则就是一定要为进行变量初始化。对于类对象来说,就是调用类的构造函数。如果我们没有提供类的构造函数,编译器会默认生成一个。当然,编译器不知道我们到底想要做哪些初始化,因此编译器生成的构造函数也许并不能满足我们的需求。所以,还是为类编写构造函数吧。
// user.h
class CUser {
public:
CUser(int id, const string& name);
private:
int m_nId; // id号
string m_strName;// 姓名
string m_strAddress;// 住址
bool m_bMarried; // 是否已婚
};
// user.cpp
#include “user.h”
// 构造函数写法1
CUser:: CUser (int id, const string& name):m_nId(id), m_strName(name),m_strAddress(“”),m_bMarried(false) {
}
从在上述代码可以看出,类的构造函数与类同名,没有返回值。类的构造函数负责对成员变量进行初始化,并做一些其他必要的工作。上述示例代码中,成员变量的初始化直接写在构造函数的函数名的那一行。这是非常常用的方法。当然,您也可以写成这样:
// 构造函数写法2
CUser:: CUser (int id, const string& name) {
m_nId = id;
m_strName = name;
}
请注意:如果按照前一个写法,类的成员变量初始化的顺序必须与头文件中定义的顺序保持一致,否则在gcc编译(或者其他编译器)可能会报错(跟编译器版本有关)。
在实际开发过程中,我们可能有不同的构造需求,比如我们可以提供另外一个构造函数2:
// user.h
class CUser {
public:
CUser(int id, const string& name); // 构造函数1
CUser(int id, const string& name, const string& addr, bool bMarried); //构造函数2
private:
int m_nId; // id号
string m_strName;// 姓名
string m_strAddress;// 住址
bool m_bMarried; // 是否已婚
};
// user.cpp
#include “user.h”
// 构造函数1
CUser:: CUser (int id, const string& name):m_nId(id), m_strName(name),m_strAddress(“”),m_bMarried(false) {
}
// 构造函数2
CUser:: CUser (int id, const string& name,const string& addr, bool bMarried):m_nId(id), m_strName(name),m_strAddress(addr),m_bMarried(bMarried) {
}
如果我们定义了派生类,派生类的构造函数也需要调用父类的构造函数。
// student.h
#include user.h
class CStudent : public CUser {
public:
CStudent(const string& school, const string& pro);
private:
string m_strSchool; // 学校
string m_strPro; // 专业
};
// student.cpp
#include “student.h”
// 构造函数
CStudent:: CStudent (const string& school, const string& pro):CUser (0, “”):m_strSchool(school), m_strPro(pro) {
}
从上述代码可以看出,子类的构造函数中,可以调用父类的构造函数(当然,也可以不调用,这完全取决于您的需要,不过一般情况下都是需要调用的)。然后才是子类成员的初始化。
从这里我们也可以得出结论,在C++中,变量的初始化是按照从父类到子类的顺序。否则父类还没有初始化完毕的情况下,如果子类的构造函数调用了父类的某个接口,就很有可能出问题。
避免在类的构造函数中抛异常,因为类还没有初始化完毕,完全处于一种未知状态。此时抛出异常,可能程序仍然无法继续正确运行。
下面,我们再看一下类的析构。每个类都有一个析构函数,显然,在析构函数中,我们要做一些释放内存、断开联系之类的工作,总之是扫尾工作。当超出对象的作用域时,编译器会自动调用类的析构函数。
// user.h
class CUser
{
public:
virtual ~CUser(){}
};
请注意,在上述代码中,我们在析构函数前使用了virtual关键字。它是做什么用的呢?稍后我们会为大家解释。现在请继续看下文。
// student.h
#include “user.h”
class CStudent : public CUser {
public:
~CStudent(){}
}
// main.cpp
CUser* pUser = new CStudent(“qinghua”, “actor”);
……
if (NULL != ) {
delete pUser;
pUser = NULL;
}
现在我们来解答刚才的疑问。在上述代码中,我们定义了一个CUser的指针指向new出来的CStudent对象。这没有问题。在最后的代码中,我们把这个指针删除。请注意,是以CUser的身份删除该指针,因此调用的是CUser的析构函数。如果我们不把~CUser()前面加上virtual关键字,那么编译器的工作到此结束,它只负责析构CUser,不会调用~CStudent(),因此就可能导致CStudent中申请的内存泄漏或者~CStudent()中该做的工作没做。为父类CUser的析构函数加上了virtual关键字,就能保证编译器可以继续调用子类的析构函数。因此,程序可以把该做的工作做完。
结语:
----------------------------------------------------------------------------
构造函数和析构函数负责类的初始化与清除工作能够正常进行,因此我们一定要注意按照规范编写构造函数与析构函数。
1. 一定要编写构造函数、析构函数。
2. 子类的构造函数中,先调用父类的构造函数,再对子类的成员变量初始化。
3. 父类的析构函数前要使用virtual关键字。