17 构造函数

1 构造函数的作用

在上一节中,我们定义一个对象后,是通过一一赋值的方式,对其中的属性进行初始化的,这很麻烦。

在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行,用于实现对象初始化。这种特殊的成员函数就是构造函数(Constructor)。

2 构造函数语法

2.1 传统的构造函数

#include<iostream>

using namespace std;

class Student {
public:
    string name;
    int age;
    int score;

    // 构造函数
    Student(string _name, int _age, int _score){
        name = _name;
        age = _age;
        score = _score;
    }

    void say() {
        cout << "我是" << name << ",我今年" << age << "岁了,我的成绩是" << score << "分。" << endl;
    }
};

int main() {
    // 使用构造函数
    Student stu1("张三", 16, 85);
    stu1.say();
    return 0;
}

也可以将构造函数放在类体外

class Student {
public:
    string name;
    int age;
    int score;
    // 声明构造函数原型
    Student(string _name, int _age, int _score);

    void say() {
        cout << "我是" << name << ",我今年" << age << "岁了,我的成绩是" << score << "分。" << endl;
    }
};
// 给出构造函数具体定义
Student::Student(string _name, int _age, int _score) {
    name = _name;
    age = _age;
    score = _score;
}

2.2 使用初始化列表的构造函数

class Student {
public:
    string name;
    int age;
    int score;

    Student(string _name, int _age, int _score) : name(_name), age(_age), score(_score) {}

    void say() {
        cout << "我是" << name << ",我今年" << age << "岁了,我的成绩是" << score << "分。" << endl;
    }
};

注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。 请看下面的代码:

#include<iostream>

using namespace std;

class Student {
public:
    string name;
    int age;
    int score;
    // 我们试图先初始化变量score,然后将score的值赋值给age变量
    Student(string _name, int _age, int _score) : name(_name), score(_score), age(score) {}

    void say() {
        cout << "我是" << name << ",我今年" << age << "岁了,我的成绩是" << score << "分。" << endl;
    }
};

int main() {
    Student stu1("张三", 16, 85);
    stu1.say();
    return 0;
}

在我们的预想中,输出的结果应该是:“我是张三,我今年85岁了,我的成绩是85分。”

但实际上的输出结果是:“我是张三,我今年458岁了,我的成绩是85分。”

因为成员变量的初始化顺序只与成员变量在类中声明的顺序有关,在初始化 age 属性时,score 属性尚未初始化,其值为随机值。

另外,const 修饰的属性,只能通过使用初始化列表的构造函数进行初始化。

3 构造函数的重载

和普通函数一样,构造函数也可以重载。在定义对象时,系统会根据用户输入的参数类型和个数,匹配到需要调用的构造函数。

#include<iostream>

using namespace std;

class Student {
public:
    string name;
    int age;
    int score;
    // 两个构造函数
    Student(string _name, int _age) : name(_name), age(_age), score(0) {}
    Student(string _name, int _age, int _score) : name(_name), age(_age), score(_score) {}

    void say() {
        cout << "我是" << name << ",我今年" << age << "岁了,我的成绩是" << score << "分。" << endl;
    }
};

int main() {
    Student stu1("张三", 16, 85);
    stu1.say();
    Student stu2("李四", 19);
    stu2.say();
    return 0;
}

4 具有默认值的构造函数

和普通函数相同,构造函数中的参数也可以有默认值,而且有默认值的参数,需要放在参数列表的最后。

例如,下面这段构造函数,给出来三个属性的默认值。

Student(string _name = "", int _age = 0, int _score = 0) : name(_name), age(_age), score(_score) {}

此时,即使我们不输入任何参数,我们也可以正常初始化对象。定义对象,可以用 Student stu(); ,还可以省略掉括号,直接写 Student stu;

5 无参构造函数

顾名思义,就是没有参数的构造函数。

例子:

Student() : name(""), age(0), score(0) {}

既然构造函数本身不能输入参数,那么我们可以直接使用 Student stu; 来定义对象。

6 默认构造函数

如果我们并没有为一个类显式声明构造函数,那么编译器会自动生成一个无参构造函数,这个构造函数,就是默认构造函数。

如果我们为一个类显式声明了构造函数,那么默认构造函数不会自动生成。

可以认为默认构造函数的函数体为空,所以它不会对那些属性进行初始化。

7 注意

定义对象时写的代码,必须可以找到一个与之对应的构造函数,并且只能有一个。

例如:Student 类中有以下两个构造函数:

Student(string _name = "", int _age = 0, int _score = 0) : name(_name), age(_age), score(_score) {}
Student() : name(""), age(0), score(0) {}

此时,如果使用 Student stu; 来定义对象,那么这两个构造函数都与之匹配,这将导致程序报错。