当前位置: 首页 > news >正文

C++11(可变参数模板、新的类功能和STL中的一些变化)

C++11(可变参数模板、新的类功能和STL中的一些变化)

  • 1. 可变参数模板
    • 1.1 基本语法及原理
    • 1.2 包扩展
    • 1.3 emplace系列接口
  • 2. 新的类功能
    • 2.1 默认的移动构造和移动赋值
    • 2.2 成员变量声明时给缺省值
    • 2.3 default和delete
    • 2.4 final和override
    • 3. STL中的一些变化

1. 可变参数模板

1.1 基本语法及原理

  1. C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零或多个函数参数。
  2. template <class …Args> void Func(Args… args) {}
  3. template <class …Args> void Func(Args&… args) {}
  4. template <class …Args> void Func(Args&&… args) {}
  5. 我们⽤省略号来指出⼀个模板参数或函数参数表示的⼀个包,在模板参数列表中,class…或typename…指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后⾯跟…指出接下来表示零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表示,跟前⾯普通模板⼀样,每个参数实例化时遵循引⽤折叠规则。
  6. 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
  7. 这⾥我们可以使⽤sizeof…运算符去计算参数包中参数的个数。
template <class ...Args>
void Print(Args&&... args)
{cout << sizeof...(args) << endl; // sizeof...运算符编译时计算参数包中参数的个数
}int main()
{double x = 2.2;Print(); // 包里有0个参数Print(1); // 包里有1个参数Print(1, string("xxxxx")); // 包里有2个参数Print(1.1, string("xxxxx"), x); // 包里有3个参数return 0;
} // 原理1:编译器本质这⾥会结合引⽤折叠规则实例化出以下四个函数
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);// 原理2:更本质去看,如果没有可变参数模板,我们要实现出这样的多个函数模板才能⽀持
// 这⾥的功能,它是类型泛化基础上叠加数量变化,让我们泛型编程更灵活。
void Print();template <class T1>
void Print(T1&& arg1);template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
// ...

在这里插入图片描述

1.2 包扩展

  1. 对于⼀个参数包,我们除了能计算它的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(…)来触发扩展操作。底层的实现细节如下图所示。
  2. C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

在这里插入图片描述

// 可变模板参数
// 参数类型可变
// 参数个数可变
// 打印参数包内容
template <class ...Args>
void Print(Args... args)
{// 可变参数模板编译时解析// 下⾯是运⾏获取和解析,所以不⽀持这样⽤cout << sizeof...(args) << endl;//errfor (size_t i = 0; i < sizeof...(args); i++){cout << args[i] << " ";}cout << endl;
}

要递归推演的包扩展


void ShowList()
{// 编译时递归的终止条件cout << endl;
}//err
//template <class T, class ...Args>
//void ShowList(T x, Args... args)
//{
//	cout << x << " ";
//	
//	// 运行时条件判定,所以不能这样写
//	if (sizeof...(args) == 0)
//		return;
//
//	ShowList(args...); // 编译时递归
//}template <class T, class ...Args>
void ShowList(T x, Args... args)
{cout << x << " ";// args是N个参数的参数包// 调用ShowList,参数包的第一个传给x,剩下N-1个传给第二个参数包ShowList(args...);
}// 编译时递归推导解析函数
template <class ...Args>
void Print(Args... args)
{ShowList(args...);
}int main()
{Print();Print(1);Print(1, string("xxxxx"));Print(1, string("xxxxx"), 2.2);return 0;
}

在这里插入图片描述

void ShowList()
{cout << endl;
}void ShowList(double x)
{cout << x << " ";ShowList();
}void ShowList(string x, double x3)
{cout << x << " ";ShowList(x3);
}void ShowList(int x, string x2, double x3)
{cout << x << " ";ShowList(x2, x3);
}void Print(int x1, string x2, double x3)
{ShowList(x1, x2, x3);
}int main()
{Print(1, string("xxxxx"), 2.2);return 0;
}

在这里插入图片描述

不用递归推演的包扩展

