C++构造函数

利用构造函数对类对象进行初始化

对象的初始化

  • 不能在声明类时对数据成员初始化,因为类并不是一个实体,而是一种抽象类型,并不占存储空间。

例:(❌)

class Time
{
	hour = 0; //不能在类声明中对数据成员初始化
	minute = 0;
	sec = 0;
};
  • 如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化

例:(√)

class Time
{
public: //声明为公用成员
	hour; 
	minute;
	sec;
};
Time t1 = { 14,56,30 }; //将t1初始化为14:56:30
  • 这种情况和结构体变量的初始化是类似的,在一个花括号内顺序列出各公用数据成员的值,两个值用逗号分隔。
  • 但是,如果数据成员是私有的,或者类中有private或protected的数据成员,就不能用这种方法初始化

用构造函数实现数据成员的初始化

  • 为了解决上述问题,C++提供了构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行
  • 构造函数的名字必须与类名同名,而不能任意命名,以便编译系统能识别它并把它作为构造函数处理。它不具有任何类型,不返回任何值。
  • 构造函数的定义形式为:
类名(形式参数列表)
{
	函数体
}
  • 定义默认构造函数的一般形式为:
类名()
{
	函数体
}

例:

#include<iostream>
using namespace std;
class Time //声明Time类
{
public: //以下为公用函数
	Time() //定义构造成员函数,函数名与类名相同
	{
		hour = 0; //利用构造函数对对象中的数据成员赋初值
		minute = 0;
		sec = 0;
	}
	void set_time(); //成员函数声明
	void show_time(); //成员函数声明
private: //以下为私有数据
	int hour;
	int minute;
	int sec;
};

void Time::set_time() //定义成员函数,向数据成员赋值
{
	cin >> hour;
	cin >> minute;
	cin >> sec;
}

void Time::show_time() //定义成员函数,输出数据成员的值
{
	cout << hour << ":" << minute << ":" << sec << endl;
}

int main()
{
	Time t1; //建立对象t1,同时调用构造函数t1.Time()
	t1.set_time(); //对t1的数据成员赋值
	t1.show_time(); //显示t1的数据成员的值
	Time t2; //建立对象t2,同时调用构造函数t2.Time()
	t2.show_time(); //显示t2的数据成员的值
	return 0;
}

运行结果:

程序分析:

  • 在类中定义了构造函数Time,它和所在的类同名。在建立对象时会自动执行构造函数,根据构造函数Time的定义,其作用是对该对象中的全部数据成员赋予初值0。
  • 不要认为是在声明时直接对程序数据成员赋初值(那是不允许的),赋值语句是写在构造函数的函数体中,只有在调用构造函数时才执行这些赋值语句,对当前对象中的数据成员赋值。
  • 程序运行时首先建立对象t1,在执行构造函数过程中对t1中的数据成员赋予初值0,然后再执行主函数中的t1.set_time函数,从键盘输入新值赋给对象t1的数据成员,再输出t1的数据成员的值。接着建立对象t2,同时对t2中的数据成员赋予初值0,但对t2的数据成员不再赋予新值,直接输出数据成员的初值。
  • 上面是在类内定义构造函数的,也可以只在类内对构造函数进行声明而在类外定义构造函数。
#include<iostream>
using namespace std;
class Time //声明Time类
{
public: //以下为公用函数
	Time(); //对构造函数进行声明
	void set_time(); //成员函数声明
	void show_time(); //成员函数声明
private: //以下为私有数据
	int hour;
	int minute;
	int sec;
};

void Time::set_time() //定义成员函数,向数据成员赋值
{
	cin >> hour;
	cin >> minute;
	cin >> sec;
}

//定义成员函数,输出数据成员的值
void Time::show_time() //在类外定义构造成员函数,要加上类名Time和域限定符"::"
{
	cout << hour << ":" << minute << ":" << sec << endl;
}

Time::Time() //在类外定义构造函数
{
	hour = 0; //利用构造函数对对象中的数据成员赋初值
	minute = 0;
	sec = 0;
}

int main()
{
	Time t1; //建立对象t1,同时调用构造函数t1.Time()
	t1.set_time(); //对t1的数据成员赋值
	t1.show_time(); //显示t1的数据成员的值
	Time t2; //建立对象t2,同时调用构造函数t2.Time()
	t2.show_time(); //显示t2的数据成员的值
	return 0;
}

说明:

  • 在建立类对象时会自动调用构造函数。在建立对象时系统为该对象分配存储单元,此时执行构造函数,就把指定的初值送到有关数据成员的存储单元中。每建立一个对象,就调用一次构造函数。在上面的程序中,在主函数中定义了一个对象t1,在此时,就会自动调用t1对象中的构造函数Time,使各数据成员的值为0
  • 构造函数没有返回值,因此也没有类型,它的作用只是对对象进行初始化。因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要的不同之点。不能写成int Time(){ ... }void ? time(){ ... }
  • 构造函数不需用户调用,也不能被用户调用。下面用法是错误的:t1.Time(); //试图用调用一般成员函数的方法调用构造函数
    构造函数是在定义对象时由系统自动执行的,而且只能执行一次。构造函数一般声明为public
  • 可以用一个类对象初始化另一个类对象,如Time t1; //建立对象t1,同时调用构造函数t1.Time() Time t2=t1; //建立对象t2,并用一个t1初始化t2此时,把对象t1的各数据成员的值复制到t2相应各成员,而不调用构造函数t2.Time()
  • 在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句,例如cout语句。但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰
  • 如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行初始化
  • 与其他任何函数一样,构造函数可以声明为内置函数

