C++11详解
一、C++11发展史

二、列表初始化
1、C++98传统的{}
C++98一般结构体和数组可以用{}进行初始化
struct Point
{
int _x;
int _y;
};
int mian()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[5] = { 0 };
Point p{ 1,2 };
return 0;
}
2、C++11中的{}
- C++11后想要统一初始化,试图想要使得一切对象都可以用{}进行初始化,用{}进行初始化也叫列表初始化;
- 内置类型和自定义类型都可以用{}进行初始化,自定义类型用{}进行初始化本质是类型转换之后构造产生临时对象再拷贝构造,但是优化之后就是直接构造;
- {}初始化的过程中=可以省略;
- C++11使用{}进行初始化本身是想要实现初始化的统一,其次在很多情形中使用{}进行初始化带来了很多遍历,比如容器的初始化。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
// ⼀切皆可⽤列表初始化,且可以不加=
int mian()
{
// C++98支持的
int arr1[] = { 1,2,3,4,5 };
int arr2[5] = { 0 };
Point p{ 1,2 };
// C++11⽀持的
// 内置类型⽀持
int x1 = { 2 };
// ⾃定义类型⽀持
// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化d1
// 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的
Date d1 = { 2025, 1, 1 };
// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象
const Date& d2 = { 2024, 7, 25 };
// 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
Date d3 = { 2025 };
Date d4 = 2025;
// 可以省略掉=
Point p1{ 1, 2 };
int x2{ 2 };
Date d6{ 2024, 7, 25 };
const Date& d7{ 2024, 7, 25 };
// 不⽀持,只有{}初始化,才能省略=
// Date d8 2025;
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐
v.push_back({ 2025, 1, 1 });
return 0;
}
注意const Date& d2 = {2024, 7, 2}引用的是产生的临时对象,因为临时对象具有常性,所以用const,加了&,那么d2就是那个临时对象;一般来说,不使用const和&时,临时对象会作为拷贝蓝图给d2进行拷贝构造之后这个临时对象的生命周期就结束了,但是这里用const和&表示d2就是那个临时对象,那么这个临时对象也就没有立即销毁,相当于延长了临时对象的生命周期 。
3、C++11中的std::inlitiallizer_list
在C++11中很多STL容器初始化想用很多值去初始化,会显得很不方便;此时引入了初始化列表这个类型,这个类型支持迭代器(可以理解为一个带有两个指针的数组,一个指向前,一个指向后)。这个类型可以作为STL容器构造函数中的一个参数类型,用来一次性用多个值初始化容器。
// STL 中的容器都增加了⼀个 initializer_list 的构造vector (initializer_list<value_type> il, const allocator_type& alloc =allocator_type());list (initializer_list<value_type> il, const allocator_type& alloc =allocator_type());map (initializer_list<value_type> il,const key_compare& comp =key_compare(),const allocator_type& alloc = allocator_type());// ...
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
for (auto e : l)
push_back(e)
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
// 另外,容器的赋值也⽀持initializer_list的版本
vector& operator= (initializer_list<value_type> il);
map& operator= (initializer_list<value_type> il);
int main()
{
std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl;
// 这⾥begin和end返回的值initializer_list对象中存的两个指针
// 这两个指针的值跟i的地址跟接近,说明数组存在栈上
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
// {}列表中可以有任意多个值
// 这两个写法语义上还是有差别的,第⼀个v1是直接构造,
// 第⼆个v2是构造临时对象+临时对象拷⻉v2+优化为直接构造
vector<int> v1({ 1,2,3,4,5 });
vector<int> v2 = { 1,2,3,4,5 };
const vector<int>& v3 = { 1,2,3,4,5 };
// 这⾥是pair对象的{}初始化和map的initializer_list构造结合到⼀起⽤了
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };
// initializer_list版本的赋值⽀持
v1 = { 10,20,30,40,50 };
return 0;
}
###值得注意的是:
只要是带有{}的构造就是列表初始化,{}是语法;初始化列表是一个类型;在初始化容器时,容器的构造函数有一个initializerlist类型形参接受然后去初始化,无论是形为({})还是=({})的都是优先匹配含有初始化列表类型形参的构造函数去初始化。
也就是所,初始化列表和列表初始化不是一个层面的东西,一个是语法,一个是类型;在使用列表初始化的过程中可能用到初始化列表,初始化列表是幕后处理的机制;两者共同作用实现批量元素的初始化。
三、右值引用和移动语义
1、左值和右值
int main()
{
// 左值:可以取地址
// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
cout << &c << endl;
cout << (void*)&s[0] << endl;
// 右值:不能取地址
double x = 1.1, y = 2.2;
// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值
10;
x + y;
fmin(x, y);
string("11111");
//cout << &10 << endl;//常量
//cout << &(x+y) << endl;//表达式返回值是一个临时对象,具有常性
//cout << &(fmin(x, y)) << endl;
//cout << &string("11111") << endl;//匿名对象
return 0;
}
2、左值引用和右值引用
int main()
{
// 左值:可以取地址
// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引⽤给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
// 右值引⽤给右值取别名
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
string&& rr4 = string("11111");
// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");
// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s;
// b、r1、rr1都是变量表达式,都是左值
cout << &b << endl;
cout << &r1 << endl;
cout << &rr1 << endl;
// 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除⾮move⼀下
int& r6 = r1;
// int&& rrx6 = rr1;
int&& rrx6 = move(rr1);
int& r7 = rr1;//再次说明右值引用变量的属性是左值
return 0;
}
3、引用延长生命周期
int main()
{
std::string s1 = "Test";
// std::string&& r1 = s1; // 错误:不能绑定到左值
const std::string& r2 = s1 + s1; // OK:到 const 的左值引⽤延⻓⽣存期
// r2 += "Test"; // 错误:不能通过到 const 的引⽤修改
std::string&& r3 = s1 + s1; // OK:右值引⽤延⻓⽣存期
r3 += "Test"; // OK:能通过到⾮ const 的引⽤修改
std::cout << r3 << '\n';
return 0;
}
一般来说,s1+s1会返回一个具有常性的临时变量给接收方拷贝,那么这个临时对象在拷贝结束后会销毁即生命周期结束了,但是若是用引用接收这个临时变量就能在程序结束前一直使用这个临时变量,这就相当于延长了对象的生命周期。
值得注意的是延长的生命周期只是在当前作用域的临时变量生命周期被延长了。
4、左值和右值的参数匹配
void f(int& x)
{
std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
int i = 1;
const int ci = 2;
f(i); // 调⽤ f(int&)
f(ci); // 调⽤ f(const int&)
f(3); // 调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&)
f(std::move(i)); // 调⽤ f(int&&)
// 右值引⽤变量在⽤于表达式时是左值
int&& x = 1;//右值引用表达式属性是左值
f(x); // 调⽤ f(int& x)
f(std::move(x)); // 调⽤ f(int&& x)
return 0;
}
5、右值引用和移动语义的使用场景
回顾
class Solution {
public:
// 传值返回需要拷⻉
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
// 进位
int next = 0;
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
str += ('0' + ret);
}
if (next == 1)
str += '1';
reverse(str.begin(), str.end());
return str;
}
};
class Solution {
public:
// 这⾥的传值返回拷⻉代价就太⼤了
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv(numRows);
for (int i = 0; i < numRows; ++i)
{
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < numRows; ++i)
{
for (int j = 1; j < i; ++j)
{
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
return vv;
}
};
移动构造和移动赋值
namespace bit
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
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);
}
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;
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()
{
cout << "~string() -- 析构" << endl;
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';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
int main()
{
bit::string s1("xxxxx");
// 拷⻉构造
bit::string s2 = s1;
// 构造+移动构造,优化后直接构造
bit::string s3 = bit::string("yyyyy");
// 移动构造
bit::string s4 = move(s1);
cout << "******************************" << endl;
return 0;
}
这里的移动构造和赋值相当于窃取了右值的内容,swap(右值)就是不管右值的“死活”,因为右值可能只是常量,临时对象,匿名对象,对于我们的程序来说,这些右值被窃取了不会有影响;所以这样就无需再向系统申请空间了,大大提高了效率;
右值对于函数返回值的优化:
完全不优化:即返回值会拷贝构造一个临时对象,这个临时对象再去拷贝构造ret;
一代优化:直接用局部变量str去拷贝构造ret;
二代优化:str作为ret的引用,返回是不会有任何拷贝构造;
对于赋值重载:
完全不优化:str先拷贝构造一个临时对象再去赋值重载ret;
一代优化:将str作为临时对象的引用,省去一步拷贝构造;
总结:
左值引用让传参效率提高,不需要拷贝传参;并且可以修改实参以及返回值;但是左值不能优化返回值;
对于返回值的优化,编译器的优化确实可以省去很多拷贝步骤让效率得到提高 ;但是不是任何地方使用的都是新版本具有优化功能的编译器;此时需要C++11的移动语义了;
移动语义的移动拷贝和移动赋值都是掠夺其他对象的资源,并且是直接swap,在这种情况下就算不去使用编译器的优化,就是直接返回,返回值的拷贝和赋值也会是移动拷贝和赋值,而这种移动语义效率极高;
一般来说并不是任何地方都要写移动拷贝和赋值,只有当对象是自定义对象要进行深拷贝时才需要;因为若是浅拷贝就是简单的赋值,而没有涉及到开辟新的空间;
6、类型分类
C++11中所有的表达式分为泛左值(generalized lvalue)和右值(rvalue),而泛左值又分为左值和将亡值(expiring value),右值分为纯右值(pure rvalue)和将亡值;
将亡值:将亡值通常是由 std::move()
函数或 static_cast
到右值引用类型转换产生的。它表示一个对象的资源即将被转移,其生命周期即将结束。
7、引用折叠
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n;//lref&是左值
lref&& r2 = n;//lref&&是左值
rref& r3 = n;//rref&是左值
//rref&& r4 = n;//报错:无法将右值引用绑定到左值->rref&&是右值
rref&& r4 = 0;//rref&&是右值
也就是所但凡出现一个左值引用那么这个引用就是左值引用,只用全是右值引用时最终才是右值引用,比如这里的rref是int&&右值,r4前面又有&&,那么就全是右值引用,最终就是右值引用;
再比如:像f2这样的函数模板中,T&& x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。
template<class T>
void f1(T& x)//T&本身就是左值,那么无论如何x都是一个左值引用
{}
template<class T>
void f2(T&& x)//这里可以控制
{}
f1<int>(n);
//f1<int>(0);//报错,这里0是右值,但是int传过去推导出来x是左值引用
f1<int&>(n);//两个左值,最终x就是左值引用
//f1<int&>(0);//报错
f1<int&&>(n);//T&是左值,那么就算T是右值int&&,折叠之后仍是左值引用
//f1<int&& (0);//报错
f1<const int&>(n);//两个左值,x仍是左值引用,类型是const int&
f1<const int&>(0);//x类型是const int&是左值但是因为const出现,可以接受右值
f1<const int&&>(n);//折叠->void f1(const int& x)
f1<const int&&>(0);//折叠->void f1(const int& x)
//f2<int>(n);//T为int那么x就是右值引用,不能接收左值n
f2<int>(0);
f2<int&>(n);//int&是左值那么折叠->void f2(T&)
//f2<int&>(0);
//f2<int&&>(n);//两个右折叠之后->void f1(int&&)
f2<int&&>(0);
//f2<const int&&>(n);//折叠之后->void f2(const int&&)
f2<const int&&>(0);
再比如:
template<class T>
void function(T&& t)
{
int a = 0;
T x = a;
x++;
cout << &x << endl;//就算x是右值表达式,但是其属性还是左值,就可以取地址
cout << &a << endl;
}
function(10);//10是一个右值,传过去推导为int&&
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
function(std::move(a)); // 右值
const int b = 8;
// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&t)
// 所以function内部会编译报错,x不能++
function(b); // const 左值
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
// 所以function内部会编译报错,x不能++
function(std::move(b)); // const 右值
8、完美转发
在我们使用右值引用参数接收传过来数据时,可以根据数据是什么类型来推导出现在的模板T是什么类型,但是若是在这个函数内部再将传过来的值传给其他的函数无论这个实参时是右值引用还是左值引用,它们的属性都是左值,那么此时传给其他的函数就不能传右值引用过去了,所以这里引入了完美转发;
完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 Function的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤ 返回。
不使用完美转发:
//template <class _Ty>
//_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
//{ // forward an lvalue as either an lvalue or an rvalue
// return static_cast<_Ty&&>(_Arg);
//}
void func(int& x) { cout << "左值引用"<< endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用"<<endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }
template<class T>
void function(T&& t)
{
func(t);
//func(forward<T>(t));
}
int main()
{
function(10);
int a = 0;
function(a);
function(std::move(a));
const int b = 0;
function(b);
function(std::move(b));
return 0;
}
使用完美转发func(forward<T>(t)):
四、可变参数模板
1、基本语法以及原理
template<class...Args>//表示类型参数包
void Print(Args&&... args)//表示参数包中每个类型的对应的形参
{
cout << sizeof...(args) << endl;//sizeof...专门计算参数包中参数个数
}
int main()
{
double x = 2.2;
Print();
Print(1);
Print(1, string("aaaaaaa"));
Print(1, string("aaaaaaa"), x);
return 0;
}
//原理1:编译时会结合引用折叠实例化出这四个函数
void Print();
void Print(int&& args1);
void Print(int&& args1, string&& args2);
void Print(int&& args1, string&& args2, double& args3);
//原理2:更本质去看没有可变参数模板,我们实现出这样的多个函数模板才能⽀持
// 这⾥的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础
// 上叠加数量变化,让我们泛型编程更灵活
void Print();
template<class T1>
void Print(T1&& args1);
template<class T1,class T2>
void Print(T1&& args1, T2&& args2);
template<class T1, class T2,class T3>
void Print(T1&& args1, T2&& args2, T3&& args3);
可变参数模板就像是模板的模板,就像它可以通过传不同的参数来生成把不同的函数模板,从而实例化出不同的函数;
并且,可变参数模板的类型参数包的个数没有限制。
2、包扩展
void Showlist()
{
//最后依次传的是空
cout << endl;
}
template<class T, class...Args>
void Showlist(T x ,Args...args)
{
cout << x << endl;
//每次传过来的N个参数被分为1和N-1两个部分
Showlist(args...);
}
// 编译时递归推导解析参数
template<class...Args>
void Print(Args...args)
{
Showlist(args...);//将参数包传过去
}
int main()
{
double x = 2.2;
Print(1, string("aaaaaaa"), x);
return 0;
}
// 本质编译器将可变参数模板通过模式的包扩展,编译器推导的以下三个重载函数函数
//void Print(int x, string y, double z)
//{
// Showlist(x, y, z);
//}
//void Showlist(int x, string y, double z)
//{
// cout << x << endl;
// Showlist(y, z);
//}
//void Showlist(string x, double y)
//{
// cout << x << endl;
// Showlist(y);
//}
//void Showlist(double x)
//{
// cout << x << endl;
// Showlist();
//}
template<class T>
const T& GetArg(const T& arg)
{
cout << arg << endl;
return arg;
}
template<class...Args>
void Argument(Args...args)
{}
template<class...Args>
void Print(Args...args)
{
//GetArg必须返回,Argument才能接收到值
Argument(GetArg(args)...);
}
本质上可以理解为
//void Print(int x, string y, double z)
//{
// Argument(GetArg(x), GetArg(y), GetArg(z));
//}
int main()
{
double x = 2.2;
Print(1, string("aaaaaaa"), x);
return 0;
}
若是没有Argument,那么GetArg只能一个一个使用,有了Argument就能把多个GetArg以参数包的方式一起使用;这里的GetArg必须要有返回值,Argument才能接收到多个参数,才能有参数包。
3、emplace系列接口
int main()
{
bit::list<bit::string> lt;
// 传左值,跟push_back⼀样,⾛拷⻉构造
bit::string s1("111111111111");
lt.emplace_back(s1);
cout << "*********************************" << endl;
// 右值,跟push_back⼀样,⾛移动构造
lt.emplace_back(move(s1));
cout << "*********************************" << endl;
// 直接把构造string参数包往下传,直接⽤string参数包构造string
// 这⾥达到的效果是push_back做不到的
lt.emplace_back("111111111111");
cout << "*********************************" << endl;
bit::list<pair<bit::string, int>> lt1;
// 跟push_back⼀样
// 构造pair + 拷⻉/移动构造pair到list的节点中data上
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
// 跟push_back⼀样
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
// 这⾥达到的效果是push_back做不到的
lt1.emplace_back("苹果", 1);
cout << "*********************************" << endl;
return 0;
}
// List.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace bit
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _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;
}
Ref operator*()
{
return _node->_data;
}
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);
}
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));
}
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(), std::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));
}
*/
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);
}
private:
Node* _head;
};
}
namespace bit
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
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(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
void swap(string& ss)
{
::swap(_str, ss._str);
::swap(_size, ss._size);
::swap(_capacity, ss._capacity);
}
// 移动构造
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()
{
//cout << "~string() -- 析构" << endl;
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';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
emplace的新增的点就是它的模板参数是可变参数模板,也就是说,无论此时的容器中存放的是什么类型的值,使用emplace都可以传过去,并且这个参数包无需解析,只需要一直往下传,传到初始化节点时,这个参数包就被使用了,也就是直接用参数包里面的值进行构造节点。
五、类的新功能
1、默认的移动构造和移动赋值
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name),
_age(age)
{}
拷贝构造
//Person(const Person& p)
// :_name(p._name),
// _age(p._age)
//{}
private:
bit::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = move(s1);
return 0;
}
此时没有Person没有写析构、拷贝构造和赋值重载,并且也没有写移动赋值或者重载,那么编译器会自己生成默认的移动构造以及重载,对于内置类型进行浅拷贝,对于自定义类型则看其是否实现了移动构造函数,实现了就调用这个移动构造,反之则调用其拷贝构造(对于赋值重载也是一样的)
去掉拷贝构造的注释之后:
2、声明时给缺省值
3、default和delete