template <class T>
const T& GetArg(const T& x)
{cout << x << " ";return x;
} //template <class T>
//int GetArg(const T& x)
//{
//	cout << x << " ";
//	// GetArg返回什么不重要,只要返回N个参数就行
//  // 因为返回的参数在Arguments中什么也不干
//	return 0;
//}template <class ...Args>
void Arguments(Args... args)
{}template <class ...Args>
void Print(Args... args)
{// 注意GetArg必须返回获得到的对象,这样才能组成参数包给Arguments// GetArg的返回值组成实参参数包,传给ArgumentsArguments(GetArg(args)...); // 这个不用递归推演,就是包扩展
}// 本质可以理解为编译器编译时,包的扩展模式将上⾯的函数模板扩展实例化为下⾯的函数
//void Print(int x, string y, double z)
//{
// Arguments(GetArg(x), GetArg(y), GetArg(z));
//}int main()
{Print(1, string("xxxxx"), 2.2);return 0;
}

在这里插入图片描述

为什么是倒序打印?

函数调用参数求值顺序​​:
在C++标准中,​​函数参数的求值顺序是未指定的​​
大多数编译器的实际实现是​​从右到左​​求值
所以实际执行可能是:GetArg(2.2)→ GetArg(string(“xxxxx”))→ GetArg(1)

1.3 emplace系列接口

  1. template <class… Args> void emplace_back (Args&&… args);
  2. template <class… Args> iterator emplace (const_iterator position, Args&&… args);
  3. C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container< T >,empalce还⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
  4. emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列
  5. 第⼆个程序中我们模拟实现了list的emplace和emplace_back接⼝,这⾥把参数包不段往下传递,最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
  6. 传递参数包过程中,如果是Args&&… args 的参数包,要⽤完美转发参数包,⽅式如下std::forward< Args >(args)… ,否则编译时包扩展后右值引⽤变量表达式就变成了左值。
// emplace系列总体而言更高效,推荐以后使用emplace系列替代insert和push系列
int main()
{std::list<bs::string> lt1;// 传左值,跟push_back一样,走拷贝构造bs::string s1("1111111111111");lt1.push_back(s1);lt1.emplace_back(s1);cout << "**********************************" << endl;// 传右值,跟push_back一样,走移动构造bs::string s2("11111111111111111");lt1.push_back(move(s2));bs::string s3("111111111111111111");lt1.emplace_back(move(s3));cout << "**********************************" << endl;// emplace_back的效率略高一筹lt1.push_back("1111111111111111111"); // 单参数构造的隐式类型转换// 直接把构造string参数包往下传,直接用string参数包构造stringlt1.emplace_back("1111111111111111111");cout << "**********************************" << endl;std::list<pair<bs::string, int>> lt2;// 传左值,跟push_back一样,走拷贝构造pair<bs::string, int> kv1("11111111111", 1);lt2.push_back(kv1);lt2.emplace_back(kv1);cout << "**********************************" << endl;// 传右值,跟push_back一样,走移动构造pair<bs::string, int> kv2("11111111111", 1);lt2.push_back(move(kv2));pair<bs::string, int> kv3("11111111111", 1);lt2.emplace_back(move(kv3));cout << "**********************************" << endl;// emplace_back的效率略高一筹lt2.push_back({ "1111111111111111111", 1 }); // 多参数构造的隐式类型转换// 花括号{},编译器会识别为initializer list,而initializer list的参数类型要相同// 所以编译器报错: “initializer list”: 不是“_Valty”的有效模板参数// lt2.emplace_back({ "1111111111111111111", 1 }); // 不支持// 参数包传下去,最后直接构造容器上的对象lt2.emplace_back("1111111111111111111", 1);cout << "**********************************" << endl;return 0;
}

在这里插入图片描述

