【C++】C++11——列表初始化及decltype

统一的列表初始化

注意这里的列表初始化 和 初始化列表是两个性质哦

{ }的初始化

C++98的时候,我们的大括号只准许到了对数组或者结构体元素进行统一的列表初始化。例如

//结构体
struct Point
{
	int _x;
	int _y;
};

int main()
{
	//使用大括号对数组进行元素初始化
	int a[] = { 1,2,3,4 };
	int b[4] = { 0 };

	//使用大括号对结构体进行初始化
	Point c = { 1,2 };

	return 0;
}

在这里插入图片描述

C++11 扩大了用大括号括起来的列表{}初始化的范围,使其可用与于所有的 内置类型自定义类型 使用列表初始化的时候,可以添加等号,也可以不添加等号,例如:

内置类型

//结构体
struct Point
{
	int _x;
	int _y;
};

int main()
{
	//内置类型
	int x1 = { 1 };//可以使用大括号
	int x2{ 2 };//不适用大括号也是可以的

	int a[]{ 1,2,3 };//不适用大括号也可以初始化数组
	int b[3]{ 0 };

	//C++11中列表初始化也可以用于new表达式中(C++98中是无法这样初始化的)
	int* p = new int[4]{ 1,2,3,4 };//不可添加等号
	Point{ 1,2 };
	return 0;
}

在这里插入图片描述
注意: 用大括号对new表达式初始化时不能加等号。

自定义类型
创建对象时也可以使用列表初始化方式调用构造函数初始化。比如:

//自定义类型
class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//在C++98中我们这样使用的
	Date d1(2023, 9, 20);

	//在C++11中,有增加了新的形式
	Date d2 = { 2023,9,19 };
	Date d3{ 2023,9,21 };
	return 0;
}

在这里插入图片描述

initializer_list容器

initializer_list的出现让很多容器都支持了{}的列表初始化

C++11中新添加了initialize_list容器,该容器没有提供过多的成员函数

  • 提供了 begin 和 end 函数,用于支持迭代器遍历。
  • 以及 size 函数支持获取容器中的元素个数。

在这里插入图片描述

当我们使用auto进行定义一个变量,来进行接收列表初始化过的变量时,我们发现它的类型为initializer_list类型,使用typeid(变量名).name()的方式查看该变量的类型。

int main()
{
	auto a = { 1,2,3 };
	cout << typeid(a).name() << endl;
}

在这里插入图片描述

initializer_list的使用场景

initializer_list容器没有提供对应的增删查改等接口,因为initializer_list并不是专门用于存储数据的,而是为了让其他容器支持列表初始化的。比如:

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//用大括号括起来的列表对容器进行初始化
	vector<int> v = { 1, 2, 3, 4, 5 };
	list<int> l = { 10, 20, 30, 40, 50 };
	vector<Date> vd = { Date(2022, 8, 29), Date{ 2022, 8, 30 }, { 2022, 8, 31 } };
	map<string, string> m{ make_pair("sort", "排序"), { "insert", "插入" } };

	//用大括号括起来的列表对容器赋值
	v = { 5, 4, 3, 2, 1 };
	return 0;
}

C++98并不支持直接用列表对容器进行初始化,这种初始化方式是在C++11引入initializer_list后才支持的。

而这些容器之所以支持使用列表进行初始化,根本原因是因为C++11给这些容器都增加了一个构造函数,这个构造函数就是以initializer_list作为参数的。
在这里插入图片描述

initializer_list使用示例

如果要让我们之前模拟实现的vector支持列表初始化,就需要增加一个以initializer_list作为参数的构造函数。比如:

namespace cl
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		vector(initializer_list<T> il)
		{
			_start = new T[il.size()];
			_finish = _start;
			_endofstorage = _start + il.size();
			//迭代器遍历
			//typename initializer_list<T>::iterator it = il.begin();
			//while (it != il.end())
			//{
			//	push_back(*it);
			//	it++;
			//}
			//范围for遍历
			for (auto e : il)
			{
				push_back(e);
			}
		}
		vector<T>& operator=(initializer_list<T> il)
		{
			vector<T> tmp(il);
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
			return *this;
		}
	private:
		iterator _start;//指向头
		iterator _finish;//指向有效数据的尾
		iterator _endofstorage;//指向容器的尾
	};
}

