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;
来定义对象,那么这两个构造函数都与之匹配,这将导致程序报错。