C++构造函数和析构函数(详解版)

本节将详细给大家介绍 C++ 类中的构造函数析构函数

构造函数

构造函数是一个特殊的公共成员函数,它在创建类对象时会自动被调用,用于构造类对象。如果程序员没有编写构造函数,则 C++ 会自动提供一个,这个自动提供的构造函数永远不会有人看到它,但是每当程序定义一个对象时,它会在后台静默运行。

当然,程序员在创建类时通常会编写自己的构造函数。如果这样做,那么除了构建类的每个新创建的对象之外,它还将执行程序员写入其中的其他任何代码。程序员通常使用构造函数来初始化对象的成员变量。但实际上,它可以做任何正常函数可以做的事情。

构造函数看起来像一个常规函数,除了它的名称必须与它所属类的名称相同,这就是为什么编译器知道这个特定的成员函数是一个构造函数。此外,构造函数不允许有返回类型。

下面的程序包括一个名为 Demo 的类,其构造函数除了打印消息之外什么都不做。编写它的目的就是为了演示构造函数何时执行。因为 Demo 对象是在两个 cout 语句之间创建的,所以构造函数将在这两个语句生成的输出行之间打印它的消息。
// This program demonstrates when a constructor executes.
#include <iostream>
using namespace std;

class Demo
{
    public:
        Demo()
        {
            cout << "Now the constructor is running.\n";
        }
};

int main()
{
    cout << "This is displayed before the object is created. \n";
    Demo demoObj;    // Define a Demo object
    cout << "This is displayed after the object is created.\n";
    return 0;
}
程序输出结果为:

This is displayed before the object is created.
Now the constructor is running.
This is displayed after the object is created.

程序中,将构造函数定义为类声明中的内联函数。当然,像任何其他类成员函数一样,也可以将其原型放在类声明中,然后将其定义在类之外。在这种情况下,需要添加函数所属类的名称和函数名前面的作用域解析运算符。但是由于构造函数的名称与类名相同,所以名称会出现两次。

以下就是在类声明之外定义 Demo 构造函数时,其函数头的样子:
Demo:: Demo ()    // 构造函数
{
    cout << "Now the constructor is running. \n";
}

重载构造函数

我们知道,当两个或多个函数共享相同的名称时,函数名称被称为重载。只要其形参列表不同,C++ 程序中可能存在具有相同名称的多个函数。

任何类成员函数都可能被重载,包括构造函数。例如,某个构造函数可能需要一个整数实参,而另一个构造函数则需要一个 double,甚至可能会有第三个构造函数使用两个整数。只要每个构造函数具有不同的形参列表,则编译器就可以将它们分开。

下面的程序声明并使用一个名为 Sale 的类,它有两个构造函数。第一个构造函数的形参接受销售税率;第二个构造函数是免税销售,没有形参。它将税率设置为 0。这样一个没有形参的构造函数称为默认构造函数。
#include <iostream>
#include <iomanip>
using namespace std;

// Sale class declaration
class Sale
{
    private:
        double taxRate;
    public:
        Sale(double rate) // Constructor with 1 parameter
        {
            taxRate = rate; // handles taxable sales
        }
        Sale ()    // Default constructor
        {
            taxRate = 0.0    // handles tax-exempt sales
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost*taxRate;
            return total;
        }
};

int main()
{
    Sale cashier1(.06); // Define a Sale object with 6% sales tax
    Sale cashier2;    // Define a tax-exempt Sale object
    // Format the output
    cout << fixed << showpoint << setprecision (2);
    // Get and display the total sale price for two $24.95 sales
    cout << "With a 0.06 sales tax rate, the total\n";
    cout << "of the $24.95 sale is $";
    cout << cashier1.calcSaleTotal(24.95) << endl;
    cout << "\nOn a tax-exempt purchase, the total\n";
    cout << "of the $24.95 sale is, of course, $";
    cout << cashier2.calcSaleTotal(24.95) << endl;
    return 0;
}
程序输出结果:

With a 0.06 sales tax rate, the total
of the $24.95 sale is $26.45
On a tax-exempt purchase, the total
of the $24.95 sale is, of course, $24.95

注意看此程序如何定义的两个 Sale 对象:

Sale cashier1(.06);
Sale cashier2;

在 cashier1 的名称后面有一对括号,用于保存值,发送给有一个形参的构造函数。但是,在 Cashier2 的名称后面就没有括号,它不发送任何参数。在 C++ 中,当使用默认构造函数定义对象时,不用传递实参,所以不能有任何括号,即:

Sale cashier2 () ;    // 错误
Sale cashier2;    // 正确

默认构造函数

Sale 类需要一个默认构造函数来处理免税销售的情况,但是其他类可能并不需要这样一个构造函数。例如,如果通过类创建的对象总是希望将实参传递给构造函数。那么,在设计一个具有构造函数的类时,应该包括一个默认构造函数,这在任何时候都会被认为是一个很好的编程实践。

如果没有这样一个默认构造函数,那么当程序尝试创建一个对象而不传递任何参数时,它将不会编译,这是因为必须有一个构造函数来创建一个对象。为了创建不传递任何参数的对象,必须有一个不需要参数的构造函数,也就是默认构造函数。

如果程序员没有为类编写任何构造函数,则编译器将自动为其创建一个默认构造函数。但是,当程序员编写了一个或多个构造函数时,即使所有这些构造函数都是有参数的,编译器也不会创建一个默认构造函数,所以程序员有责任这样做。

类可能有许多构造函数,但只能有一个默认构造函数。这是因为:如果多个函数具有相同的名称,则在任何给定时刻,编译器都必须能够从其形参列表中确定正在调用哪个函数。它使用传递给函数的实参的数量和类型来确定要调用的重载函数。因为一个类名称只能有一个函数能够接受无参数,所以只能有一个默认构造函数。

一般情况下,就像在 Sale 类中那样,默认构造函数没有形参。但是,也可能有另一种的默认构造函数,其所有形参都具有默认值,所以,它也可以无实参调用。如果创建了一个接受无实参的构造函数,同时又创建了另外一个有参数但允许所有参数均为默认值的构造函数,那么这将是一个错误,因为这实际上是创建了两个“默认”构造函数。以下语句就进行了这种非法的声明:
class Sale //非法声明
{
    private:
        double taxRate;
    public:
        Sale()    //无实参的默认构造函数
        {
            taxRate = 0.05;
        }
        Sale (double r = 0.05)    //有默认实参的默认构造函数
        {
            taxRate = r;
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost * taxRate;
            return total;
        };
};
可以看到,第一个构造函数没有形参,第二个构造函数有一个形参,但它有一个默认实参。如果一个对象被定义为没有参数列表,那么编译器将无法判断要执行哪个构造函数。

析构函数

析构函数是具有与类相同名称的公共成员函数,前面带有波浪符号(〜)。例如,Rectangle 类的析构函数将被命名为 〜Rectangle。

当对象被销毁时,会自动调用析构函数。在创建对象时,构造函数使用某种方式来进行设置,那么当对象停止存在时,析构函数也会使用同样的方式来执行关闭过程。例如,当具有对象的程序停止执行或从创建对象的函数返回时,就会发生这种情况。

下面的程序显示了一个具有构造函数和析构函数的简单类。它说明了在程序执行过程中这两个函数各自被调用的时间:
//This program demonstrates a destructor.
#include <iostream>
using namespace std;

class Demo
{
    public:
        Demo(); // Constructor prototype
        ~Demo(); // Destructor prototype
};

Demo::Demo()    // Constructor function definition
{
    cout << "An object has just been defined,so the constructor" << " is running.\n";
}

Demo::~Demo() // Destructor function definition
{
    cout << "Now the destructor is running.\n";
}

int main()
{
    Demo demoObj;    // Declare a Demo object;
    cout << "The object now exists, but is about to be destroyed.\n";
    return 0;
}
程序输出结果:

An object has just been defined, so the constructor is running.
The object now exists, but is about to be destroyed.
Now the destructor is running.

除了需要知道在对象被销毁时会自动调用析构函数外,还应注意以下事项:
  1. 像构造函数一样,析构函数没有返回类型。
  2. 析构函数不能接收实参,因此它们从不具有形参列表。
  3. 由于析构函数不能接收实参,因此只能有一个析构函数。