【C++ 第十八章】C++11 新增语法(3)
前情回顾:
【C++11 新增语法(1):1~6 点】
C++11出现与历史、花括号统一初始化、initializer_list初始化列表、 auto、decltype、nullptr、STL的一些新变化
【C++11 新增语法(2):7~8 点】
右值引用和移动语义∶移动构造和移动赋值、move 函数、左值引用的短板、万能引用、完美转发、push_back 函数重载右值引用版本(借助list的push-back 使用举例)﹔新增两个默认类函数与delete关键字
⭐⭐本文会使用到自己模拟实现的 string 和 list 类,为了更好的观察各种函数的构造过程,建议先将本文最后的 string 和 list 代码拷贝下来创建一个 string.h / list.h 文件
9. 可变参数模板
一个模板,如果你有这样的需求:内容不变,但是参数的数量需要有 3 个,4个,5个参数,岂不是要写 好几个不同参数但内容相同的重复模板??
因此 可变参数模板的作用就凸显了(其实这个底层也是累死编译器)
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现
阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大
家如果有需要,再可以深入学习。
下面就是一个基本可变参数的函数模板
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args... args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数
包”,它里面包含了0到N(N>=0)个模版参数。参数包的用法有点奇怪,需要时多多尝试后使用即可
可变参数模板 相当于 不限制参数个数的 模板,模板+可变参数 == 参数可变的模板
同时,可变参数模板 是 编译时获取,即像之前学的模板,这个是 直接在编译阶段,编译器通过实例化的对象,来 实例化出模板
这个也是模板,因此 参数包 Args… args 需要在编译阶段时 直接确定,因此不能使用 参数包 args 进行一些 运行时获取和解析的程序:sizeof、for循环打印…..
我们无法直接获取参数包args中的每个参数的, 只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
🚩递归函数方式展开参数包
ShowList 函数将参数包传给 Print 函数,到 Print 函数这里,会自动解析出一个参数放到 T&& x,剩余的参数留在参数包 Args&&... args,
然后再递归:下一层则再拿一个参数出来放到 x 处,一直递归下去,直到取出所有
Print(T&& x, Args&&... args)
我们写的 程序
void Print()
{
cout << endl;
}
template <class T, class ...Args>
void Print(T&& x, Args&&... args)
{
cout << x << " ";
Print(args...);
}
// 编译时递归推导解析参数
template <class ...Args>
void ShowList(Args&&... args)
{
Print(args...);
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, "xxxxx");
ShowList(1, "xxxxx", 2.2);
return 0;
}
在编译器底层其实长这样:
void Print()
{
cout << endl;
}
template <class T, class ...Args>
void Print(T&& x, Args&&... args)
{
cout << x << " ";
Print(args...);
}
// 编译器推导的
void Print(double x)
{
cout << x << " ";
Print();
}
void Print(const char* x, double z)
{
cout << x << " ";
Print(z);
}
void Print(int x, const char* y, double z)
{
cout << x << " ";
Print(y, z);
}
// 可变参数模板:编译时递归推导解析参数
template <class ...Args>
void ShowList(Args&&... args)
{
Print(args...);
}
//编译器实例化生成
void ShowList(int x, const char* y, double z)
{
Print(x, y, z);
}
int main()
{
ShowList(1, "xxxxx", 2.2);
return 0;
}
🚩逗号表达式+在数组中直接展开
后面使用 三个点的省略号代表当前这里要展开
逗号表达式的执行原理:从左到右依次执行逗号表达式,最后结果取最后一个位置的数据
因此,下面使用逗号表达式,既执行了表达式的内容,返回值也是 int 类型
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
// 不能使用 ostream 作为这里的类型,这个对象不允许拷贝
//ostream arr[] = { cout(args)... };
// 逗号表达式
// (PrintArg(args), 0) :先执行前面的内容即 printarg(args),再得到逗号表达式的结果 0
// 这样间接的达到目的
int arr[] = { (PrintArg(args), 0)... };
cout << '\n';
// 或者说,逗号表达式的目的是为了控制返回值
// 同时执行想要的表达式
// 如下:先执行前面的 cout 表达式子,然后逗号表达式的最后返回值 是最后的那个值 0
int arr2[] = { (cout << (args) << ' ', 0)...};
cout << endl;
// int 数组,逗号表达式依次运行了,顺便最后返回 0 给数组
}
int main()
{
//ShowList(1);
//ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
🚩也可以不使用 逗号表达式
逗号表达式的作用主要是控制返回值是数组的 int 类型,你也可以控制 打印函数的返回值是 int 类型,则数组的元素类型也就是 int 类型了
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArg(args)... }; // 三个点代表当前这里要展开
cout << endl;
}
// 编译推演生成下面的函数
void ShowList(int x, char y, std::string z)
{
int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
cout << endl;
}
int main()
{
//ShowList(1);
//ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
10. emplace_back 函数:升级版 push_back
emplace_back 函数使用了 可变参数模板
这个是函数模板,是 可变参数模板
push_back 的所有操作,本函数都能做,那本函数的特别之处在于?
🚩使用模拟实现的 list 和 string,演示使用过程
string 和 list 的代码在本文的 末尾
// emplace_back总体而言是更高效,推荐使用
int main()
{
// 一、 push_back 的行为
bit::list<bit::string> lt;
// 左值
bit::string s1("111111111111");
lt.emplace_back(s1);
// 右值
lt.emplace_back(move(s1));
// 直接把构造string参数包往下传,直接用string参数包构造string
lt.emplace_back("111111111111");
lt.emplace_back(10, 'x');
cout << endl << endl;
// 构造pair + 拷贝/移动构造pair到list的节点中data上
bit::list<pair<bit::string, int>> lt1;
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.emplace_back(move(kv));
cout << endl << endl;
// 二、特别之处
// 直接把构造pair参数包往下传,直接用pair参数包构造pair
lt1.emplace_back("苹果", 1);
//lt1.emplace_back(1, "苹果"); // 为什么不支持这样写?因为参数包也是从左到右依次拿出的,要和参数顺序对应上
return 0;
}
上面的代码演示可以发现: 给 emplace_back 传递构造 pair 的参数包,最终只会产生一次 直接构造,效率好高!
单独拿出来解释原理:
pair<my::string, int> kv1("苹果", 1);
lt1.emplace_back(kv1);
lt1.emplace_back("苹果", 1);
上面这组:先直接构造一个 pair,创建链表节点时再 拷贝构造 pair
下面这组:直接拿着参数包,层层传递到 节点初始化列表构造data = pair,只需调用一次 string 的直接构造(相当于有个构造pair的积木零件包,在上头先不构造出来,零件包一直传递到下层,再拼积木 直接构造 节点的 pair出来)
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), forward<Args>(args)...);
}
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* newnode = new Node(std::forward<Args>(args)..);
//....
}
template <class... Args>
ListNode(Args&&... args)
, _data(std::forward<Args>(args)...)
{}
上面这组:参数包中放一个 pair 对象,转发给 insert,再转发给 Node 的构造函数:pair里面的 string 已经是成型的 string 对象,则调用 string 移动拷贝构造
下面这组:参数包中放一个 string 和 int,转发给 insert,再转发给 Node 的构造函数:string 仅仅是参数,未成型,调用 stirng 的直接构造
所谓的参数包 实际上不存在,底层就是编译器根据模板生成好多个 函数,每个函数对应传递 一个 参数包里面的 参数
比如 emplate_back 接收了一个参数包 args,其中有参数 string 和 int
向下传递实际上是 实例化两组函数:传递string的左右值版本函数、传递 int 的左右值版本函数
然后传递参数包给 insert 函数接收,insert 函数也用一个参数包接收,然后底层进行一样的操作
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), args...);
}
// 原理:编译器根据可变参数模板生成对应参数的函数
void emplace_back(string& s){
insert(end(), s);
}
void emplace_back(string&& s){
insert(end(), s);
}
void emplace_back(const char* s){
insert(end(), s);
}
void emplace_back(size_t n, char ch){
insert(end(), n, ch);
}
🚩【面试题】emplace_back 为什么会更加高效?
push 左值时:这个左值是一个深拷贝对象
push_back 和 emplace_back 需要 直接构造 + 深拷贝
push_back 和 emplace_back 可以 move变成右值: 直接构造 + 移动构造
push 左值时:这个左值是一个浅拷贝对象
push_back 和 emplace_back 需要 直接构造 + 浅拷贝
push_back 和 emplace_back 可以 move变成右值: 直接构造 + 移动构造
其实浅拷贝对象一般不会使用移动构造,也没什么意义
push 右值时:匿名对象
push_back 和 emplace_back 需要 直接构造 + 移动构造
push 参数包:只有 emplace_back 可以
只需调用 直接构造
综合而言:emplace_back 整体而言会更加高效
🚩总结:
以后如果有需要 push_back 和 push_front 的地方都可以换成使用 emplace_back 和 emplace_front
本章节使用到的 模拟实现 string 代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<iostream>
#include<vector>
namespace my
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
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);
}
string(size_t n, char ch = '\0')
{
cout << "string(size_t n, char ch = '\0')" << endl;
reserve(n);
for (size_t i = 0; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
// s1.swap(s2)
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 拷贝构造
// s2(s1)
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
// 临时创建的对象,不能取地址,用完就要消亡
// 深拷贝的类,移动构造才有意义
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
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];
if (_str)
{
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';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity) {
// 要多少加多少
reserve(_size + len);
}
// 可以使用 strcat 追加字符串:遍历字符串,找到 \0 ,从这个位置开始追加字符串
// 遍历一遍效率较低
// 使用 strcpy 指定起始位置:_str + _size,刚好是 \0 的位置
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(const char ch)
{
this->push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
this->append(str);
return *this;
}
const char* c_str() const
{
return _str;
}
const size_t size() const {
return _size;
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
my::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
my::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;
}
}
本章节使用到的 模拟实现 list 代码
#pragma once
#include<assert.h>
#include<iostream>
#include<vector>
namespace my
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{}
ListNode(T&& data)
:_next(nullptr)
, _prev(nullptr)
, _data(move(data))
{}
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...)
{}
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
// ++it;
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
list(initializer_list<T> il)
{
empty_init();
for (const auto& e : il)
{
push_back(e);
}
}
list(const list<T>& lt)
{
empty_init();
for (const auto& e : lt)
{
push_back(e);
}
}
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
insert(end(), x);
cout << '\n';
}
void push_back(T&& x)
{
insert(end(), move(x));
cout << '\n';
}
void pop_back()
{
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
// 没有iterator失效
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), forward<Args>(args)...);
}
原理:编译器根据可变参数模板生成对应参数的函数
//void emplace_back(string& s)
//{
// insert(end(), std::forward<string>(s));
//}
//
//void emplace_back(string&& s)
//{
// insert(end(), std::forward<string>(s));
//}
//
//void emplace_back(const char* s)
//{
// insert(end(), std::forward<const char*>(s));
//}
//
//void emplace_back(size_t n, char ch)
//{
// insert(end(), std::forward<size_t>(n), std::forward<char>(ch));
//}
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
// erase 后 pos失效了,pos指向节点被释放了
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
private:
Node* _head;
};
}