4、final和override
六、stl中的变化
最重要的变化:unordered_map、set以及移动语义加入一些容器的接口,比如list的emplace.
七、lambda表达式
1、语法
int main()
{
//lambda表达式
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto add1 = [](int x, int y)->int { return x + y; };
cout << add1(10, 20) << endl;
auto func = [] {cout << "hello world!" << endl; };
func();
auto swap = [](int& x, int& y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
};
int a = 10, b = 20;
swap(a, b);
cout << "a:" << a << endl << "b:" << b << endl;
return 0;
}
2、捕捉列表
lambda表达式默认只能使用lambda表达式内部的变量以及静态变量或者全局变量;想要使用外部变量需要进行捕捉;
捕捉分为引用捕捉和值捕捉,被捕捉变量前面加上&就是引用捕捉,反之就是值捕捉;值捕捉无法改变变量的值,引用捕捉可以改变变量的值
int main()
{
int a = 0, b = 1, c = 2, d = 3;
auto f1 = [a, &b]
{
//报错,可以理解为捕捉默认有const修饰
//a++;
b++;
cout << "a:" << a << " ; " << "b:" << b << endl;
};
f1();
return 0;
}
隐式捕捉:在[]里面写了=或者&表示隐式捕捉,此时在函数体中写了什么就会捕捉什么并且使用,但是=是隐式值捕捉,&是隐式引捕捉;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
auto f2 = [=]
{
cout << "a:" << a << " ; " << "b:" << b << endl;
};
f2();
auto f3 = [&]
{
a++;
b++;
cout << "a:" << a << " ; " << "b:" << b << endl;
};
f3();
return 0;
}
混合捕捉,就是=或者&和变量在捕捉列表中同时出现,[]中第一个必须是=或者&,若是=那么后面的变量要求全是引用捕捉,若是&要求后面捕捉的全部是值捕捉
捕捉是用哪些变量就捕捉,而并不是全部都捕捉
int a = 0, b = 1, c = 2, d = 3;
auto f4 = [&, c, d]
{
a++;
b++;
//c++;//后面是值捕捉,报错
cout << "a:" << a << " ; " << "b:" << b << endl;
cout<< "c:" << c << " ; " << "d:" << d << endl;
};
f4();
auto f5 = [=, &c, &d]
{
c++;
d++;
//a++;//后面是值捕捉,报错
cout << "a:" << a << " ; " << "b:" << b << endl;
cout << "c:" << c << " ; " << "d:" << d << endl;
};
f5();
lambda只能捕捉所在函数体定义在其之前的变量,不能捕捉全局和静态变量,当然全局和静态也不需要捕捉,可以直接使用;那么当lambda表达式定义在全局时,捕捉列表必须是空
//定义在全局的lambda不需要捕捉
int x = 100;
auto f6 = []{cout << x << endl;};
int main()
{
f6();
int a = 0, b = 1, c = 2, d = 3;
//可以直接使用全局和静态变量
auto f7 = [c, d] {cout << x + c + d << endl; };
f7();
return 0;
}
使用mutable加在lamb表达式参数列表后面那么值捕捉的变量就可以改变了,取消了常性;但是此时改变的是形参而不是原本的变量的值; 那么参数列表不能省略了,即使是空,因为要在括号后面加mutable;
int main()
{
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉
int a = 0, b = 1, c = 2, d = 3;
auto f8 = [=]()mutable
{
a++;
b++;
c++;
d++;
cout << "a:" << a << " ; " << "b:" << b << endl;
cout << "c:" << c << " ; " << "d:" << d << endl;
};
f8();
cout << "a:" << a << " ; " << "b:" << b << endl;
cout << "c:" << c << " ; " << "d:" << d << endl;
return 0;
}
3、应用
struct Goods
{
string _name;//名字
double _price;//价格
int _evaluate;//评价
Goods(const char* name,double price,int evaluate)
:_name(name),
_price(price),
_evaluate(evaluate)
{}
Goods(const Goods& goods)//拷贝构造
: _name(goods._name),
_price(goods._price),
_evaluate(goods._evaluate)
{}
};
void Print(vector<Goods>& goods)
{
for (auto x : goods)
{
cout << x._name << ":" << x._price << ":" << x._evaluate << endl;
}
}
//商品按价格比较的仿函数
struct ComparePriceLess
{
bool operator()(Goods& g1, Goods& g2)
{
return g1._price < g2._price;
}
};
struct ComparePriceGreater
{
bool operator()(Goods& g1, Goods& g2)
{
return g1._price > g2._price;
}
};
int main()
{
vector<Goods> goods = { { "苹果", 2.1, 2 }, { "香蕉", 3, 1 }, { "橙子", 2.2, 4}, { "菠萝", 1.5,3 } };
vector<Goods> v1 = goods;
sort(v1.begin(), v1.end(), ComparePriceLess());
Print(v1);
cout << endl;
vector<Goods> v2 = goods;
sort(v2.begin(), v2.end(), ComparePriceGreater());
Print(v2);
cout << "***************************" << endl;
//类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
//不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
vector<Goods> v3 = goods;
vector<Goods> v4 = goods;
vector<Goods> v5 = goods;
vector<Goods> v6 = goods;
//按价格排序
cout << "按价格排序:" << endl;
cout << "升序:" << endl;
sort(v3.begin(), v3.end(), [](Goods& g1, Goods& g2) ->bool
{return g1._price < g2._price; });
Print(v3);
cout << "降序:" << endl;
sort(v4.begin(), v4.end(), [](Goods& g1, Goods& g2) ->bool
{return g1._price > g2._price; });
Print(v4);
cout << endl;
//按评价排序
cout << "按评价排序" << endl;
cout << "好评:" << endl;
sort(v5.begin(), v5.end(), [](Goods& g1, Goods& g2) ->bool
{return g1._evaluate < g2._evaluate; });
Print(v5);
cout << "差评:" << endl;
sort(v6.begin(), v6.end(), [](Goods& g1, Goods& g2) ->bool
{return g1._evaluate > g2._evaluate; });
Print(v6);
return 0;
}
4、lambda表达式原理
其实就是生成了一个仿函数的类,类名按照一定的编译规则生成,不同lambda表达式的生成的类名不同;捕捉列表对应的就是仿函数的成员变量,返回类型就是仿函数的返回类型,参数列表和函数体就是仿函数的参数列表以及函数体;
八、包装器
1、function
std:function是一个类模板也是一个包装器,function的实例出来的对象可包装其他的可调用对象例如:函数指针、仿函数、lambda表达以及bind;存储的可调用对象被称为std:function的目标,若目标为空时,调用function实例对象会出现抛出 bad_function_call 异常
function头文件是<functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
//function对象包装各种可调用对象
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
return 0;
}
使用function包装类里面的静成员函数需要指定类域在类域前面加上&;包装普通成员函数也要指定类域,可以不加&但是一般建议是加上,最重要的是,普通成员函数隐含this指针,所以此时function需要在()里面的第一个参数加上类类型或者类指针或者类的右值引用类型;
这里虽然成员函数的第一个参数是this指针,也就是类的指针类型,但是实际上对象调用函数指针时都是用对象.*去获得的((obj.*ptr)),那么当传类类型时就是生成临时对象(obj.*ptr),当传类指针时就是(*(pobj).*ptr),传类匿名对象更直接不需要再生成一个临时对象直接(obj.*ptr)
class Plus
{
private:
int _n = 10;
public:
Plus(int n = 10)
:_n(n)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return _n * (a + b);
}
};
int main()
{
//静态成员函数的包装
function<int(int, int)> f1 = &Plus::plusi;
cout << f1(1, 1) << endl;
//包装普通的成员函数
Plus pd;
function<double(Plus*, double, double)> f2 = &Plus::plusd;
cout << f2(&pd, 1.11, 1.11) << endl;
function<double(Plus, double, double)> f3 = &Plus::plusd;
cout << f3(pd, 1.11, 1.11) << endl;
function<double(Plus&&, double, double)> f4 = &Plus::plusd;
cout << f4(Plus(), 1.11, 1.11) << endl;
return 0;
function在求解逆波兰表达式求值的作用:
class Solution {
public:
//一般思路:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for(auto str : tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
int num1 = st.top();
st.pop();
int num2 = st.top();
st.pop();
switch(str[0])
{
case '+':
st.push(num2 + num1);
break;
case '-':
st.push(num2 - num1);
break;
case '*':
st.push(num2 * num1);
break;
case '/':
st.push(num2 / num1);
break;
}
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
使用function将运算符存起来,并且给每一个运算符相对应的表达式存在map里面
class Solution {
public:
//一般思路:
int evalRPN(vector<string>& tokens)
{
map<string , function<int(int,int)>> hash=
{
{"+",[](int a,int b)->int{return a+b;}},
{"-",[](int a,int b)->int{return a-b;}},
{"*",[](int a,int b)->int{return a*b;}},
{"/",[](int a,int b)->int{return a/b;}}
};
stack<int> st;
for(auto& str : tokens)
{
if(hash.count(str))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
int ans = hash[str](left,right);
st.push(ans);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
2、bind
simple ( 1 )template < class Fn , class ... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);with return type ( 2 )template < class Ret, class Fn, class ... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
int main()
{
//调整参数顺序(不常用)
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
auto sub2 = bind(Sub, _2, _1);//_1代表第一个实参,_2代表第二个实参
cout << sub2(10, 5) << endl;
//调整参数个数(常用)
//绑定某个参数,剩下需要传的参数个数减少
auto sub3 = bind(SubX, 10, _1, _2);//绑定第一个参数
cout << sub3(2, 3) << endl;
auto sub4 = bind(SubX, _1 ,10, _2);//绑定第二个参数
cout << sub4(2, 3) << endl;
auto sub5 = bind(SubX, _1, _2 ,10);//绑定第三个参数
cout << sub5(2, 3) << endl;
return 0;
}
使用bind可以绑定function中一些固定参数,那么每次传参就简化了:
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
function<double(Plus&&, double, double)> f1 = &Plus::plusd;
Plus pd;
cout << f1(move(pd), 1.1, 1.1) << endl;
cout << f1(Plus(), 1.1, 1.1) << endl;
//使用bind绑定一些固定参数,就不需要每次都传了
function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), _1, _2);//Plus()为固定参数
cout << f2( 1.1, 1.1) << endl;
cout << f2(1.1, 1.1) << endl;
return 0;
}
计算年化率的function,用bind绑定固定参数,只需要传本金就行
//计算年化率的lambda
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int main()
{
auto f = [](double rate, double money, int year)->double
{
int ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
//3 15\5 15\10 25\20 35
// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
function<double(int)> func_3_15 = bind(f, 0.015, _1, 3);
function<double(int)> func_5_15 = bind(f, 0.015, _1, 5);
function<double(int)> func_10_25 = bind(f, 0.025, _1, 10);
function<double(int)> func_20_35 = bind(f, 0.05, _1, 20);
cout << func_3_15(1000000) << endl;
cout << func_5_15(1000000) << endl;
cout << func_10_25(1000000) << endl;
cout << func_20_35(1000000) << endl;
return 0;
}