带参数的构造函数

  • 构造函数不带参数的方式使该类的每一个对象的数据成员都得到同一组初值。但有时用户希望对不同的对象赋予不同的初值,这时就可以使用带参数的构造函数。在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。
  • 构造函数首部的一般形式为:构造函数名(类型1 形参1,类型2 形参2,...)
  • 前面已说明:用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参(如fun(a,b);)。实参是在定义对象时给出的。
  • 定义对象的一般形式为:类名 对象名(实参1,实参2,...);在建立对象时把实参的值传递给构造函数相应的形参,把它们作为数据成员的初值。

例:有两个长方柱,其高、宽、长分别为(1)12,20,25;(2)10,14,20。求它们的体积。编写一个基于对象的程序,在类中用带参数的构造函数对数据成员初始化

#include<iostream>
using namespace std;
class Box //声明Box类
{
public:
	Box(int, int, int); //声明带参数的构造函数
	int volume(); //声明计算体积的函数
private:
	int height; //高
	int width; //宽
	int length; //长
};

Box::Box(int h, int w, int len) //在类外定义带参数的构造函数
{
	height = h;
	width = w;
	length = len;
}

int Box::volume()  //定义计算体积的函数
{
	return(height * width * length);
}

int main()
{
	Box box1(12, 25, 30); //建立对象box1,并指定box1的高、宽、长的值
	cout << "The volume of box is " << box1.volume() << endl;
	Box box2(15, 30, 21); //建立对象box2,并指定box2的高、宽、长的值
	cout << "The volume of box2 is " << box2.volume() << endl;
	return 0;
}

运行结果:

程序分析:

  • 构造函数Box有3个参数(h,w,l),分别代表高、宽、长。在主函数中定义对象box1时,同时给出函数的实参12,25,30。然后在cout语句中调用函数box1.volume(),并输出box1的体积。对box2也类似。
  • 注意:定义对象的语句形式是:Box box1(12,25,30);
    可以知道:
    1.带参数的构造函数中的形参,其对应的实参是在建立对象时给定的。即在建立对象时同时指定数据成员的初值
    2.定义不同对象时用的实参是不同的,他们反映不同对象的属性。用这种方法可以方便地实现对不同对象进行不同的初始化

用参数初始化表对数据成员初始化

  • 上述介绍的是在构造函数的函数体内通过赋值语句对数据成员实现初始化。C++还提供另一种初始化数据成员的方法——参数初试化表来实现对数据成员的初始化。这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
  • 如上例中定义构造函数可以改用以下形式:Box::Box(int h,int w,int len):height(h),width(w),length(len){}
    即在原来函数首部的末尾加一个冒号,然后列出参数的初始化表。上面的初始化表表示:用形参h的值初始化数据成员height,用形参w的值初始化数据成员width,用形参len的值初始化数据成员length。后面的花括号是空的,即函数体是空的,没有任何执行语句。这种形式的构造函数的作用和上例中在类外定义的Box构造函数相同。
  • 用参数的初始化表法可以减少函数体的长度,使结构函数显得精炼简单。这样就可以直接在类体中(而不是在类外)定义构造函数。尤其当需要初始化的数据成员较多时更显其优越性。
  • 带有参数初始化表的构造函数的一般形式如下:
类名::构造函数名([参数表])[:成员初始化表]
{
	[构造函数体] //其中方括号内为可选项(可有可无)
}
  • 说明:如果数据成员是数组,则应当在构造函数的函数体中用语句对其赋值,而不能在参数初始化表中对其初始化。

例:

class Student
{
public:
	Student(int n, char  s, nam[]) :num(n), sex(s) //定义构造函数
	{
		strcpy(name, mam); //函数体
	}
private:
	int num;
	char sex;
	char name[20];
};
  • 可以这样定义对象stud1:Student stud1(10101,'m',"Wang_li");
  • 利用初始化表,把形参n得到的值10101赋给私有数据成员num,把形参s得到的值’m’赋给sex,把形参数组nam的各元素的值通过strcpy函数复制到name数组中。这样对象stud1中所有的数据成员都初始化了,此对象是有确定内容的。
  • 有时必须用构造函数初始化列表。如果没有为类类型的数据成员提供初始化列表,编译器会隐式地使用该成员的默认构造函数。如果那个类没有默认构造函数,则编译器会报告错误。在这种情况下,为了初始化类类型的数据成员,必须提供初始化列表。
  • 一般地,没有默认构造函数的成员,以及const或引用类型的成员,都必须在构造函数初始化列表中进行初始化。

构造函数的重载

  • 在一个类中可以定义多个构造函数,以便为对象提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。这称为构造函数的重载

