5. 构造函数

在 C++ 语言中,任何一个对象都有自己的生命周期。在 C++ 语言中,这个生命周期完全可以自己控制。

C++ 语言中变量的声明周期要经历一下五个阶段:

  1. 内存分配(出生)
  2. 构造对象(出生后)
  3. 使用对象
  4. 析构对象(销毁前)
  5. 内存释放(销毁)

这五个阶段的顺序是固定不变的,不能颠倒。

在创建对象(变量)方面,C++ 语言和 C 语言是一样的,这个对象可以建立在栈上的局部对象、可以是建立在数据段的全局对象、可以是动态内存分布的堆上的对象,也可以是临时对象。无论是在哪里构建对象,都要完整的经历上述五个阶段,否则可能会带来系统的不稳定。

阶段
状态
说明
内存分配
原始内存(未初始化)
分配一块内存,但内部数据不确定
构造对象
对象诞生
调用构造函数,确定成员变量的值。
使用对象
有效状态
成员变量和成员函数可正常调用。
析构对象
对象死亡
调用析构函数,释放对象占用的资源(但成员变量占用的内存依旧占用)。
内存释放
销毁
释放成员变量占用的内存块。

上面我们提到了两个函数:构造函数析构函数,这是两个特殊的成员函数,这些成员函数由开发人员编写,在创建对象时由系统自动调用。

我们先了解构造函数

构造函数(Constructor Function)的作用是对新创建的对象的成员变量进行初始化。

构造函数通常不是一个函数,而是一组函数。在创建对象分配内存后,对象会根据函数重载规则(根据形参的个数和类型)来调用其中的一个构造函数来初始化这个对象的成员。

构造函数的语法

构造函数的名称一定要和类名完全一致,并且构造函数一定没有返回类型。构造函数通常是共有的访问权限。定义语法如下所示:

class 类名:
{
    public:
        类名(形参列表1) : 构造函数的初始化列表 {...}
        类名(形参列表2) : 构造函数的初始化列表 {...}
        ...
};

说明

示例

定义一个矩形类 Rectangle,在创建对象时可以无参数的创建对象,也可以有参数的方式来创建对象。

// filename: constructor.cpp
#include <iostream>

using namespace std;

class Rectangle {
    public:
        Rectangle() {
            cout << "无参的构造函数被调用" << endl;
            width = 1, height = 1;
        }
        Rectangle(int w, int h) {
            cout << "有参的构造函数被调用,w:" << w << " h:" << h << endl;
            width = w, height = h;
        }
        int get_area(void) {
            return width * height;
        }
        int get_length(void) {
            return  (width + height) * 2;
        }
    private:
        int width;
        int height;
};

上面定义了两个构造函数,Rectangle()Rectangle(int w, int h),其中 Rectangle() 会在创建对象没有实参时调用。而 Rectangle(int w, int h) 会在有两个实参时调用。

下面我们来学习如何显式的调用对应的构造函数。

创建对象时调用构造函数的规则

1. 调用无参数的构造函数的语法:

类名 对象名;

2. 调用有参数的构造函数的语法:

类名 对象名(实参1, ...);

如:

Rectangle r1;  // 调用无参的构造函数,成员变量 width 和 height 都是 1。
Rectangle r2(5, 2);  // 调用两个参数的构造函数,width赋值为 5,height 赋值为 2

完整示例代码如下:

// filename: constructor.cpp
#include <iostream>

using namespace std;

class Rectangle {
    public:
        Rectangle() {
            cout << "无参的构造函数被调用" << endl;
            width = 1, height = 1;
        }
        Rectangle(int w, int h) {
            cout << "有参的构造函数被调用,w:" << w << " h:" << h << endl;
            width = w, height = h;
        }
        int get_length(void) {
            return  (width + height) * 2;
        }
    private:
        int width;
        int height;
};

int main(int argc, char * argv[]) {
    Rectangle r1;  // 调用无参的构造函数,成员变量 width 和 height 都是 1。
    Rectangle r2(5, 2);  // 调用两个参数的构造函数,width赋值为 5,height 赋值为 2

    cout << "r1的周长:" << r1.get_length() << endl;
    cout << "r2的周长:" << r2.get_length() << endl;

    return 0;
}

编译和运行结果如下:

weimz@mzstudio:~$ g++ -o constructor constructor.cpp
weimz@mzstudio:~$ ./constructor 
无参的构造函数被调用
有参的构造函数被调用,w:5 h:2
r1的周长:4
r2的周长:14

可见 Rectangle r1; 则调用无参的构造函数,Rectangle r2(5, 2) 则调用有参的构造函数。

简化构造函数

因为构造函数也可以有缺省参数,因此上述示例中的两个构造函数也可以使用缺省参数的方式将两个构造函数整合成一个构造函数,整合后的代码比较简洁,类的实现代码如下所示。

class Rectangle {
    public:
        Rectangle(int w=1, int h=1) {  // 使用缺省参数
            width = w, height = h;
        }
        int get_length(void) {
            return  (width + height) * 2;
        }
    private:
        int width;
        int height;
};

这样定义的类创建对象时可以无参构造,也可以用一个或两个参数来构造。创建对象的代码都可以不变。

实验

自己实现一个正方形类 class Square,其中只有一个属性为边长,在创建对象时如果没有给定边长则边长为1。然后编写两个获取周长和面积的成员函数。