### 构造函数和析构函数

每个类都具有构造函数和析构函数。其中，构造函数在定义对象时被调用，析构函数在对象释放时被调用。如果用户没有提供构造函数和析构函数，系统将提供默认的构造函数和析构函数。

#### 1．构造函数

构造函数是一个与类同名的方法，可以没有参数，有一个参数或多个参数，但是构造函数没有返回值。如果构造函数没有参数，该函数被称为类的默认构造函数。下面的代码显示地定义了一个默认构造函数。其程序代码如下：

class CUser //定义 CUser 类

{

private:

char m_Username[128] ; //定义数据成员 m_Username

char m_Password[128]; //定义数据成员 m_Password

public:

CUser() //定义默认的构造函数

{

strcpy(m_Username,"MR"); //为数据成员赋值

strcpy(m_Password,"KJ"); //为数据成员赋值

}

char* GetUsername()const //定义成员函数 GetUsername

{

return (char*)m_Username;

}

char* GetPassword()const //定义成员函数 GetPassword

{

return (char*)m_Password;

}

};

如果用户为类定义了构造函数，无论是默认构造函数还是非默认构造函数，系统均不会提供默认的构造函数。下面定义一个 CUser 类的对象，它将调用用户定义的默认构造函数。

int main(int argc, char* argv[])

{

CUser user; //定义 CUser 类对象

printf("%s\n",user.GetPassword()); //调用 GetPassword 方法输出 m_Password

return 0;

}

执行上述代码，效果如图 1 所示。

图 1 默认构造函数

注意：一个类可以包含多个构造函数，各个构造函数之间通过参数列表进行区分。

从图 1 中可以发现，在定义 user 对象时，调用了默认的构造函数对数据成员进行了赋值。下面再定义一个非默认的构造函数，使得用户能够在定义 CUser 类对象时为数据成员赋值。

class CUser //定义 CUser 类

{

private:

char m_Username[128] ; //定义数据成员 m_Username

char m_Password[128]; //定义数据成员 m_Password

public:

CUser() //定义默认的构造函数

{

strcpy(m_Username,"MR");

strcpy(m_Password,"KJ");

}

CUser(const char *pUsername,const char *pPassword) //定义普通的构造函数

{

if (pUsername != NULL && pPassword != NULL)

{

strcpy(m_Username,pUsername);

strcpy(m_Password,pPassword);

}

}

char* GetUsername()const //定义成员函数 GetUsername

{

return (char*)m_Username;

}

char* GetPassword()const //定义成员函数 GetPassword

{

return (char*)m_Password;

}

};

下面定义两个 CUser 对象，分别采用不同的构造函数。

int main(int argc, char* argv[])

{

CUser user; //调用默认构造函数定义 user 对象

printf("%s\n",user.GetUsername()); //输出用户名

CUser Customer("SK","songkun"); //调用非默认构造函数定义 Customer 对象

printf("%s\n",Customer.GetUsername()); //输出用户名

return 0;

}

执行上述代码，效果图 2 如所示。

图 2 非默认构造函数

从图 2 中的输出结果可以发现，语句“CUser Customer("SK","songkun");”调用了非默认的构造函数，通过传递两个参数为初始化 CUser 类的数据成员。如果想要定义一个 CUser 对象的指针，并调用非默认构造函数进行初始化，可以采用如下的格式。

CUser *pCustomer = new CUser("SK","songkun"); //调用非默认构造函数为指针分配空间

在定义常量或引用时，需要同时进行初始化，为其直接赋值是非法的，那么如果在类中包含常量或引用类型数据成员时该如何初始化呢？

类的构造函数通过使用冒号:运算符提供了初始化成员的方法。

例如，在构造函数中初始化数据成员。

class CBook //定义一个类 CBook

{

public:

char m_BookName[128]; //定义数据成员

const unsigned int m_Price; //定义常量数据成员

int m_ChapterNum;

CBook() //定义默认的构造函数

:m_Price(32) ,m_ChapterNum(15) //对数据成员进行初始化

{

strcpy(m_BookName,"大学英语");

}

};

上述代码在定义 CBook 类的构造函数时对数据成员 m_Price 和 m_ChapterNum 进行了初始化。

编译器除了能够提供默认的构造函数外，还可以提供默认的复制构造函数。当函数或方法的参数采用按值传递时，编译器会将实际参数复制一份传递到被调用函数中，如果参数属于某一个类，编译器会调用该类的复制构造函数来复制实际参数到被调用函数。复制构造函数与类的其他构造函数类似，以类名作为函数的名称，但是其参数只有一个，即该类的常量引用类型。因为复制构造函数的目的是为函数复制实际参数，没有必要在复制构造函数中修改参数，因此参数定义为常量类型。下面的代码为 CBook 类定义了一个复制构造函数。其程序代码如下：

