目录
一、初始化列表
在C++98标准中{ },仅仅应用于数组,struct, class等
int a = 10;
int b{10};
int c = {10};
这三种写法都是可以的,并且STL的所有容器都支持初始化列表的方式进行初始化
vector<int> v = {1, 2, 3, 4, 5, 55, 6, 7, 6, 4};
这样使用容器就会变得更加灵活,而所谓的初始化列表本质其实是在调用构造函数
以Date类举例
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
cout << "Date(int year, int month, int day)" << endl;//为了证明初始化列表调用构造函数
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 11, 20);
Date d2 = {2022, 11, 20};
return 0;
}
其实所谓的初始化列表是C++11添加的一种容器
我们使用typeid查看它的类型
auto it = {1, 2, 3, 4, 5, 6, 0, 7, 8, 9};
cout << typeid(it).name() << endl;
同时STL的每一个容器的构造函数都多了一项 initializer_list
原本只有单参数构造函数支持的隐式类型转换,有了初始化列表,多参数构造函数也支持了
总结:C++11以后,一切对象都可以使用初始化列表,但是建议普通对象还是使用以前的方式初始化,容器有需要可以使用初始化列表
二、变量类型的推导
1、auto
//常用场景
unordered_map<string, int> m;
unordered_map<string, int>::iterator it = m.begin();
auto it = m.begin();
我们正常要写迭代器,变量的类型太长,这时可以使用auto,简化代码,让编译器自动推断类型
2、decltype
乍一看decltype与auto十分的相似,但是它们具有本质的区别
auto是自动推导变量,推导之后,不能使用auto这个类型进行创建新的变量
而decltype是获取变量的类型,可以使用decltype创建变量
int x = 10;
auto y = 10.1;
decltype(x) z = 11.1;
cout << y << endl;
cout << z << endl;
auto推导y一定是一个浮点数类型,而z是一个int
三、右值引用
1、左值与右值
总结一句:左值是可以取地址的,左值不一定能够被修改,例如const修饰的变量
右值一般都是:字面量, 表达式返回值, 函数返回值,临时对象也可以被认为是右值
2、关于左值引用、右值引用的问题
1、左值引用可以引用右值吗?
左值是不能直接引用右值的,但是可以在左值引用前面加上const
template<class T>
void Func(const T& x)
{
}
x既可以接收左值也可以接收右值
2、右值引用可以引用左值吗?
答案是不能直接引用,但是可以引用move之后的左值
int a = 10;
int&& ra = move(a);
move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
基于 move() 函数特殊的功能,其常用于实现移动语义。
3、右值引用之后的问题
也就是说右值引用之后,右值就会变成左值,这里与后面完美转发有关
3、移动构造、移动拷贝
1、引用的价值
引用主要是用来减少拷贝,提高效率
左值引用常用的场景:
1、做参数:a、减少拷贝,提高效率 b、做输出型参数
2、做返回值:a、减少拷贝,提高效率 b、引用返回,可以修改返回对象
只有左值引用很难处理以下场景
to_string的返回值是一个string对象,要直接返回它代价较高,如果返回引用,就会出现野指针问题,to_string内部一定会定义一个临时string对象,它出了作用域就会销毁
如果要想提高效率,只能使用输出型参数,传一个string&给to_string,但是是不符合使用习惯的。
C++11的右值引用一个重要功能就是要解决上面的问题。
2、移动构造、移动赋值
在C++11以后,将右值分为两类1、内置类型右值-纯右值 2、自定义类型右值-将亡值
为了更好的说明这个问题,我使用了之前实现的string
namespace ww
{
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);
}
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::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()
{
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)
{
push_back(ch);
return *this;
}
const char *c_str() const
{
return _str;
}
private:
char *_str;
size_t _size;
size_t _capacity;
};
string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
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;
}
}
int main()
{
ww::string str = ww::to_string(-2345);
return 0;
}
在g++关闭所有优化的条件下,调用to_string会进行两次深拷贝
一次拷贝构造是to_string里面的临时对象拷贝给返回的临时对象,另一次拷贝是临时对象拷贝给在main函数栈帧的str,调用一次to_string就要深拷贝两次,这个代价就十分的巨大了
这时右值拷贝的作用就体现了,不过它不是直接起作用,而是间接起作用,实现方式就是移动构造和移动拷贝
在STL容器中添加移动构造,移动赋值
所谓的移动构造和移动赋值 ,其实是通过右值引用,因为只有将亡值会走移动构造,移动赋值
而将亡值除了作用域很快就会销毁,我们可以使新的类的指针指向将亡值的数据,将亡值的指针指向新构造的类,然后让将亡值的析构函数来处理新构造类的原始数据,这样就延长了资源的生命周期,而没有改变对象的声明周期。这样就极大的提高了效率,如果没有移动构造移动赋值,只能走拷贝构造,赋值重载,进行深拷贝,代价极大,而移动构造和移动赋值,只是更换指针指向方向,消耗几乎可以忽略不记,例如进行红黑树的拷贝也就成了现实,红黑树如果只进行深拷贝代价十分的大,要深拷贝整棵树,而移动构造和移动赋值可以只更改树根节点指针方向
// 移动构造
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;
}
只有将亡值才会走移动构造移动赋值,不是将亡值的还会走原来的拷贝构造赋值操作符重载
STL容器,插入接口C++11以后都提供了右值版本,插入过程中,如果传递的对象是右值,那么进行资源转移,减少拷贝
4、完美转发
template<class T>
void Func(T&& x)
{
}
void Fun(int &x) { cout << "左值引用" << endl; }
void Fun(const int &x) { cout << "const 左值引用" << endl; }
void Fun(int &&x) { cout << "右值引用" << endl; }
void Fun(const int &&x) { cout << "const 右值引用" << endl; }
template <typename T>
void PerfectForward(T &&t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}

