构造函数
到目前为止,对类中数据成员的赋值都是通过set_XXX()函数完成的,如例2-3中通过set_carname()函数为对象mycar中m_strCarName成员进行了设置,再调用get_carname()函数显示汽车名称。但如果不小心先执行了get_carname()函数,则会因为m_strCarName没有有效值,而看不到任何有效信息。因此,希望能有一种方法在创建对象时完成数据成员的初始化,构造函数就能满足这样的要求。
构造函数是类中特殊的成员函数,用于初始化对象的数据成员,其定义语法如下所示:
class 类名
{
public:
构造函数名称(参数表)
{
函数体
}
private:
数据成员;
};
构造函数的定义语法规定:
● 构造函数名与类名相同。
● 构造函数名前没有返回值类型声明。
● 构造函数中不能通过return语句返回一个值。
● 通常构造函数具有public属性。
下面定义一个包含构造函数的汽车类Car,代码如下所示:
class Car //定义类
{
public:
Car() //构造函数
{
m_strCarName = "default name";
}
private:
string m_strCarName; //数据成员
};
从上述代码看出,在类中定义了一个无参数的构造函数Car,在构造函数中将数据成员m_strCarName初始化为“default name”。
构造函数在对象创建时被自动调用,完成数据成员的初始化,其调用形式多样。接下来分三种情况讨论构造函数的定义形式。
1、系统提供的默认构造函数
若程序中没有显式提供类的构造函数,编译器会自动提供一个无参构造函数,通常这个函数的函数体为空,不具有实际意义,形式如下所示。
类名::构造函数名()
{
}
在类中没有显式定义构造函数,则创建类对象时会使用编译器提供的默认无参构造函数。 接下来通过一个案例演示默认构造函数的调用,如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3
4 class Car //定义类Car
5 {
6 //成员函数
7 public:
8 void disp_mems_value(); //声明用于显示数据成员值的函数
9 //数据成员
10 private:
11 int m_nWheels;
12 int m_nSeats;
13 int m_nLength;
14 };
15 void Car::disp_mems_value() //类成员函数disp_mems_value()的实现
16 {
17 cout << "wheels = " << m_nWheels << endl;
18 cout << "seats = " << m_nSeats << endl;
19 cout << "length = " << m_nLength << endl;
20 }
21 int main()
22 {
23 Car mycar; //定义类对象mycar
24 mycar.disp_mems_value(); //调用函数显示数据成员值
25 system("pause");
26 return 0;
27 }
运行结果如图1所示。
图1 例1运行结果
从运行结果看出,系统提供的默认无参构造函数并没有为数据成员提供有效初值,三个整型数据成员的值为随机量。
除了上述的情况之外,如果在类中定义了对象成员或类中包含特殊成员,如虚函数,默认的构造函数会完成特殊操作,感兴趣的读者可以参考相关资料。
2、自定义无参构造函数
除了系统提供的默认构造函数外,在程序中可以显式定义无参构造函数。通常自己定义的无参构造函数将数据成员初始化为固定值。
接下来通过一个案例来学习无参构造函数的定义方法,如例2所示。
例2
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car //定义类
6 {
7 public:
8 Car() //定义构造函数
9 {
10 cout << "Car constructor!" << endl;
11 m_strCarName = "default name";
12 }
13 string get_carname() //定义获取汽车名称的函数
14 {
15 return m_strCarName;
16 }
17 private:
18 string m_strCarName; //数据成员,记录汽车名称
19 };
20
21 int main()
22 {
23 Car mycar; //创建对象
24
25 cout << "car name : " << mycar.get_carname() << endl; //显示汽车名称
26 system("pause");
27 return 0;
28 }
运行结果如图2所示。
图2 例2运行结果
例2中类Car定义了无参构造函数Car()。从运行结果看出,构造函数在创建对象时被自动调用,通过函数向成员m_strCarName存入固定值“default name”。
3、自定义带参数的构造函数
通常我们希望能在对象创建时为数据成员提供有效初值,通过定义带参数的构造函数可以实现这样的功能。此外还可定义多个具有不同参数的构造函数,实现对不同数据成员的初始化。定义多个构造函数也就是构造函数的重载。
接下来通过一个案例来学习如何定义带参数的构造函数并完成函数重载,如例3所示。
例3
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 Car(string con_carname,int con_seats) //定义带有两个参数的构造函数
9 {
10 cout << "Calling Car constructor, set carname,seats!" << endl;
11 m_strCarName = con_carname; //对m_strCarName属性赋值
12 m_nSeats = con_seats; //对m_nSeats属性赋值
13 }
14 Car(string con_carname) //定义带有一个参数的构造函数
15 {
16 cout << "Calling Car constructor, set carname!" << endl;
17 m_strCarName = con_carname;
18 }
19 void disp_memmsg() //显示成员值的函数
20 {
21 cout << "carname: " << m_strCarName << ","
22 << "seats = " << m_nSeats << endl;
23 }
24 private:
25 string m_strCarName;
26 int m_nSeats;
27 };
28 int main()
29 {
30 Car mycar("my new car", 4); //调用带两个参数的构造函数创建对象
31 Car tomcar("tom's car"); //调用带一个参数的构造函数创建对象
32 mycar.disp_memmsg();
33 tomcar.disp_memmsg();
34
35 system("pause");
36 return 0;
37 }
运行结果如图3所示。
图3 例2-6运行结果
例3中定义了两个构造函数,它们都带有参数,用来完成不同数据成员的初始化,两个函数实现了构造函数的重载。在创建对象mycar和tomcar时,根据传入参数的不同调用了不同的构造函数。创建对象mycar时调用带两个参数的构造函数Car(string, int),对m_strCarName和m_nSeats属性进行了赋值,创建对象tomcar时调用带一个参数的构造函数Car(string),只对m_strCarName进行了赋值,由于m_nSeats没有给出有效值,则tomcar对象的m_nSeats为随机值。
对于带参数的构造函数,除了像前面介绍的,在函数体内对数据成员进行赋值外,C++中还可以通过下面两种方式为数据成员提供初值。
(1)通过初始化表来实现数据成员的初始化
初始化列表就是在构造函数的参数列表后加冒号“:”,然后列出参数的初始化表,有多个参数时,中间以逗号“,”隔开,具体格式如下所示:
类名::构造函数名(参数列表):数据成员1(参数1),数据成员的2(参数2),…,数据成员n(参数n)
{
构造函数体
}
构造函数若在类外定义需要在构造函数名称前添加类名和“::”。
下面将例2-6中带有两个参数的构造函数改写为使用初始化表来进行初始化,代码如下所示:
Car(string con_carname,int con_seats):m_strCarName(con_carname),
m_nSeats(con_seats)
{
}
(2)默认参数值的构造函数
前面介绍的带参数的构造函数在定义对象时必须给构造函数传递相应的参数值,但在现实世界中,对象往往会有一些默认值,如幼儿园老师一般是女老师,孩子6岁开始上小学等,为了描述现实世界的这些情况,C++允许定义带默认参数值的构造函数,无特别要求时参数采用默认值,当然也可由用户根据实际情况指定。
带默认参数的构造函数的定义形式如下所示:
类名::构造函数名(类型 参数1 = 默认值, 类型 参数2 = 默认值)
{
函数体
}
下面将例2-6中带有一个参数的构造函数修改为带默认参数值的函数,代码如下所示:
Car(string con_carname = "my new car")
{
cout << "Calling Car constructor, set carname!" << endl;
m_strCarName = con_carname;
}
有了默认参数,定义对象时的形式就会有很多的选择。接下来通过一个案例来说明如何调用带有默认值的构造函数来创建对象,如例4所示。
例4
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 //通过初始化表对数据成员进行初始化
9 Car(string con_carname, int con_seats) :m_strCarName(con_carname),
10 m_nSeats(con_seats)
11 {
12 cout << "Calling Car constructor, set carname and seats!" << endl;
13
14 }
15 Car(string con_carname = "my new car") //定义带有默认参数值的构造函数
16 {
17 cout << "Calling Car constructor, set carname!" << endl;
18 m_strCarName = con_carname;
19 m_nSeats = 4;
20 }
21
22 void disp_memmsg()
23 {
24 cout << "carname: " << m_strCarName << ","
25 << "seats = " << m_nSeats << endl;
26 }
27
28 private:
29 string m_strCarName;
30 int m_nSeats;
31 };
32
33 int main()
34 {
35 //调用带有一个默认参数的构造函数创建对象
36 Car mycar;
37 //调用带有一个参数的构造函数创建对象,不使用默认值
38 Car tomcar("tom's car");
39
40 mycar.disp_memmsg();
41 tomcar.disp_memmsg();
42
43 system("pause");
44 return 0;
45 }
运行结果如图4所示。
图4 例4运行结果
在例4中带一个参数的构造函数具有默认值,这样在定义对象时可以有不同形式。创建对象mycar时没有提供参数值,创建对象tomcar时给出了参数值,从运行结果看出这两个对象都是调用代码第15-20行的带一个参数的构造函数创建的,对象mycar的数据成员m_strCarName被初始化为默认值“my new car”。
对于带默认参数值的构造函数的定义及使用有两个注意事项:
1、对于带默认参数值的构造函数来说,需要防止调用的二义性。
2、构造函数中若第n个参数有默认值,则其后的所有参数都应该有默认值,若构造函数定义为如下内容,则编译出错:
Car(string con_carname = "my car name", int con_seats);
上述代码中,第一个参数有默认值,则后续所有参数均应该有默认值。
脚下留心:类中定义构造函数后,编译器不再提供默认构造函数
只要类中定义了一个构造函数,C++将不再提供默认的构造函数。如果在类中定义的是带参数的构造函数,创建对象时想使用不带参数的构造函数,则需要再实现一个无参的构造函数,否则编译出错,如例5所示。
例5
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 Car(string con_carname) //定义带参数的构造函数
9 {
10 m_strCarName = con_carname;
11 }
12 private:
13 string m_strCarName;
14 };
15
16 int main()
17 {
18 Car mycar; //应调用无参数的构造函数创建对象
19 return 0;
20 }
编译出错,结果如图5所示。
图5 例5编译错误
从运行结果看出,代码第18行想通过调用无参构造函数创建对象出错。因为类中已提供了一个带参数的构造函数,编译器不再提供默认的构造函数,若想调用无参构造函数必须显式定义。