特殊构造函数

是C++中重要的概念,而类的实例化离不开构造函数。本文将通过实验介绍一些特殊的构造函数,以及有关构造函数的一些细碎知识点。

定义一个实验用的类

我们先定义一个myClass类,myClass类封装了一些变量成员。面对不同的参数,需要执行不同的初始化操作,因此我们定义多个构造函数。

class myClass {
private:
	int a;
	double b;
	char c;
public:
	myClass(int x, double y, char z) :a(x), b(y), c(z) {}
	myClass(char x, double y) :a(x), b(y), c(x) {}
	myClass(int x) :a(x), b(x), c(x) {}
};

委托构造函数

在myClass类中,3个构造函数各司其职,独立完成对变量成员的初始化。在C++11标准中,一个构造函数可以把自己的一部分或全部职责,委托给另一个构造函数完成,我们把提出委托的构造函数叫做委托构造函数。使用委托构造函数的格式如下:

class myClass {
private:
	int a;
	double b;
	char c;
public:
	//非委托构造函数使用初始化列表正常初始化
	myClass(int x, double y, char z) :a(x), b(y), c(z) {}
	//其余构造函数把初始化过程委托给另一个构造函数
	myClass(char x, double y) :myClass(x, y, x) {}
	myClass(int x) :myClass(x, x, x) {}
};

调用委托构造函数,会同时调用被委托的构造函数。我们在所有构造函数体内补充一些输出信息的提示语句,以研究委托构造函数的实现过程。

class myClass {
private:
	int a;
	double b;
	char c;
public:
	myClass(int x, double y, char z) :a(x), b(y), c(z) { cout << "case 1" << endl; }
	myClass(char x, double y) :myClass(x, y, x) { cout << "case 2" << endl; }
	myClass(int x) :myClass(x, x, x) { cout << "case 3" << endl; }
};

尝试传入不同的参数构造对象,观察输出的不同。

myClass test1(0, 0, 0);
//输出:
case 1

myClass test2(0, 0);
//输出:
case 1
case 2

myClass test3(0);
//输出:
case 1
case 3

分析输出结果,我们得知委托构造函数在调用另一个构造函数时,如果被委托的构造函数体内有代码,要执行完这些代码,才能执行委托者的代码。

很容易想到,委托构造函数是可以嵌套的,A委托给B,B又委托给C,而代码的执行将从底层的被委托者开始,到顶层的委托者结束。我们只更改第3个构造函数的委托对象,并调用该构造函数生成对象。

myClass(int x, double y, char z) :a(x), b(y), c(z) { cout << "case 1" << endl; }
myClass(char x, double y) :myClass(x, y, x) { cout << "case 2" << endl; }
myClass(int x) :myClass(x, x) { cout << "case 3" << endl; }

myClass test3(0);

输出结果如下:

case 1
case 2
case 3

转换构造函数

部分情况下,不同的数据类型之间也可以相互赋值,例如我们定义的myClass类:

myClass test=0;

虽然没有重载赋值运算符=,但这段代码依然可以编译通过,原因在于myClass类的第3个构造函数:

myClass(int x) :myClass(x, x) { cout << "case 3" << endl; }

该构造函数只有一个参数,且这个参数的数据类型与类本身不同,相当于根据int类型的参数生成了一个myClass类型的对象,我们称这样的构造函数为转换构造函数。顺便一提,如果唯一参数的数据类型与类本身相同,我们称为复制构造函数,这将在后文提到。

用其他数据类型为类对象赋值,实际上是通过转换构造函数实现的。

myClass test=0;
//相当于
myClass test=myClass(0);

类似于myClass test=0;这样没有直接调用转换构造函数的代码,被称为隐式类型转换。这样的行为可能产生意想不到的BUG,如果我们希望抑制构造函数的隐式转换,可以使用下面的关键字实现。

explicit关键字

explicit译为“明确的”,在构造函数前使用explicit关键字,将使得该构造函数只能用于直接初始化。我们在myClass类的第3个构造函数前使用explicit关键字,并尝试使用隐式类型转换。

explicit myClass(int x) :myClass(x, x) { cout << "case 3" << endl; }

myClass test=0;

编译器报错:不存在从int转换到myClass的适当构造函数。这时我们可以确信隐式类型转换被抑制了,如果还想调用explicit构造函数的话,只能通过强制类型转换,代码如下:

myClass test = static_cast<myClass>(0);

强制类型转换的有关知识我在上一篇文章中做了总结强制类型转换

复制构造函数

构造函数只有一个参数,且这个参数的数据类型与类本身相同,我们称这样的构造函数为复制构造函数。复制构造函数的参数是该类的常引用。和构造函数和析构函数一样,复制构造函数是默认存在的,用于同类数据的相互传递。

myClass ori(0, 0, 0);
//以下两种定义形式都调用了复制构造函数
myClass mirror(ori);
myClass mirror = ori;

如果我们不满足于默认复制构造函数的功能,可以重新编写复制构造函数,不过要注意复制构造函数也是构造函数的一种,不能有返回值。请看下例:

//在myClass中重新编写复制构造函数
myClass(myClass const&x) { cout << "copy" << endl; }
//通过复制构造函数生成对象
myClass ori(0, 0, 0);
myClass mirror(ori);
//输出:
case 1
copy

当然我们也可以为复制构造函数添加explicit关键字,来抑制同类对象的相互赋值,但这样做毫无意义。

=default关键字

在myClass类中,我们定义了3个构造函数,使得该类的默认构造函数不会被创建。这可能会导致每次定义类的对象时都要给出参数,不免有些麻烦。除了默认参数,在C++11标准中我们还有新的解决方法:显式的让编译器为我们创建默认构造函数。在myClass类定义中加入以下代码:

myClass() = default;
//在类外尝试创建默认对象
myClass test;

在类外创建默认对象,编译通过,并且没有输出任何"case 1","case 2"等提示信息,说明成功创建了默认构造函数。

需要注意的是,=default关键字只能在函数的第一个声明中创建默认的构造函数,析构函数和复制构造函数,因为只有这些特殊的成员函数才有默认定义。不要试图在其他任何地方使用=default关键字。

//错误!试图在类定义外使用=default关键字
myClass::myClass(myClass const&x) = default;

=delete关键字

既然可以显式的让编译器为我们创建默认构造函数,那么是否也可以显式的删除一个默认的构造函数呢?答案是肯定的,=delete关键字可以实现这个功能。我们尝试删除myClass类的复制构造函数:

myClass(myClass const&x) = delete;
//尝试在该类的对象直接赋值
myClass test(0, 0, 0);
myClass test1 = test;

编译器报错:无法引用函数myClass(myClass const&x)----它是已经删除的函数。

和=default关键字一样,=delete关键字只能在函数的第一个声明中删除默认的构造函数,析构函数和复制构造函数,因为只有这些特殊的成员函数才有默认定义。另外,虽然=delete关键字可以删除析构函数,但这将导致严重的错误。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值