template <typename T>
void PerfectForward(T &&t)
{
Fun(forward<T>(t));//使用完美转发,保持属性不变
}

5、补充说明
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
四、关于默认成员函数的关键字
1、final
class A final
{
private:
int _a;
};
class B : public A
{
private:
int _b;
};
2、override
class A
{
public:
virtual void show()
{
cout << _a << endl;
}
private:
int _a = 0;
};
class B : public A
{
public:
virtual void show() override
{
cout << _b << endl;
}
private:
int _b = 0;
};

3、default
class A
{
public:
A(const A& a)
{
_a = a._a;
}
private:
int _a = 0;
};
这时我们使用default强制生成默认构造函数
class A
{
public:
A(const A& a)
{
_a = a._a;
}
A() = default;
private:
int _a = 0;
};
4、delete
1、如何利用delete,定义一个只能在堆上开空间的类?
很显然,只能在堆上定义的类,一定要使用new,使用new就会调用构造函数,所以我们不能够修改构造函数,那么我们就把析构函数delete就可以了,在栈上的对象,出了作用域要销毁,就要调用析构函数,无法调用析构函数,就会报错,可是删除了析构函数,要怎么回收这个类呢?
我们只要自己手动写一个类似析构函数的成员函数,然后手动调用就好了
class A
{
public:
~A() = delete;
A()
{
_a = new int[10];
}
void destory()
{
delete[] _a;
operator delete(this);
}
private:
int *_a;
};
int main()
{
A a;
return 0;
}
先来看一下直接在栈上创建对象

接下来是在堆上创建对象
class A
{
public:
~A() = delete;
A()
{
_a = new int[10];
}
void destory()
{
delete[] _a;
operator delete(this);
}
private:
int *_a;
};
int main()
{
A* a = new A();
a->destory();
return 0;
}

注意:这里destory函数销毁自己使用的是operator delete ,不可以使用delete,因为delete会自动调用析构函数可是我们已经删除了析构函数,所以不可以使用delete,但是我们可以使用operator delete,它类似于free,不会调用析构函数
四、可变参数模板
1、概念
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
我们可以使用sizeof来获取模板参数的个数
template<class ...Args>
void showList(Args...args)
{
cout << sizeof...(args) << endl;
}
int main()
{
showList(1,2,3,4,5,5,5,6);
showList();
showList(1,2);
return 0;
}
2、模板包的展开
template <class T, class... Args>
void showList(T value, Args... args)
{
cout << value << endl;
showList(args...);
}
//递归终止函数
template <class T>
void showList(T t)
{
cout << t << endl;
}
int main()
{
showList(1, 2, 3, 4, 5, 5, 5, 6);
// showList();
showList(1, 2, 3);
return 0;
}
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
3、STL中具体应用

在某些场景下emplace的效率要高于insert
对于内置类型没有差别,自定义类型例如像unordered_map这样调用insert要插入键值对,或者其它类型对象的场景,效率更高
拿unordered_map举例,insert要插入一个pair,我们要么创建一个临时对象,或者make_pair,
五、lambda表达式
1. lambda表达式各部分说明
2. 捕获列表说明
3、lambda表达式底层实现
六、包装器
1、包装器
包装器算是C++11在具体后端开发使用的较多的特性
我们可以将函数通过包装器,包装起来,然后放入到map等容器中,根据实际情况来调用
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
int add(int a, int b)
{
return a + b;
}
struct Func
{
int operator()(int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int addi(int a, int b)
{
return a + b;
}
double addd(double a, double b)
{
return a + b;
}
};
这里有三种不同的函数,分别演示如何包装
function<int(int, int)> f1 = add;//没有什么特殊条件直接绑定
function<int(int, int)> f2 = Func();//仿函数要绑定对象
function<int(int, int)> f3 = Plus::addi;//静态成员函数指定类域
function<double(Plus, double, double)> f4 = &Plus::addd;//非静态成员函数需要取地址,并且传类名
2、bind
// 原型如下:
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);
3、具体应用
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st;
unordered_map<string, function<long long(long long, long long)>> mp =
{
{"+", [](long long x, long long y){ return x + y; }},
{"-", [](long long x, long long y){ return x - y; }},
{"*", [](long long x, long long y){ return x * y; }},
{"/", [](long long x, long long y){ return x / y; }}
};
for(const auto& e : tokens)
{
if(mp.count(e))
{
long long right = st.top();
st.pop();
long long left = st.top();
st.pop();
st.push(mp[e](left, right));
}
else
{
st.push(stoll(e));
}
}
return st.top();
}
};
总结
例如:以上就是今天要讲的内容,本文仅仅简单介绍了C++11相关特性