注意:

  • 在构造器中遍历initializer_list时可以使用迭代器遍历,也可以使用范围for遍历,因为范围for底层实现就是采用的迭代器进行实现的。
  • 使用迭代器方式遍历时,需要在迭代器类型前面加上typename关键字,指明这是一个类型名字。因为这个迭代器类型定义在一个类模板中,在该类模板未被实例化之前编译器是无法识别这个类型的。
  • 最好也增加一个以initializer_list作为参数的赋值运算符重载函数,以支持直接用列表对容器对象进行赋值,但实际也可以不增加。

如果没有增加以initializer_list作为参数的赋值运算符重载函数,下面的代码也可以正常执行:

vector<int> v = { 1, 2, 3, 4, 5 };
v = { 5, 4, 3, 2, 1 };
  • 对于第一行代码,就是调用以initializer_list作为参数的构造函数完成对象的初始化。
  • 而对于第二行代码,会先调用initializer_list作为参数的构造函数构造出一个vector对象,然后再调用vector原有的赋值运算符重载函数完成两个vector对象之间的赋值。

auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。

C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。比如:

int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;

	cout << typeid(p).name() << endl;  //int *
	cout << typeid(pf).name() << endl; //char * (__cdecl*)(char *,char const *)

	map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();  //简化代码

	return 0;
}

typeid(变量名).name()

typeid(变量名).name() 函数返回一个字符串,表示指定变量类型的名称。这个函数的行为在 C++98 和 C++11 标准中是有所不同的。

在 C++98 标准下,typeid(变量名).name() 返回的是一个类型的可读名称,但是这个名称的具体格式没有被标准定义,因此可能在不同的编译器中有所差异。

而在 C++11 标准中,typeid(变量名).name() 的行为发生了变化。根据 C++11 的文档,这个函数返回的是一个 const char*,指向一个以 null 终止的字符串,表示类型的限定名。限定名的格式是实现定义的,因此可能在不同编译器和平台上有所不同。

注意: 通过typeid(变量名).name()的方式可以获取一个变量的类型,但无法用获取到的这个类型去定义变量。

decltype

关键字decltype将变量的类型声明为表达式指定的类型。

decltype就是一种类型说明符,它的出现主要是解决复杂的类型声明。

decltype并不会实际计算表达式的值,编译器分析表达式并得到它的类型。

工作原理

  • decltype + 变量 var
  • decltype + 表达式 expr
  • decltype + 函数名 func_name

decltype除了能够推演表达式的类型,还能推演函数返回值的类型。比如:

void F(T1 a,T2 b)
{
	decltype(a + b) ret =  a + b;
	cout << typeid(ret).name() << endl;
}

void F2(int a)
{
	
}

int main()
{
	const int x = 1;
	double y = 2.2;

	decltype(x * y) ret;
	decltype(&x) p;
	cout << typeid(ret).name() << endl;//double
	cout << typeid(p).name() << endl;//int const *

	F(1, 'a');//int
	F(1, 2.2);//double

	//推演函数返回值的类型
	cout << typeid(decltype(F2)).name() << endl;
	cout << typeid(decltype(F2(1))).name() << endl;
	return 0;
}

在这里插入图片描述
decltype不仅可以指定定义出的变量类型,还可以指定函数的返回类型。比如:

template<class T1, class T2>
auto Add(T1 t1, T2 t2)->decltype(t1+t2)
{
	decltype(t1+t2) ret;
	ret = t1 + t2;
	cout << typeid(ret).name() << endl;
	return ret;
}
int main()
{
	cout << Add(1, 2) << endl;;   //int
	cout << Add(1, 2.2) << endl;; //double
	return 0;
}