//list.h
#pragma once//list.h
// 无关接口删除了
namespace bs
{template<class T>struct list_node{T _data;list_node<T>* _next = nullptr;list_node<T>* _prev = nullptr;//list_node(const T& val)//	:_data(val)//	, _next(nullptr)//	, _prev(nullptr)//{}//// insert中的val传到这//list_node(T&& val = T())//	// 再把val强转成右值属性//	// val会去调用bs:string的移动构造//	:_data(move(val))//	, _next(nullptr)//	, _prev(nullptr)//{}template<class... Args>list_node(Args&&... args): _data(forward<Args>(args)...), _next(nullptr), _prev(nullptr){}list_node() = default;// 这里写万能引用效果不好// 得加一个默认构造,因为我们在new Node时,// 函数模板的X是泛型,还未实例化template<class X>list_node(X&& val):_data(forward<X>(val)), _next(nullptr), _prev(nullptr){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> Self;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Self& operator++(){_node = _node->_next;return *this;}bool operator!=(const Self& it) const{return _node != it._node;}};template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}//// 左值引用//void push_back(const T& x)//{//	insert(end(), x);//}//// 右值引用//void push_back(T&& x)//{//	insert(end(), move(x));//}// 万能引用template<class X>void push_back(X&& x){insert(end(), forward<X>(x));}template<class... Args>void emplace_back(Args&&... args){emplace(end(), forward<Args>(args)...);}template<class... Args>iterator emplace(iterator pos, Args&&... args){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(forward<Args>(args)...);//prev newnode curprev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;//返回新插入节点位置的迭代器return iterator(newnode);}//iterator insert(iterator pos, const T& val)//{//	Node* cur = pos._node;//	Node* prev = cur->_prev;//	Node* newnode = new Node(val);//	//prev newnode cur//	prev->_next = newnode;//	newnode->_next = cur;//	cur->_prev = newnode;//	newnode->_prev = prev;//	++_size;//	//返回新插入节点位置的迭代器//	return iterator(newnode);//}//// 假设val具有右值属性,那val应该具有常性,不能改变//// 但是移动构造/移动赋值需要改变它,所以val必须具有左值属性//iterator insert(iterator pos, T&& val)//{//	Node* cur = pos._node;//	Node* prev = cur->_prev;//	// 既然val具有左值属性,那么要想调用移动构造//	// 要把val强转成右值属性传过去才行//	Node* newnode = new Node(move(val));//	//prev newnode cur//	prev->_next = newnode;//	newnode->_next = cur;//	cur->_prev = newnode;//	newnode->_prev = prev;//	++_size;//	//返回新插入节点位置的迭代器//	return iterator(newnode);//}// X的类型是实参传递给形参推出来的// 万能引用template<class X>iterator insert(iterator pos, X&& val){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(forward<X>(val));//prev newnode curprev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;//返回新插入节点位置的迭代器return iterator(newnode);}private:Node* _head;size_t _size = 0;};
}
// 换成我们自己写的list,方便观察
#include "list.h"int main()
{bs::list<bs::string> lt1; // 我们自己写的有一个哨兵位cout << "**********************************" << endl;// 传左值,跟push_back一样,走拷贝构造bs::string s1("1111111111111");lt1.push_back(s1);lt1.emplace_back(s1);cout << "**********************************" << endl;// 传右值,跟push_back一样,走移动构造bs::string s2("11111111111111111");lt1.push_back(move(s2));bs::string s3("111111111111111111");lt1.emplace_back(move(s3));cout << "**********************************" << endl;// emplace_back的效率略高一筹lt1.push_back("1111111111111111111"); // 单参数构造的隐式类型转换// 直接把构造string参数包往下传,直接用string参数包构造stringlt1.emplace_back("1111111111111111111");cout << "**********************************" << endl;bs::list<pair<bs::string, int>> lt2;// 我们自己写的有一个哨兵位cout << "**********************************" << endl;// 传左值,跟push_back一样,走拷贝构造pair<bs::string, int> kv1("11111111111", 1);lt2.push_back(kv1);lt2.emplace_back(kv1);cout << "**********************************" << endl;// 传右值,跟push_back一样,走移动构造pair<bs::string, int> kv2("11111111111", 1);lt2.push_back(move(kv2));pair<bs::string, int> kv3("11111111111", 1);lt2.emplace_back(move(kv3));cout << "**********************************" << endl;// emplace_back的效率略高一筹// 下面这句代码不能用万能引用,因为花括号初始化列表本身​​没有具体的类型信息​// 模板参数 x 无法被推导出来// 模板参数推导规则​:​// 1. 模板参数推导需要明确的类型信息// 2. 但{ ... }在编译期没有确定的类型// 3. 编译器不知道 x 应该是什么类型//lt2.push_back({ "1111111111111111111", 1 }); // 多参数构造的隐式类型转换// 把类型给出来就行了,或者不用万能引用,用一个左值和一个右值引用// 编译器底层就是用一个左值和一个右值引用lt2.push_back(std::pair<bs::string, int>("11111111111111111111", 1));// 花括号{},编译器会识别为initializer list,而initializer list的参数类型要相同// 所以编译器报错: “initializer list”: 不是“_Valty”的有效模板参数// lt2.emplace_back({ "1111111111111111111", 1 }); // 不支持// 参数包传下去,最后直接构造容器上的对象lt2.emplace_back("1111111111111111111", 1);cout << "**********************************" << endl;return 0;
}

在这里插入图片描述

2. 新的类功能

2.1 默认的移动构造和移动赋值

