北京海淀网站制作公司,襄阳seo优化排名,旅游网站建设方案简介,哪个网站做美食视频网站前言
本期我们接着继续介绍C11的新特性#xff0c;本期我们介绍的这个新特性是很多人都感觉抽象的语法#xff01;它就是可变的模板参数#xff01;
目录
前言
一、可变的模板参数
1.1可变的参数列表
1.2可变的参数包
1.3可变参数包的解析
• 递归展开解析
• 逗号…前言
本期我们接着继续介绍C11的新特性本期我们介绍的这个新特性是很多人都感觉抽象的语法它就是可变的模板参数
目录
前言
一、可变的模板参数
1.1可变的参数列表
1.2可变的参数包
1.3可变参数包的解析
• 递归展开解析
• 逗号表达式解析
二、emplace系列接口 一、可变的模板参数
1.1可变的参数列表
对于可变参数列表我们在C语言就见过例如最典型的就是 printf / scanf 了 我们在使用的时候需要指定类型将不同类型/数量的类型都可以传递过去
int main()
{int a;double b;char c;printf(请输入一个整数、一个浮点数和一个字符: );scanf(%d %lf %c, a, b, c);printf(输入了: %d %lf %c\n, a, b, c);return 0;
} 1.2可变的参数包
C98/03类模板和函数模板中只能含固定的模板参数C11引入了可变的模板参数能够让你创建接受可变参数的函数模板和类模板这无疑是一个巨大的改进正是改变巨大所以这块比较抽象也比较晦涩难懂本博客主要介绍的是函数模板的可变参数
先来见一见基本的可变参数的函数模板
template class ...Args
void ShowList(Args... args)
{// ...
}
此时我们就可以给这个函数模板传递任意类型、任意个数的参数把这个前面带 ... 的参数统称为参数包Args是模板的参数包args是函数形参的参数包Args... args的个数是0~N个 一般为了提高传递参数的效率可变参数的类型一般会被写成万能引用引用折叠的形式
template class ...Args
void ShowList(Args... args)
{// ...
}
由于可变模板的参数是可变的即支持任意类型、任意数量的参数所以可以这样写
int main()
{ShowList(1);ShowList(1, a, 9.9);ShowList(1, aaa, 9.9, b);ShowList(std::vectorint(), std::listdouble(), zzz, 999, aaaaaaa);return 0;
}
这也体现了可变模板参数的强大
1.3可变参数包的解析
用可变参数的人爽了但是解析参数包的过程其实是不简单的下面是一种非常典型的错误栗子
templateclass ...Args
void showList(Args... args)
{// 错误的解析参数方式int n sizeof...(args);for (int i 0; i n; i){// 获取具体的可变参数args[i];}
}这里使用sizeof获取参数包args的大小是把...放到了args的前面第一次看起来的时候有点别扭~个人感觉 这种方式是符合我们的直觉的但是他是错的 这里简单理解就是编译器不支持这样写sizeof是编译时操作符而参数包不是和数组一样连续的所以不可用下标访问
• 递归展开解析
void ShowList() { cout endl; }// 用于结束递归templateclass T, class ...Args
void ShowList(const T t, Args ...args)
{cout t ;ShowList(args...);
} 这里他是如何做的呢我用一个简单的例子第三个来画一下 也就是编译器在编译期间将上述的函数模板实例化成了很多的实例在运行时进行了调用
• 逗号表达式解析
除了上述递归的展开的方式解析参数包以外还有一种直接展开的方式就是用逗号表达式解析具体如下
templateclass T
void Print(T t)
{cout t endl;
}templateclass ...Args
void ShowList(Args... args)
{int arr[] { (Print(args), 0)... };cout endl;
} OK编译后他就是这个样子
templateclass ...Args
void ShowList(Args... args)
{int arr[] { (Print(1), 0), (Print(a), 0), (Print(9,9), 0)};cout endl;
} 为什么这里要写成 (Print(args), 0)... 呢 我们知道逗号表达式的结果是最后一项的值这是主要是在展开的同时给arr数组赋值
二、emplace系列接口
C11 也在STL中引入了可变的模板参数这一批接口被称为emlpace系列的接口/函数 这里以list为例进行介绍 为了方便演示我们玩还是使用我们前几期用的cp::string所以我们把他给拿过来
namespace cp
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)), _capacity(_size){cout string(char* str) -- 构造 endl;_str new char[_capacity 1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s):_str(nullptr){cout string(const string s) -- 拷贝构造 endl;string tmp(s._str);swap(tmp);}// 赋值重载string operator(const string s){cout string operator(string s) -- 赋值拷贝 endl;string tmp(s);swap(tmp);return *this;}// 移动构造string(string s):_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动构造 endl;swap(s);}// 移动赋值string operator(string s){cout string operator(string s) -- 移动赋值 endl;swap(s);return *this;}~string(){delete[] _str;_str nullptr;}char operator[](size_t pos){assert(pos _size);return _str[pos];}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};cp::string to_string(int value){cp::string str;bool flag true;if (value 0){flag false;value 0 - value;}while (value 0){int x value % 10;value / 10;str (0 x);}if (flag false){str -;}std::reverse(str.begin(), str.end());return str;}
}
listcp::string lt;
// 分别让emplace_back和push_back都插入一个 左值 对象
cp::string s1(hello);
cout ------------------------------- endl;
lt.push_back(s1);
cout endl;
lt.emplace_back(s1);// 分别让emplace_back和push_back都插入一个 左值move后的 对象
cout ------------------------------- endl;
lt.push_back(move(s1));
cout endl;
lt.emplace_back(move(s1));// 分别让emplace_back和push_back都插入一个 右值 对象
cout ------------------------------- endl;
lt.push_back(cp::string(6666));
cout endl;
lt.emplace_back(cp::string(6666));// 分别让emplace_back插入一个参数包 push_back都插入一个右值 对象
cout ------------------------------- endl;
lt.push_back(6666);
cout endl;
lt.emplace_back(6666);
看结果 通过上面的栗子我们可以得出结论
1、当emplace和push系列都插入左值/move(左值)的结果是一样的拷贝构造/移动构造
2、当emplace插入一个参数包push系列都插入一个右值时emplace是构造而push系列是构造移动构造
• 为什么emplace系列传入参数包后是直接构造
上面介绍了emplace系列的是引用了模板的可变参数所以他可以接受的是一个参数包其实他的底层是一层层的传递下去直接到构造那里用参数包的参数直接构造的所以就只有一次构造
这其实就是有人所说的emplace效率高的原因所在就是直接使用emplace传递参数包可以少一次移动构造
所以emplace系列的插入对象的优先级为 参数包 右值 左值 这里左值的代价较高因为有一次的拷贝构造右值其实和参数包的差别也还好原因是移动构造的待见很轻但是能用参数包的地方还是用参数包以后就建议多使用emplace系列因为emplace系列push系列 的效率 OK好兄弟本期分享就到这里我是cp我们下期再见~