例:定义两个构造函数,其中一个无参数,一个有参数

#include<iostream>
using namespace std;
class Box
{
public:
	Box(); //声明一个无参的构造函数Box
	Box(int h, int w, int len) :height(h), width(w), length(len) {} //定义一个有参的构造函数,用参数的初始化表对数据成员初始化
	int volume(); //声明成员函数volume
private:
	int height;
	int width;
	int length;
};

Box::Box() //在类外定义无参构造函数Box
{
	height = 10;
	width = 10;
	length = 10;
}

int Box::volume() //在类外定义声明成员函数volume
{
	return(height * width * length);
}

int main()
{
	Box box1; //建立对象box1,不指定实参
	cout << "The volume of box1 is " << box1.volume() << endl;
	Box box2(15, 30, 25); //建立对象box2,指定3个实参
	cout << "The volume of box2 is " << box2.volume() << endl;
	return 0;
}

运行结果:

程序分析:

  • 在类中声明了一个无参数构造函数Box(),在类外定义的函数体中对私有数据成员赋值。第2个构造函数是直接在类体中定义的,用参数初始化表对数据成员初始化,函数有3个参数,需要3个实参与之对应。这两个构造函数同名(都是Box),编译系统是根据函数调用的形式去确定对应哪一个构造函数。
  • 在主函数中,建立对象box1时没有给出参数,系统找到与之对应的无参构造函数Box,执行此构造函数的结果是使3个数据成员的值均为10。然后输出box1的体积。建立对象box2时给出3个实参,系统找到有3个形参的构造函数Box与之对应,执行此构造函数的结果是使3个数据成员的值为15,30,25。然后输出box2的体积。

说明:
1. 在建立对象时不必给出实参的构造函数,称为默认构造函数。显然,无参构造函数属于默认构造函数。一个类只能有一个默认构造函数。如果用户未定义构造函数,则系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。如果用户希望在创建对象时就能使数据成员有初值,就必须自己定义构造函数。
2.如果在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。如本程序中有以下定义对象的语句:Box box1; //建立对象的正确形式
注意不要写成Box box1(); //建立对象的错误形式,不应该有括号。该语句并不是定义Box类的对象box1,而是声明一个普通函数box1,此函数的返回值为Box类型。在程序中不应出现调用无参构造函数(如Box()),请记住:构造函数时不能被用户显式调用的。
3.尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行

使用默认参数的构造函数

  • 构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。

例:将以上程序中的构造函数改用含默认值的参数,宽、高、长的默认值均为10

#include<iostream>
using namespace std;
class Box
{
public:
	Box(int h = 10, int w = 10, int len = 10); //在声明构造函数时指定默认参数
	//在声明构造函数时指定默认参数,形参名可以省略,即写成Box(int =10;int =10;int =10);
	int volume();
private:
	int height;
	int width;
	int length;
};

Box::Box(int h, int w, int len) //在定义函数时可以不指定默认参数
{
	height = h;
	width = w;
	length = len;
}  
/*对构造函数的定义,也可以改写成参数初始化表的形式:
Box::Box(int h,int w,int len):height(h),width(w),length(len){}
*/

int Box::volume()
{
	return(height * width * length);
}

int main()
{
	Box box1; //没有给实参
	cout << "The volume of box1 is " << box1.volume() << endl;
	Box box2(15); //只给定一个实参
	cout << "The volume of box2 is " << box2.volume() << endl;
	Box box3(15, 30); //只给定两个实参
	cout << "The volume of box3 is " << box3.volume() << endl;
	Box box4(15, 30, 20); //只给定三个实参
	cout << "The volume of box4 is " << box4.volume() << endl;
	return 0;
}

运行结果:

程序分析:

  • 可以看到,在构造函数中使用默认参数是方便有效的,它提供了建立对象时的多种选择,它的作用相当于好几个重载的构造函数。它的好处是:即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对每一个对象都有同样的初始化的状况的时候用这种方法更为方便。不需输入数据,对象会按事先指定的值进行初始化。

说明:
1.应在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。因为类声明是放在头文件中的,它是
类的对外接口,用户是可以看到的,而函数的定义是类的实现细节,用户往往看不到的。在声明构造函数时指定默认参数值,使用户知道在建立对象时怎样使用默认参数。
必须在类的内部指定构造函数的默认参数,不能在类外部指定默认参数
例:(❌)

#include<iostream>
using namespace std;
class Point
{
public:
	Point(int a, int b);
	void display()
	{
		cout << "x=" << x << ",y=" << y << endl;
	}
private:
	int x, y;
};
Point::Point(int a = 0, int b = 0) //错误,不能在类外指定默认参数
{
	x = 0, y = 0;
}
  1. 在声明构造函数时,形参名可以省略
  2. 如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。由于不需要实参也可以调用构造函数,因此全部参数都指定了默认值的构造函数也属于默认构造函数。前面曾提到过:一个类只能有一个默认构造函数,也就是说,可以不用参数而调用构造函数,一个类只能有一个。其道理是显然的,是为了避免调用时的歧义性。如果同时定义了下面两个构造函数,是错误的。
  3. 在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值