C++类的使用——关于构造函数的定义与注意事项

构造函数是类必备的成员函数,它进行着类的数据成员的初始化工作。如果我们没有定义构造函数,那声明类的对象时会调用系统自动生成的默认构造函数,这个函数什么也不做,通常这样做是有风险的,因为在声明对象时如果不对对象内的数据成员初始化的话,它们的值将是不确定的,这将导致不可预料的灾难。所以为了保险起见,任何时候都要定义一个默认的构造函数对对象数据成员进行初始化。我们定义一个存储数据的类list进行讲解:

默认构造函数

class list
{
public:
	list();  //默认构造函数
	~list(); //析构函数,先不考虑

	int length() { return _length; };  //返回长度
	void display();  //打印数组
private:
	int _length;  //list对象中data数组的长度
	int* data;	  //指向数组的指针
};

list::list()
{
	_length = 0;
	data = nullptr;
}

类中的各项数据成员和函数见注释,这个类中我们只定义一个默认构造函数,它构造一个空的list。通常如果数据成员较少的话我们采用成员初始化列表(member initialization list)的方式在构造函数声明或定义的时候初始化,。

//list::list():_length(0),data(NULL) {}; //声明的时候
list() :_length(0), data(nullptr) {}; //定义的时候

带参数的构造函数

但我们经常会需要对对象进行特定的初始化,如构造具有num个值为value的对象,而不使用默认构造函数。这时我们需要对构造函数进行重载(c/c++语言支持),我们定义一个接受两个参数的构造函数,一个参数num指明数组大小,一个参数value指明值。

class list
{
~
~
	list(int num, int value = 0);
~
~
}

list::list(int num, int value)
{
	if (num <= 0)
		return;
	_length = num;
	data = new int[num];
	for (int i = 0; i < num; i++)
		data[i] = value;
}

这里我们将第二个参数设为具有默认值的参数(将形参直接赋值),这样我们可以使用只传如一个参数的构造函数,这时第二个参数默认为0,它所构造的是一个存储num个值为0的对象。效果见下面代码

int main()
{
	list s1(4);
	list s2(4, 5);
	s1.display();
	s2.display();
	
	getchar();
}

输出:

元素为:
0 0 0 0
元素为:
5 5 5 5

拷贝构造函数(copy constructor和copy assignment operator)

除此之外我们也可以使用另一个对象构造,将对象构造为数据成员与另一个对象一样,比如我们经常见到这样的使用形式:

vector<int> vec1 = { 1,2,3,4,5 };
vector<int> vec2(vec1);
vector<int> vec3;
vec3 = vec1;

我们先定义一个vec1,然后使用vec1构造vec2和vec3。这三个对象各自的数据成员的值完全相同。我们自己定义的类list也可以这样使用,看下面代码

int main()
{
	list s(4,5);
	list s2(s);
	list s3 = s;
	s2.display();
	s3.display();
	
	getchar();
}

输出:

元素为:
5 5 5 5
元素为:
5 5 5 5

此时我们并未对list定义任何新的构造函数,这时s2和s3使用的是系统自动生成的copy constructor和copy assignment operator,这非常方便,我们不用编写额外的代码就可以使用这种构造方式,但有时这样使用是有风险的,我们看下面代码:

int main()
{
	list s(4,5);
	list s2(s);
	list s3 = s;
	delete &s;
	s2.display();
	s3.display();

	getchar();
}

我们可以尝试编译这段代码,会发现程序编译成功并不会提示警告,但运行后会发现程序错误,错在哪了呢?我们去看一下list内的数据成员,发现data是一个指向数组的指针,而我们在构造完s2和s3后,s2._length=s3._length=s._lengths2.data=s3.data=s.data,注意!这里的data为指针。也就是说此时s2和s3内的data指向s中的data指向的数组。而之后我们将s删除,s中data指向的数组被删除,这时s2和s3内的data指向的数组不存在,那我们再对s2和s3操作便出现错误!所以当类中存在指针时我们不可以使用系统自动生成的copy constructor和copy assignment operator,这时候我们需要自己定义copy constructor和copy assignment operator。见下面代码:

class list
{
public:
	list() :_length(0), data(nullptr) {};
	list(int num, int value = 0);
	list(const list& l); //copy constructor
	~list();

	int length() { return _length; };
	void display();
	list& operator=(const list& l); //copy assignment operator
private:
	int _length;
	int* data;
};

list::list(const list & l)
{
	_length = l._length;
	data = new int[_length]; //新开辟一块内存
	for (int i = 0; i < _length; i++)
		data[i] = l.data[i];

}

list & list::operator=(const list & l) 
{
	if (&l == this) //如果l是本对象则直接返回
		return*this;
	_length = l._length;
	data = new int[_length]; //新开辟一块内存
	for (int i = 0; i < _length; i++)
		data[i] = l.data[i];
	return *this;
}

由此可知当定义包含指针数据成员的类时,我们也需要为它定义copy constructor和copy assignment operator。当对其中指针进行赋值时,我们新开辟一块内存。另外还有一个需注意的地方,我们看下面代码:

int main()
{
	list s=3;
	s.display();
}

请问此时s调用的是constructor还是assignment operator呢,答案是调用constructor,大家可以运行一下,会发现输出和调用list s(3)相同

元素为:
0 0 0

总结

综上,构造函数主要有:

  1. 默认构造函数
  2. 带参数的构造函数
  3. copy constructor和copy assignment operator

需要注意:

  • 当我们定义类时,我们必须为类定义默认构造函数
  • 当数据成员较少时可以使用初始化成员列表,这样可以提高效率
  • 如果数据成员不包含指针,可以使用系统默认的copy constructor和copy assignment operator
  • 当数据成员内包含指针时,需要自己定义copy constructor和copy assignment operator
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值