C++11 可变参数模板,emplace,decltype

在这里插入图片描述

一.可变参数模板

可变参数模板是C++11新增的特性之一,让我们能够创建可以接收可变参数的函数模板和类模板

1.介绍与使用

在这里插入图片描述
在这里插入图片描述

2.获取参数包中的每个参数

1.错误示范

这里的语法并不支持arg[i]的方式来获取参数包中的每个参数
只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点

以下是错误示范:

template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
	for (int i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " ";
	}
	cout << endl;
}

在这里插入图片描述
为什么会这样呢?
在这里插入图片描述
在这里插入图片描述
那么我们就是想看,该怎么办呢?

2.利用编译时递归来查看参数包

既然你在编译期间就会对这个参数包进行解析,那么我就只能在编译期间来看了呗
既然要在编译期间就看,那么就必须借助一个你编译器在编译期间就要做的事情

此刻有位大佬想到了利用模板的实例化,用魔法来打败魔法
你不是要在编译期间就去实例化吗?那么我拆分一下,再写一个模板
把你这个参数个数为n的参数包拆分成1个参数+长度为n-1的参数包
(n=1+n-1)
每次查看那一个参数,不就行了吗?
而这种方法不就能够拆分成一个一个的子问题了吗?
因此不就能够利用递归了吗?

因此这位大佬设计出了编译时递归的方法
在这里插入图片描述

//递归出口
void _ShowList()
{
	cout << endl;
}

template <class T, class ...Args >
void _ShowList(T val, Args... args)
{
	cout << val << " ";
	_ShowList(args...);
}

template <class ...Args>
void ShowList(Args... args)
{
	_ShowList(args...);
}

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,2);
	ShowList(1,2.2,4.4,"hello world");
	return 0;
}

在这里插入图片描述
查看成功,下面我们来分析一下过程
在这里插入图片描述
但是你为什么这样定义顺序呢?
好怪啊,整个递归都是往上走的
我也想让它往下走啊,可是编译器编译时只会向上找,所以要把递归子函数和递归出口写在可变参数模板的上面
在这里插入图片描述

3.利用编译时数组的初始化

其实还有1种方法:利用编译时数组的初始化来搞事情

int a[]={1,2,3,4};
对于这种定义数组的方式,编译器会在编译期间解析数组的内容
从而才能确定这个数组的大小
template <class T>
void PrintArgs(T t)
{
	cout << t << " ";
}

template <class ...Args>
void ShowList(Args... args)
{
	int a[] = { (PrintArgs(args),0)...,0 };//因为不能分配大小为0的数组,因此最后的时候加个0
	cout << endl;
}

在这里插入图片描述
刚才的可变参数模板中参数的获取在实际当中并不常用,因此大家了解即可,介绍那个主要是为了让我们更好地去理解可变参数模板这个东西

让我们知道它其实也就只是让编译器做了更多的事情而已,没什么神奇之处

可变参数模板在emplace系列的插入接口当中具有非常重要的作用和价值

二.emplace系列接口的使用

1.接口声明

我们就以list为例,先看一下STL库中emplace的接口声明
在这里插入图片描述

2.emplace与insert的对于深拷贝类的区别

我们拿出我们的string类来C++: string的模拟实现里面有我们模拟实现的string类的源代码,代码就不放过来了,太长了,大家自取

使用自己实现的是为了让我们更好地观察对象的拷贝情况)(你插入一个元素到底使用的是拷贝构造还是移动构造)

而list我们就先用一下STL库里面的,因为我们自主实现的还没有加上emplace系列接口

双方的代表:
emplace系列:emplace_back
insert系列: push_back
在这里插入图片描述
大家不要急,下面就给大家看他们的区别
在这里插入图片描述
我们知道,移动构造就是转移资源,它的消耗跟浅拷贝是一样的,
因此对于深拷贝的类来说,多一次移动构造,少一次移动构造的差别并不大
所以emplace系列的接口对于深拷贝的类来说,提升并不明显

那么浅拷贝的类呢?
有很大的提升

在介绍对于浅拷贝的类的优化之前,我们再来写一份代码,说明一下insert系列接口和emplace系列接口使用上的区别和建议
在这里插入图片描述
综上,我们建议使用emplace的时候直接传入用于构造该对象的参数即可
此时emplace会通过不断的传递可变参数包,一直传递到构造该对象的时候对该对象进行直接构造

3.emplace与insert的对于浅拷贝类的区别

在这里插入图片描述

4.小小总结

在这里插入图片描述
下面我们完善一下我们之前的vector和list的emplace相关的接口

5.vector实现emplace系列接口

在这里插入图片描述
在这里插入图片描述
至于三个点…都是出现在什么地方,大家都试一试吧…这个真不知道有啥规律
代码:

template<class ...Args>
iterator emplace(iterator pos, Args&&... args)
{
	assert(pos >= _start && pos <= _finish);
	int gap = pos - _start;
	if (_finish == _endOfStorage)
	{
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	pos = _start + gap;
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		--end;
	}
	*pos = T(forward<Args>(args)...);//重点
	++_finish;
	return pos;
}

template<class ...Args>
void emplace_back(Args&&... args)
{
	emplace(end(), forward<Args>(args)...);
}

6.list实现emplace系列接口

在这里插入图片描述
在这里插入图片描述
代码:

template<class ...Args>
iterator emplace(iterator pos, Args ...args)
{
	Node* newnode = new Node(forward<Args>(args)...);
	Node* next = pos._node;
	Node* prev = next->_prev;
	newnode->_prev = prev;
	newnode->_next = next;
	prev->_next = newnode;
	next->_prev = newnode;
	++_size;
	return iterator(newnode);
}
template<class ...Args>
void emplace_back(Args ...args)
{
	emplace(end(),forward<Args>(args)...);
}
template<class ...Args>
void emplace_front(Args ...args)
{
	emplace(begin(), forward<Args>(args)...);
}

三.default,delete

在这里插入图片描述
C++98中

//不想让A类对象被拷贝
class A
{
public:
	A()
	{}
private:
	//声明为私有,且只声明不实现,就算通过友元等等方法能够使用的话,也会爆链接错误
	A(const A& aa);
	int _a = 100;
};

在这里插入图片描述
C++11中

//不想让A类对象被拷贝
class A
{
public:
	A()
	{}
	A(const A&& aa) = delete;
private:
	int _a = 100;
};

在这里插入图片描述

四.final,override

1.final修饰父类,让父类不能被继承

关键字final:用来修饰父类,让父类不能被继承
在这里插入图片描述
C++11之前的做法:
父类构造函数只声明不实现,且私有化

class A
{
private:
	A() {}
};
class B :public A
{};
int main()
{
	B b;
	return 0;
}

在这里插入图片描述

2.final修饰虚函数,表示该虚函数不能再被重写

其实这个final也可以用来修饰虚函数
final修饰虚函数,表示该虚函数不能再被重写
在这里插入图片描述
override:检查子类虚函数是否完成了对父类虚函数的重写
如果没有重写,那么编译时就会报错
在这里插入图片描述

五.decltype

decltype是根据表达式的实际类型推演出定义变量时所用的类型

template<class T1, class T2>
void C(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret = t1 * t2;
	cout << typeid(ret).name() << endl;
	cout << ret << endl;
}

int main()
{
	const int a = 1;
	double b = 12.25;
	decltype(a * b) c; // c的类型是double
	decltype(&a) p;   //p的类型是int*
	cout << typeid(c).name() << endl;
	cout << typeid(p).name() << endl;
	C(1.1, 10);//double和int运算后转为double
	return 0;
}

在这里插入图片描述
注意:
1.typeid(变量名).name()只能用来获取一个变量的类型,但是无法通过获取到的类型去定义变量
2.而decltype除了能够推演表达式的类型,还能推演函数返回值的类型

六.initializer_list

initializer_list是C++11新增的一个容器,就是我们常见的{}
在这里插入图片描述
只提供了这么几个函数,它的作用是用来对容器进行初始化的,C++11之后STL库当中的很多容器都支持了initializer_list来进行初始化
在这里插入图片描述
在这里插入图片描述
因此我们才可以这样来构造容器的对象 :
在这里插入图片描述
而我们自己的vector和list是不支持的
在这里插入图片描述
那么下面我们就实现一下吧
vector:
在这里插入图片描述
在这里插入图片描述
list:
在这里插入图片描述
在这里插入图片描述

vector(const initializer_list<T>& il)
	:_start(nullptr)
	,_finish(nullptr)
	,_endOfStorage(nullptr)
{
	for (auto& e : il)//直接范围for+push_back即可
	{
		push_back(e);
	}
}

vector<T>& operator=(const initializer_list<T>& il)
{
	vector<T> tmp(il);//直接现代写法
	swap(tmp);
	return *this;
}
list(const initializer_list<T>& il)
{
	empty_init();
	for (auto& e : il)
	{
		push_back(e);
	}
}

list<T>& operator=(const initializer_list<T>& il)
{
	list<T> tmp(il);//直接现代写法
	swap(tmp);
	return *this;
}

七.列表初始化

列表初始化也是{},C++98当中,{}只允许对数组进行初始化,C++11之后支持一切皆可用列表初始化进行初始化

最好的作用有两个:
(1)方便进行初始化了
在这里插入图片描述
(2)new的时候可以指定初始值了
在这里插入图片描述

以上就是C++11 可变参数模板,emplace,decltype的全部内容,希望能对大家有所帮助!!!

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

program-learner

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值