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

2023-09-21 10:03:27

统一的列表初始化

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

{ }的初始化

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;
}
更多推荐

点灯科技实现 “ESP8266-01/01s + 继电器” 远程开关

教程视频ESP-01S继电器插座怎么使用?所需硬件继电器ESP-01S继电器插座WIFI模块esp8266-01swifi模块烧录器软件准备ArduinoIDE需安装好esp8266扩展点击下载下载并安装blinkerAPPAndroid下载:点击下载或在android应用商店搜索“blinker”下载安装IOS下载:

十几款IDEA开发必备的插件,新手必用

IDEA有很多优秀的插件,使用它们不仅大大增加了开发效率,也能给大家带来更好的coding体验。“工欲善其事必先利其器”,以下插件基本都可以通过IDEA自带的插件管理中心安装。1、CodeGlance拖动浏览代码更加方便,还有放大镜功能。2、Restfultoolkit一套RESTful服务开发辅助工具集,完美代替po

车企大军「涌进」零部件赛道,毫米波雷达市场被重估

垂直整合,是新一轮竞争周期的关键词。从芯片、传感器,到域控制器;从三电总成到智能底盘,不管是特斯拉、比亚迪,还是蔚来、零跑、哪吒等后来者,自研+自产+自销,玩法不一。比如,特斯拉率先开启智能驾驶芯片的自研,彻底奠定了在自动驾驶领域的领先地位(不受制于第三方芯片供应商)。从算法(软件甚至功能)出发设计芯片架构,成为车企自

企业直播如何实现多画面多场景切换?

企业直播如何实现多画面多场景切换?应用场景主要应用于:企业的会议直播、小型会务直播、异地讲师培训授课,实现较低成本的导播台场景切换效果(阿酷TONY注,比不上硬件导播台,但整体还可以,能达到场景画面切换效果)。轻导播客户端(版本:1.2.0)官方介绍是:轻量级导播软件,10分钟速成导播能手直播画面自由布局、多人连麦自由

路由和node环境搭建

路由和node环境搭建1.1什么是路由1.2案例实操1.2.3效果展示二、配置Node.js2.1新建两个文件夹2.2配置环境变量1.1什么是路由在计算机网络中,路由(Routing)是指根据某种算法将数据包从源节点传输到目标节点的过程。在Web开发中,路由则指的是根据URL地址的不同,将用户请求导向对应的处理程序或页

解锁前端Vue3宝藏级资料 第五章 Vue 组件应用 3( Slots )

5.4Slots我们已经了解到组件能够接收任意类型的JavaScript值作为props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。Slots可用于将Html内容从父组件传递到子组件进行显示。例如,你创建一个名为Button的组件,并且只想更改按

2个比较经典的PHP加密解密函数分享

项目中有时我们需要使用PHP将特定的信息进行加密,也就是通过加密算法生成一个加密字符串,这个加密后的字符串可以通过解密算法进行解密,便于程序对解密后的信息进行处理。最常见的应用在用户登录以及一些API数据交换的场景。笔者收录了一些比较经典的PHP加密解密函数代码,分享给大家。加密解密原理一般都是通过一定的加密解密算法,

JavaScript 基础 - 第1天笔记

JavaScript基础-第1天了解变量、数据类型、运算符等基础概念,能够实现数据类型的转换,结合四则运算体会如何编程。体会现实世界中的事物与计算机的关系理解什么是数据并知道数据的分类理解变量存储数据的“容器”掌握常见运算符的使用,了解优先级关系知道JavaScript数据类型隐式转换的特征介绍掌握JavaScript

头歌平台相关verilog练习

以下题目不具有难易程度的连续性文章目录1、三人表决电路2、多路选择器3、电路功能描述—门级原始结构4、电路功能描述—行为定义—连续赋值5、电路功能描述—行为定义—过程语句1、三人表决电路本关需要你根据所学的组合逻辑及数字电路的知识完成三人表决电路的设计,实现少数服从多数的表决规则,根据逻辑真值表和逻辑表达式完成表决功能

netty之数据读写源码阅读

数据读写write从client端的写开始看client与服务端建立完connect后可以从future里拿到连接的channel对象。这里的channel是io.netty.channel.Channel对象。调用其channel.writeAndFlush(msg);方法可以进行数据发送。writeAndFlush

Android 应用上线注意事项

将Android应用上线到GooglePlay商店需要仔细注意一系列问题,以确保应用的质量、安全性和用户体验。以下是一些在Android应用上线过程中需要注意的关键问题,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。1.开发者账号:确保你拥有有效的GooglePlay开发者账号,可

热文推荐