  1. 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
  2. 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造,没有实现就调⽤拷⻉构造。
  3. 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动赋值函数,对于内置类型成员会执⾏逐成员按字节移动,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)
  4. 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。

2.2 成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表⽤的,如果没有显式在初始化列表初始化,就会在初始化列表⽤这个缺省值初始化

2.3 default和delete

C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤default关键字显式指定移动构造⽣成。

如果想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明不定义,这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。

2.1 / 2.2 / 2.3

class Person
{
public:Person(const char* name = "", int age = 0): _name(name), _age(age){}// 委托构造Person(int i, const char* name = "", int age = 0):Person(name, age){_i = i;}// 强制不让生成,不期望这个类的对象被拷贝//Person(const Person& p) = delete;//Person(const Person& p)//:_name(p._name)//,_age(p._age)//{}// 强制生成//Person(Person&& p) = default;//Person& operator=(const Person& p)//{//	if(this != &p)//	{//		_name = p._name;//		_age = p._age;//	}//	return *this;//}//~Person()//{}private:bs::string _name;int _age;int _i = 0;
};int main()
{Person s1;Person s2 = s1;Person s3 = move(s1);Person s4;s4 = move(s2);
}

2.4 final和override

我截图了C++多态对final和override的描述,具体可以去C++多态文章里了解。

在这里插入图片描述

3. STL中的一些变化

  1. 下图圈起来的就是STL中的新容器,但是实际最有⽤的是unordered_map和unordered_set。这两个我们前⾯已经进⾏了⾮常详细的讲解,其他的⼤家了解⼀下即可。
  2. STL中容器的新接⼝也不少,最重要的就是右值引⽤和移动语义相关的push/insert/emplace系列接⼝和移动构造和移动赋值,还有initializer_list版本的构造等,这些前⾯都讲过了,还有⼀些⽆关痛痒的如cbegin/cend等需要时查查⽂档即可。
  3. 容器的范围for遍历,这个在容器部分也讲过了。

在这里插入图片描述

http://www.dtcms.com/a/478065.html

相关文章:

  • 医疗运营管理系统编程可靠性、安全性与动态性融合路径
  • 昂瑞微——以创新驱动未来,用芯连接世界
  • 网站承建商有哪些济南seo外贸网站建设
  • Flink1.20 CEP【水位线异常原因深度分析】
  • 30个酷炫HTML+CSS特效源码
  • vtkGaussianBlurPass代码解析
  • 网站制作过程合理的步骤是福州网站推广优化
  • 牛客算法基础noob71 学生综合评估系统
  • 如何清除 Yarn 缓存 ?
  • 做听书网站怎么做用动易建设网站教程
  • 东丽开发区做网站公司响应式网站源码下载
  • RabbitMQ为什么使用AMQP协议
  • 阜新本地网站建设平台百度竞价推广价格
  • Linux 系统启动过程
  • 多制式基站综合测试线的架构与验证实践 (2)
  • 如何阿里巴巴网站做推广方案沈阳妇科哪个医院比较专业
  • 合肥网站seo优化排名手机端网站首页怎么做
  • AI人工智能-机器学习-第一周(小白)
  • 【开题答辩过程】以《基于SpringBoot和Vue框架的智能宠物之家系统的设计与实现》为例,不会开题答辩的可以进来看看
  • 告别“手绘序列帧”:Substance Designer中的程序化VFX材质工作流
  • 网站策划与建设阶段的推广的目标办公空间设计网站
  • Ubuntu 24.04.3 LTS 设置静态IP
  • Spring 框架@Transactional注解,事务的各个传播行为的逻辑以及使用场景。
  • 福建巢网站建设chinacd小说wordpress
  • 轻松搭建RTMP推流、WebRTC拉流服务器SRS服务,源码编译安装
  • Linux内核架构浅谈26-Linux实时进程调度:优先级反转与解决方案
  • 企业官方网站建设目的网站基础知识
  • 天水市建设局企业注册网站紧急访问升级狼人通知
  • 计算机视觉进阶教学之颜色识别
  • 实战任务二:用扣子空间通过任务提示词制作精美PPT