class CBook //定义一个 CBook 类

{

public:

char m_BookName[128]; //定义数据成员 m_BookName

const unsigned int m_Price; //定义数据成员 m_Price

int m_ChapterNum; //定义数据成员 m_Chapter

CBook() //定义默认构造函数

:m_Price(32),m_ChapterNum(15) //初始化数据成员

{

strcpy(m_BookName,"大学英语");

printf("构造函数被调用\n"); //输出信息

}

CBook(const CBook &book) //定义复制构造函数

:m_Price(book.m_Price) //初始化数据成员

{

m_ChapterNum = book.m_ChapterNum; //复制 m_ChaperNum 成员数据

strcpy(m_BookName,book.m_BookName); //复制 m_BookName 成员数据

printf("复制构造函数被调用\n"); //输出信息

}

};

下面定义一个函数，以 CBook 类对象为参数，演示在按值传递函数参数的时候调用了复制构造函数。

void OutputBookInfo(CBook book) //定义一个函数，以 CBook 类对象为参数

{

printf("%s\n",book.m_BookName); //输出 m_BookName 成员数据

}

int main(int argc, char* argv[])

{

CBook book; //定义一个 CBook 类对象 book

OutputBookInfo(book); //调用 OutputBookInfo 方法

return 0;

}

执行上述代码，效果如图 3 所示。

图 3 复制构造函数 1

从图 3 中可以发现，执行“CBook book;”语句时调用了构造函数，输出了“构造函数被调用”的信息。执行“OutputBookInfo(book);”语句首先调用复制构造函数，输出“复制构造函数被调用”的信息，然后执行 OutputBookInfo 函数，输出 m_BookName 成员的信息。

如果对 OutputBookInfo 函数进行修改，以引用类型作为函数参数，将不会执行复制构造函数。

例如，引用类型作为函数参数。其程序代码如下：

void OutputBookInfo(CBook &book) //以 CBook 引用类型作为参数

{

printf("%s\n",book.m_BookName); //输出信息

}

int main(int argc, char* argv[])

{

CBook book; //定义一个 CBook 类对象 book

OutputBookInfo(book); //调用 OutputBookInfo 方法

return 0;

}

执行上述代码，效果如图 4 所示。

图 4 复制构造函数 2

从图 4 中可以发现，复制构造函数没有被调用。因为 OutputBookInfo 函数是以引用类型作为参数，函数参数按引用的方式传递，直接将实际参数的地址传递给函数，不涉及复制参数，因此没有调用复制构造函数。

通过上述试验，可以总结一条经验，编写函数时，尽量采用按引用的方式传递参数，这样可以避免调用复制构造函数，能够极大地提高程序的执行效率。

#### 2．析构函数

在介绍完构造函数之后，下面介绍一下析构函数。析构函数在对象超出作用范围或使用 delete 运算符释放对象时被调用，用于释放对象占用的空间。如果用户没有显式地提供析构函数，系统会提供一个默认的析构函数。析构函数也是以类名作为函数名，与构造函数不同的是在函数名前添加一个“~”符号，标识该函数是析构函数。析构函数没有返回值，甚至 void 类型也不可以，析构函数也没有参数，因此析构函数是不能够重载的。这是析构函数与普通函数最大的区别。

例如，下面为 CBook 类添加一个析构函数。其程序代码如下：

~CBook() //定义一个析构函数

{

m_ChapterNum = 0; //设置成员变量

memset(m_BookName,0,128); //设置成员变量的存储空间数据为 0

printf("析构函数被调用\n"); //输出析构函数调用信息

}

为了演示析构函数的调用情况，定义一个 CBook 类对象。

int main(int argc, char* argv[])

{

CBook book; //定义一个 CBook 类对象

printf("定义一个 CBook 类对象\n"); //输出信息

return 0;

}

执行上述代码，效果如图 5 所示。

图 5 析构函数

上述代码中定义了一个 CBook 对象 book，当执行“CBook book;”语句时将调用构造函数输出“构造函数被调用”的信息，然后执行“printf("定义一个 CBook 类对象\n");”语句输出一行信息，最后在函数结束时，也就是 book 对象超出了作用域时调用了析构函数释放 book 对象，因此输出了“析构函数被调用”的信息。