7.5 构造函数再探

构造函数初始值列表

当对象被默认初始化或值初始化时,自动执行默认构造函数:

默认初始化:

不使用初始值定义一个非静态变量或数组;
一个类本身含有类成员,且使用合成默认构造函数
类类型成员采用赋值的方法赋初值

构造函数的初始值有时候必不可少

有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。如果成员是const或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。

如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。

初始值列表:

#include<iostream>
using namespace std;

class A
{
public:
	A(int,int);
private:
	int i1;
	const int i2;//必须进行初始化。
	int& i3;//必须进行初始化。
};

//A::A(int j1,int j2)//运行到此行结束代表着初始化了,所以后续的代码没办法进行初始化。需要用带初始化列表。
//{
//	i1 = j1;
//	i2 = j2;
//	i3 = i1;
//}

A::A(int j1,int j2):i1(j1),i2(j2),i3(i1)//初始化列表
{}

int main() 
{
	
	return 0;
}

成员初始化的顺序

同时进行初始化的,对于初始化列表来说。

class A
{
public:
	A(int);
private:
	int i1;
	int i2;
};

A::A(int j1):i1(j1),i2(i1)//由于i2和i1的顺序未定义,常常是同时的,所以此处可能会报错。
{}

默认实参和构造函数

如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。

委托构造函数

C++11新标准扩展了构造函数初始值的功能,使得我们可以定义所谓的委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。

和其他构造函数一样,一个委托构造函数也有一个成员初始值的列表和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始的一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另外一个构造函数匹配。

class A
{
public:
	//非委托构造函数
	A(double i1,int i2,string i3): i1(i1),i2(i2),i3(i3){}
	//委托构造函数
	A( string i3): A(0.0,0,i3){}
private:
	double i1;
	int i2;
	string i3;
};

默认构造函数的作用

当对象被默认初始化或值初始化时自动执行默认构造函数。

默认初始化在以下情况下发生:

  1. 当我们在块作用域内不使用任何初始值定义一个非静态变量或者数组时。
  2. 当一个类本身含有类类型的成员且使用合成的默认构造函数时。
  3. 当类类型的成员没有在构造函数初始值列表中显式地初始化时。

值初始化在以下情况下发生:

  1. 在数组初始化的过程中如果我们提供的初始值数量少于数组的大小时。
  2. 当我们不使用初始值定义一个局部静态变量时。
  3. 当我们通过书写形如T( )的表达式显式地请求值初始化时,其中T是类型名。

使用默认的构造函数

在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。

隐式的类类型转换

当构造函数只接收一个实参时,可以进行此类类型的隐式转换。

类型转换只能进行一步

函数声明explicit,抑制构造函数定义的隐式转换,此时拷贝形式初始化也失效了。

#include<iostream>
using namespace std;

class A
{
public:
	//非委托构造函数
	A(double i1,int i2,string i3): i1(i1),i2(i2),i3(i3){}
	//委托构造函数
	A( string i3): A(0.0,0,i3){}

	const A& fun(const A& a) const { return *this; }

private:
	double i1;
	int i2;
	string i3;
};

int main() 
{
	string pp = " world";
	A a("hello");
	//const A& fun(const A& a) const { return *this; }
	//可以进行,pp是一个string类型转化为A类型,一个最简单的隐式转化。
	a.fun(pp);
	//不可以进行,因为隐式转化仅能转化一步。
	//第一步将" world"转化为string类型
	//第二步需要将string类型转化为A类型,所以不行。
	a.fun(" world");
	//可以运行,原因可见上
	a.fun(string(" world"));
	//可以进行
	a.fun(A(" world"));
	return 0;
}

explicit抑制构造函数的隐式转化

#include<iostream>
using namespace std;

class A
{
public:
	//非委托构造函数
	A(double i1,int i2,string i3): i1(i1),i2(i2),i3(i3){}
	//委托构造函数,声明explicit抑制隐式转化
	explicit A( string i3): A(0.0,0,i3){}

	const A& fun(const A& a) const { return *this; }

private:
	double i1;
	int i2;
	string i3;
};

int main() 
{
	string pp = " world";
	A a("hello");
	//explicit A( string i3): A(0.0,0,i3){}
	//const A& fun(const A& a) const { return *this; }
	//可以进行,pp是一个string类型转化为A类型,一个最简单的隐式转化。
	//现在进行不了了,没办法在构造函数处进行隐式转化。
	a.fun(pp);
	//不可以进行,因为隐式转化仅能转化一步。
	//第一步将" world"转化为string类型
	//第二步需要将string类型转化为A类型,所以不行。
	a.fun(" world");
	//无法进行转化
	a.fun(string(" world"));
	//可以进行,手动跳过转化过程。
	a.fun(A(" world"));
	//可以转化
	a.fun(static_cast<A>(" world"));
	return 0;
}

标准函数中含有显示构造函数的类

我们用过的一些标准库中的类含有单参数的构造函数:

  1. 接受一个单参数的const char*的string构造函数不是explicit的。
  2. 接受一个容量参数的vector构造函数是explicit的。

聚合类

聚合类使用户可以直接访问其成员,并具有特殊初始化语法形式,满足如下条件:

所有成员都是public;
没有定义任何构造函数;
没有类内初始值;
没有基类和virtual函数;

可以通过初始化列表进行聚合类初始化,此时初始值顺序必须与声明顺序一致,未初始化部分会被值初始化。

#include<iostream>
using namespace std;

class A {
public:
	int i1;
	int i2;
	string i3;

};
int main() {
	A a = {60,90,"hello world"};
	cout << h1.i3 << endl;
	return 0;
}

字面值常量类

数据成员都是字面值类型的聚合类是字面值常量类。

数据成员必须是字面值类型;
类必须至少含有一个constexpr构造函数;
含有的类内初始值必须是常量表达式;
如果成员属于类类型,必须使用成员自己的constexpr构造函数。
类必须使用析构函数默认定义,该成员负责销毁类的对象。

constexpr 构造函数

样例后续补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值