盛世宏图 发表于 2024-11-12 13:53:33

C++11的简介

  https://i-blog.csdnimg.cn/direct/b54c51390fe94735805822770b9c0852.gif             杀马特主页:羑悻的小杀马特.-CSDN博客            https://i-blog.csdnimg.cn/direct/797deef385164bae87c6bc5a12bf1449.gif
 ------ ->欢迎阅读             欢迎阅读         https://i-blog.csdnimg.cn/direct/05470035cec24f58b616aa470b258173.gif      欢迎阅读                   欢迎阅读 <-------          
目录

一·列表初始化的厘革:
二·左右值即各自引用的概念: 
2.1左右值:
2.2左右值引⽤:
2.3左值和右值的参数匹配 :
三·右值引用以及移动构造和移动赋值的作用:
3.1移动构造和移动赋值:
3.2:对于一些右值对象传参配合移动语义解决返回值题目分析:
<1>移动构造有无分析:
①右值对象构造,只有拷⻉构造,没有移动构造的场景 :
②右值对象构造,有拷⻉构造,也有移动构造的场景:
<2>移动赋值有无分析:
①右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景:
②右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景:
3.3右值引用对容器操作的提效: 
四·范例分类: 
五·引用折叠:
六·完满转发: 
七·可变模版参数:
7.1先容: 
 7.2包扩展:
7.3empalce系列接⼝:
八·类的一些新功能:
8.1默认的移动构造和移动赋值:
8.2声明时给缺省值:
8.3defult和delete:
①default:
②delete:
8.4final与override:
①final:
②override:
8.5STL中⼀些厘革:
九·const限定符:
9.1顶层const和底层const:
9.2constexpr:
十·lambda:
10.1先容:
10.2表达式用法及其部分先容:
10.2.1构成先容:
 10.2.2捕获列表:
10.2.3lambda原理: 
十一·包装器:
11.1function:
11.2bind:
十二·智能指针:
12.1智能指针引入背景:
12.2智能指针设计条件: 
 12.3智能指针先容:
12.3.1 shared_ptr: 
 12.3.2 weak_ptr:
12.3.3 unique_ptr: 
 12.4内存泄漏:
十三·处理范例:
13.1auto:
13.2decltype:
13.3typedef和using: 

一·列表初始化的厘革:

为我们所知在之前的c++98规定的⼀般数组和布局体可以⽤{}进⾏初始化。
https://i-blog.csdnimg.cn/direct/e55fae730ed849d7bd9d73ef3d422f4e.png
但是到了c++11实现了可以用{}对容器举行一些初始化等,比如push/inset多参数构造的对象时,{}初始化会很⽅便,这是因为每个范例它都会有个initializer_list的一个构造,如许就方便了我们操作。
下面就拿我们实现的一个日期类来说:
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;
} 展示一下上面叙述的{} 的一些操作:
Date d { 2024, 7, 25 };
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
v.push_back({ 2025, 1, 1 })//{}支持的插入

下面我们简略简述一下这个“帮凶”: initializer_list:
它是std::initializer_list的类:这个类的本质是底层开⼀个数组,将数据拷⻉过来,内部有两个指针分别指向数组的开始和结束,然后通过迭代器等(自身也支持迭代器)完成相干容器初始化构造等。
下面我们来看一下容器增长的这个类的构造格式:
//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;
};  https://i-blog.csdnimg.cn/direct/160aba77b50e4c07a403a08d49ea457f.png
从这里我们可以看出底层是两个指针(x86为四个字节);而地址非常接近变量i的,故同样是位于栈中的。
这里我们作为相识,其次会用{}的一些操作即可。

二·左右值即各自引用的概念: 

2.1左右值:

C++11中新增了的右值引⽤语法特性,C++11之后我们之前学 习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。
这里我们都已相识了左值引用(就是我们之前常用的引用),以是这里就不过多先容,那么我们区分左值还是右值的区别就是给它取地址,我们会得到结论:左值可以取地址无论是const修饰还是没有被修饰,而右值却无法取到它的地址。
https://i-blog.csdnimg.cn/direct/9d577cee56a74ca0ad3cd93326786fcd.png
左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有长期状态,存储在内存中,我 们可以获取它的地址,左值可以出现赋值符号的左边,也可以出如今赋值符号右边。界说时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。
右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的暂时对象 等,右值可以出如今赋值符号的右边,但是不能出现出如今赋值符号的左边,右值不能取地址。 
这里我们总结一下:右值一样寻常就是暂时对象,匿名对象,常量,像这些不能取地址的等都是右值,其他都是左值,我们经常也以是否能取地址来判断。 
2.2左右值引⽤:

先上一个简朴的例子:
Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别 名,第⼆个就是右值引⽤,同样的原理,右值引⽤就是给右值取别名。
其次就是:
①左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值。
②右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)(这里相当于给它转化成右值了,此时是一个将亡值)。
固然这么说,但是还有会有一个误区,也就是左右值引用它们的属性是什么?
变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量变 量表达式的属性是左值。(底层都是指针)
下面看一下例子:
https://i-blog.csdnimg.cn/direct/f5ad3214dd4343158226183815cf69b0.png

那么右值引用的作用是什么:右值引⽤可⽤于为暂时对象延⻓⽣命周期,const的左值引⽤也能延⻓暂时对象⽣存期,但这些对象⽆ 法被修改。 
https://i-blog.csdnimg.cn/direct/0250516575654940a5fbcf42fe05401a.png
此时暂时对象的生命周期就从一行往后延伸了。 
2.3左值和右值的参数匹配 :

这里就涉及到实参范例与形参范例匹配题目,也就是对于左值就是找左值引用,固然假如不存在也可以权限缩小即走const的,而对应右值那么走右值引用,假如右值引用不存在就会走const的左值引用。
https://i-blog.csdnimg.cn/direct/4ee0a6c7477141a0bb41d3007832e9e7.png
https://i-blog.csdnimg.cn/direct/de84d079262d44b38c5d030174e9f929.png
三·右值引用以及移动构造和移动赋值的作用:

首先我们要先明白:左值引⽤重要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实 参和修改返回对象的价值。
但是假如是对函数内里创建的暂时对象做返回值,那么无论是末了左值引用返回还是右值引用返回,局部销毁的时间,空间也销毁了,因此如许也是无法解决的。
3.1移动构造和移动赋值:

   ①移动构造函数是⼀种构造函数,雷同拷⻉构造函数,移动构造函数要求第⼀个参数是该类范例的引 ⽤,但是差别的是要求这个参数是右值引⽤,假如还有其他参数,额外的参数必须有缺省值。
② 移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,雷同拷⻉赋值函数,移动赋值函 数要求第⼀个参数是该类范例的引⽤,但是差别的是要求这个参数是右值引⽤。
那么移动的概念又是什么呢?这里可以简朴理解为偷取,也就是我们实现一个指针把原指针的数据偷走了(swap),如许原指针就指向空了,而我们的新指针指向的就是那块区域。(固然是右值引用才会有的)。
对于像string/vector如许的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有 意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的范例,他的本质是要“窃取”引⽤的 右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率。下⾯的bit::string 样例实现了移动构造和移动赋值。
下面我们来看一下它们俩是怎样实现的:
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}

// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
3.2:对于一些右值对象传参配合移动语义解决返回值题目分析:

下面我们讲一下对于右值对象传参及返回时间,编译器做出来的一系列优化操作:
下面在自己实现的string类中测试一下:
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;
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';
_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;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char;
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 = ch;
++_size;
_str = '\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;
};
}
<1>移动构造有无分析:

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 - '0' : 0;
                int val2 = end2 >= 0 ? num2 - '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());
        cout << "******************************" << endl;
        return str;
}
//移动构造有无测试:
int main()
{
        string ret = addStrings("11111", "2222");
        cout << ret.c_str() << endl;
        return 0;
} ①右值对象构造,只有拷⻉构造,没有移动构造的场景 :

https://i-blog.csdnimg.cn/direct/ee9a5d14420b4d09a7d34285a96ca6bb.png

 而对应vs22这里就一步到位,直接让main函数中的ret所指向的区域让str也指向,有点抽象了。
②右值对象构造,有拷⻉构造,也有移动构造的场景:

https://i-blog.csdnimg.cn/direct/ca684df168b84b529a7f0fad830ee662.png

<2>移动赋值有无分析:

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 - '0' : 0;
                int val2 = end2 >= 0 ? num2 - '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());
        cout << "******************************" << endl;
        return str;
}
//移动赋值有无测试:
int main()
{
        string ret;
        ret = addStrings("11111", "2222");
        cout << ret.c_str() << endl;
        return 0;

} ①右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景:

https://i-blog.csdnimg.cn/direct/8771c3bb53e0453d84c9b173a91fba97.png
②右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景:

https://i-blog.csdnimg.cn/direct/05f68d2f21654bd8bb2b642858e45287.png
3.3右值引用对容器操作的提效: 

   ①当实参是⼀个左值时,容器内部继续调⽤拷⻉构造进⾏拷⻉,将对象拷⻉到容器空间中的对象 (因为所传的参数是自界说范例都会对非引用的函数都要调用它的拷贝构造)。 
②当实参是⼀个右值,容器内部则调⽤移动构造,右值对象的资源到容器空间的对象上。
因此一些容器的push_back和insert就支持了右值版本的参数范例,减少了资源的拷贝,再比如emplace系列,这里涉及可变模版参数,后续讲。

四·范例分类: 

C++11以后,进⼀步对范例进⾏了划分,右值被划分纯右值(purevalue,简称prvalue)和将亡值 (expiringvalue,简称xvalue)。
 
   ①纯右值:就是匿名对象,暂时对象,字面常量这类:1,a+b,string(“abc”)等
②将亡值:返回右值引⽤的函数的调⽤表达式和转换为右值引⽤的转换函数的调⽤表达,如 move(x)(强转为右值)、static_cast<int&&>(x)(强转为右值引用,左值属性)。
③泛左值(generalizedvalue,简称glvalue),泛左值包含将亡值和左值。
下面一张图理清关系:
https://i-blog.csdnimg.cn/direct/a270d5543f724cbe9059b39402387004.png
五·引用折叠:

简朴来说就是以前不答应出现int& && r = i;如许的形式,c++11出现了支持如许操作:
右值引⽤的右值引⽤折叠成右值引⽤(int &&&&x->int &&x),全部其他组合均折叠成左值引⽤(int && &x,int & &&x->int &x)。
下面看一下例子:
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
下面是一个根据模版实例化差别引用的例子:
// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤
template<class T>
void f1(T& x)
{}
// 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤
template<class T>
void f2(T&& x)
{}

f1<int&>(n);
f1<int&>(0); //实例化出左值引用无法引用右值

f1<int&&>(n);
f1<int&&>(0); // 实例化出左值引用无法引用右值

f1<const int&>(n);
f1<const int&>(0);//const左值引用的可以引用右值

f1<const int&&>(n);//const右值引用的可以引用右值,权限缩小
f1<const int&&>(0);

f2<int>(n); //右值引用无法引用左值
f2<int>(0);

f2<int&>(n);
f2<int&>(0); //左值引用无法引用右值

f2<int&&>(n); // 右值引用无法引用左值
f2<int&&>(0);

   对于f2而言, 这就是一个模版,根据我们转达的模版范例差别,我们实例化右值引用它这个x就变成右值引用,实例左值引用,它这个模版就变成左值引用,
https://i-blog.csdnimg.cn/direct/df5f112e6b93433fa4522b6e8cf16ecd.png
   这里根据我们传的值差别自动推导T的范例,也就是我们传的实参是T&&范例,推导出T的范例,这里就运用了引用折叠的知识。
六·完满转发: 

完满转发forward本质是⼀个函数模板,他重要还是通过引⽤折叠的⽅式实现,具体用法如下:
https://i-blog.csdnimg.cn/direct/9cee21361e3c4940a4de5c9fff0801e4.png
也可以把它当成强转来理解。 
   简朴来说就是我们当传一个参数给一个函数,此时它内里的形参假如是右值引用,假如又拿这个形参去调用新的函数,由于这个形参是左值属性,它会自动匹配对应的左值,而我们必要它走右值的故可以在它要传之前给它,实现给它的左值属性变成右值,来完成我们想要的操作。
七·可变模版参数:

7.1先容: 

可变模版参数就是可以实例化出差别个数模版参数的模版(可以是差别参数的类模版也可以是函数模版)。
可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。
如:
template <class ...Args> void Func(Args... args) {}
• template <class ...Args> void Func(Args&... args) {}
• template <class ...Args> void Func(Args&&... args) {} 这里假如是模版参数包就是typename/class...+模版范例,函数参数包就是模版范例+...+形参;如许就形成了一个一个很多差别范例的参数长度差别的的模版函数了。
这⾥我们可以使⽤sizeof...运算符去盘算参数包中参数的个数。如:sizeof...(args)。
   这里区分一下:
函数模版:一个模版实例化出很多函数(参数个数相同)。
可变函数模版:一个模版实例化多个参数的模版函数。
 7.2包扩展:

对于⼀个参数包,我们除了能盘算他的参数个数,我们能做的唯⼀的事变就是扩展它。
说白了,我们简朴模拟一下扩展它:把它取出来打印一下,这时可能会说直接遍历它不就行了,但是基于它的底层实现,无法做到,因此我们要递归每次调用自身就展开一个参数把它打印,直到末了无参数了让它走空的递归函数,如许简朴模拟一下过程。
这里我们要知道它递归是个形象的说法不是运行时间递归而是编译的时间的递归调用解析。
void ShowList()
{
        // 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数
        cout << endl;
}
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;
}
https://i-blog.csdnimg.cn/direct/a04e7df61c3648b78a3ed2c4a5e6e556.png

https://i-blog.csdnimg.cn/direct/bb30debed8d544219b77b9c4d448fde3.png
固然也有另一种模拟的包扩展:
template <class T>
const T& GetArg(const T& x)
{
        cout << x << " ";
        return x;
}
template <class ...Args>
void Arguments(Args... args)
{}
template <class ...Args>
void Print(Args... args)
{
       
        Arguments(GetArg(args)...);
}

int main()
{
        Print(1, string("xxxxx"), 2.2);
        return 0;
} 这里就简朴说一下它的思路:
这里我们的Arguments就充当一个空函数的作用,它所担当的一堆实参就是 GetArg函数的返回值,也就是说把参数包一个个都交给了 GetArg,这个函数完成了全部的打印任务,然后返回空给 Arguments,末了返回Print即可。
7.3empalce系列接⼝:

分为emplace和emplace_back:实在效率是高于push和push_back,因此以后建议利用前两者,
   其次就是前两者差别于后两者的是就是可以不消像后两者一样用容器中的对象去初始化,而是可以直接用构造这个对象的参数去初始化,(这里涉及了它底层收到转达的这个参数该怎样操作题目)
:实在就是由于它支持了可变模版参数,于是就把你给它的这个参数包不停往里传(这里留意右值引用是左值属性可能会走不是我们预期的函数,因此转达的时间保持它的右值属性给它完满转发一下),直到走到了构造处,完成构造成对象末了就完成了此操作。背面我们会展示一下这个过程。

template <class... Args> void emplace_back (Args&&... args);
template <class... Args> iterator emplace (const_iterator position,
Args&&... args);
 https://i-blog.csdnimg.cn/direct/3d44c37d98284c6e8fc63d3c36a159c8.png
这里可以看出emplace_back支持可以插入参数包,而push_back却不能。 
下面就是这个emplace_back可以直接插入参数包的具体操作过程:
 

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->_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->_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)...);
        }

        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->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
        }
private:
        Node* _head;
};

因为我们这里传的是参数包,因此担当它的函数都要包含这个参数模版,其次就是因为我们一开始的emplace_back是右值引用,因此其他担当的也应该是右值引用再之就是之间要留意完满转发。 
八·类的一些新功能:

下面是c++11之后新更新的一些新的操作功能:
8.1默认的移动构造和移动赋值:

C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
这些也就是假如我们没写(析构函数、拷⻉构造、拷⻉赋值重载,这三个函数是绑定的,因为无资源开毁的时间我们都不写让它浅拷贝就好,假如有的话那么就要深拷贝了,也就是要写了),此时编译器会自己生成移动构造,假如是内置范例就是浅拷,自界说范例就会调用它的移动构造,假如无,就浅拷;对于移动赋值也是如此,但是一旦自己提供一个,编译器都不会再自己生成。
   因此我们做了一个规定假如无资源申请与销毁我们这几个都不写,让它走默认生成的浅拷贝,反之就都要写;话说得好,移动构造和赋值效率高,说白了只是对那些必要深拷贝的才会提高效率(转移资源)。

8.2声明时给缺省值:

这里就是以前类和对象讲述的,假如没传实参就会走缺省值,(告急的就是,声明和界说缺省值只能给一个)就不做多说明了。
8.3defult和delete:

①default:

  它的作用就是告诉编译器要自己默认生成它:比如:我们再一个类内写了拷贝构造,但是没写构造,我们想要它的默认构造(此时假设声明给了缺省值),但是假如不写构造它会报错,但是写了也没故意义故可以函数声明背面=default。
②delete:

在C++11中,只需在该函数声明加上=delete即可,该语法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数:比如我们像拷贝构造那样的三个函数都没写,想让编译器生成默认的,但是又不盼望移动构造等生成,可以在其函数声明=delete。
8.4final与override:

①final:

我们不盼望一个类被继承(或者不让它的子类出现像多态如许的举动)可以在类名背面加上final。
②override:

只答应加在子类的虚函数背面,检查子类的虚函数是否被重写,假如重写了父类的就不报错,否则直接报错。
8.5STL中⼀些厘革:

①STL增长了很多新容器,背面这两个重要是常用的:unordered_map和unordered_set。
② STL中容器的新接⼝函数范例也增长了,最告急的就是右值引⽤和移动语义相干的push/insert/emplace系列 接⼝和移动构造和移动赋值,还有initializer_list版本的构造,以及范围for的遍历等,这些实在会利用就好。
九·const限定符:

9.1顶层const和底层const:

指针或者引用自身被const修饰就是顶层const,指向的对象被const修饰就是底层const。
   我们可以这么记:*或者&左就是底层const,右边就是顶层const;无这两者就都是顶层const。
int main()
{
int i = 0;
int* const p1 = &i; // 顶层const
const int ci = 42; // 顶层const
const int* p2 = &ci; // 底层const
const int& r = ci; // 底层const
return 0;
}
9.2constexpr:

①constexpr(constant expression):只能修饰常量表达式,且这个常量表达式只能用常量初始化不能用变量。
这里首先判断可不可以修饰:第一就是把constexpr舍弃后,句子语法建立,其次就是修饰的背面是不是常量表达式。
如:
constexpr int aa = 1;
int b=2;
constexpr const int x=b;//修饰的表达是是变量赋值。报错 那什么是常量表达式呢?
    不会改变并且在编译过程中就能得到盘算结果的表达式,字⾯值、常量表达式初 始化的const对象都是常量表达式,要留意变量初始化的const对象不是常量表达式。
const int a = 1; // a是常量表达式
const int b = a + 1; // b是常量表达式
int c = 1; // c不是常量表达式
const int d = c; // d不是常量表达式
const int e = size(); // e不是常量表达式
②固然constexpr可以修改变量,constexpr修饰的变量⼀定是常量表达式, 且必须⽤常量表达式初始化,否则会报错。如:
constexpr int aa = 1;
constexpr int bb = aa+1;
//constexpr int cc = c; 报错 ③constexpr可以修饰指针,constexpr修饰的指针是顶层const,也就是指针本⾝。(也就是说constexpr只是起到修饰作用,被修饰的量怎样举行还要看它自己的性子)如:
constexpr const int* p3 = &d; //constexpr修饰的是p3本⾝,const修饰*p3 ④constexpr还可以修饰函数的返回值,要求函数体中,包含⼀条return返回语句,修饰的函数可以 有⼀些其他语句,但是这些语句运⾏时可以不执⾏任何操作就可以,如范例别名、空语句、using 声明等。并且要求参数和返回值都是字⾯值范例(整形、浮点型、指针、引⽤),并且返回值必须是 常量表达式。  如:
constexpr int func(int x)
{
return 10 + x;
}
constexpr int fxx(int x)
{
int i = x;
i++;
cout << i << endl;
return 10 + x;
}

int main{
constexpr int N2 = func(10);
//constexpr int N3 = func(i); 返回值不是常量表达式
constexpr int N5 = fxx(10); // 报错 ,返回值是常量表达式,但函数体内运行时还执行了其他操作

} ⑤constexpr不能修饰⾃界说范例的对象,但是⽤constexpr修饰类的构造函数后可以就可以,但是初始化该 构造函数成员时必须使⽤常量表达式,并且函数体内部的语句运⾏时可以不执⾏任何操作就可以, 跟修饰函数雷同。 如:
class Date{
public:
constexpr Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
constexpr int GetYear() const
{
return _year;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
constexpr Date d1(2024, 9, 8);//修饰自定义类型对象(参数必须字面值类型初始化)
constexpr int y = d1.GetYear();//修饰成员函数的返回值(类似普通函数操作)
return 0;
}
⑥constexpr可以修饰模板函数,但是也要满足除了return其他运行时不执行的操作 ;其次就是由于模板中范例的不确定性,因此模板函数实例化后的函数是否 符合常量表达式函数的要求也是不确定的;因此c++11规定,假如onstexpr修饰的模板函数实例 化结果不满⾜常量表达式函数的要求自动忽略constexpr,也就和平凡模版函数一样了。
template<typename T>
constexpr T Func(T t)
{
return t;
}
int main(){
string ret1 = Func<string>("111111");//不是字面值常量,直接忽略constexpr存在
constexpr int ret2 = Func<int>(10);//是字面值常量
return 0;


} 十·lambda:

10.1先容:

lambda 表达式本质是⼀个匿名函数对象,跟平凡函数差别的是他可以界说在函数内部;
但是它的界说虽差别于仿函数,但是用法相似都是对象调用+(参数);对于lambda它也是有返回范例的但是我们风俗用auto去自动推导它。
10.2表达式用法及其部分先容:

 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的 范例界说起来⽐较⿇烦,仿函数要界说⼀个类,相对会⽐较⿇烦。使⽤ lambda 去界说可调⽤对 象,既简朴⼜⽅便。
 lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中界说线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不停接触到。
   lambda表达式的格式: (parameters)-> return type { function boby } 
10.2.1构成先容:

① :捕获列表,该列表总是出如今 lambda 函数的开始位置,编译器根据来判断接下来的代码是否为 lambda 函数,捕获列表能够捕获上下⽂中的变量供 lambda 函数使⽤,捕获列表可以传值和传引⽤捕获,也可以是它们俩的混合捕获,捕获列表为空也不能省略。
 ②(parameters) :参数列表,与平凡函数的参数列表功能雷同,假如不必要参数转达,则可以连 同()⼀起省略。
③->return type :返回值范例,⽤追踪返回范例形式声明函数的返回值范例,没有返回值时此 部分可省略。⼀般返回值范例明白情况下,也可省略,由编译器对返回范例进⾏推导。
④{function boby} :函数体,函数体内的实现跟平凡函数完全雷同,在该函数体内,除了可以 使⽤其参数外,还可以使⽤全部捕获到的变量,函数体为空也不能省略。
下面就是一个简朴的lambda表达式:
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
 10.2.2捕获列表:

这里什么样的变量必要捕获呢?对于参数列表内的肯定不消,全局和静态的也不消,只有我们用的
 局外的才必要捕获才能用。
这里又分为两种范例的捕获(都是隐式捕获,用谁捕获谁):
①传值捕获:x:假如我们是传值,那么内里默认是cosnt不能修改的;假如全部都传值捕获可以直接=。
②传引用捕获:&x:假如我们是传引用捕获,是可以修改的;全部传引用捕获可直接&。
固然两种也是可以混合利用的:;[&,x,y];[=,&x]:后两个和我们理解的有区别:它不是除了给定的之外其他都传引用捕获或者传值捕获,而是用到哪个变量就去捕获它。
https://i-blog.csdnimg.cn/direct/add5e8bc6d50472da9ee36db002a1c54.png
https://i-blog.csdnimg.cn/direct/8bafea8e3bbb4811acb78bf67a635f74.png

一样寻常来说传值捕获是不可以修改的,但是假如我们在参数列表背面加上mutable可以取消其常量性 (假如要加mutable则()不能省略)。
https://i-blog.csdnimg.cn/direct/30184ecadf944725bad8a44dfcb51973.png

10.2.3lambda原理: 

lambda底层是仿函数对象,也就说我们写了⼀个 lambda 以后,编译器会⽣成⼀个对应的仿函数的类,仿函数的类名是编译按⼀定规则⽣成的,包管差别的 lambda ⽣成的类名差别。
这里我们拿lambda的构成和仿函数这个类对比一下:
   lambda参数/返 回范例/函数体就是仿函数operator()的参数/返回范例/函数体, lambda 的捕获列表本质是⽣成 的仿函数类的成员变量,也就是说捕获列表的变量都是 lambda 类构造函数的实参,固然隐式捕获,编译器要看使⽤哪些就传那些对象。
下面就是感兴趣就可以相识一下它的原理,固然我们会利用就可:
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = (double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}

// lambda
auto r2 = (double money, int year) {
return money * rate * year;
};
// 捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量
// 下⾯operator()中才能使⽤
00D8295C lea eax,
00D8295F push eax
00D82960 lea ecx,
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)
// 函数对象
Rate r1(rate);
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr
00D82970 movsd mmword ptr ,xmm0
00D82975 lea ecx,
00D82978 call Rate::Rate (0D81438h)
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr
00D8298A movsd mmword ptr ,xmm0
00D8298F lea ecx,
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr
00D829A6 movsd mmword ptr ,xmm0
00D829AB lea ecx,
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)
十一·包装器:

分为function和bind,它们的头文件都是<functional>
11.1function:

首先它是一个可变参数模版(固然也是一个包装器):
template class Ret, class... Args> class functionRet(Args...)>; function 的实例对象可以包装存 储其他的可以调⽤对象,包罗函数指针、仿函数、 lambda 、 bind 表达式等。 
   下面演示一下它包装各种范例的对象怎样利用:
 模型:function<返回范例(参数范例)> 封装后的对象=被调用的对象。
        封装后的对象(实参)

#include<functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};

平凡函数或者lambda表达式: 
 https://i-blog.csdnimg.cn/direct/c1ea9f7bae7e41a99d2da7d4c472f181.png

假如它包装的是函数,平凡函数取不取地址都一样(包装函数指针);但是假如是类的静态成员函数或者类的平凡成员函数(这些函数默认有个this指针,故我们要从包装的function参数中加上,固然也可以是引用也可以是指针或者左右值对类的引用,目的就是可以进入类内去访问这个函数)肯定要取地址的,因此这里我们只要是函数指针被包装都取地址。
//静态成员函数:
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
//传指针:
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
//类自己:
function<double(Plus&, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
cout << f6(pd, 1.1, 1.1) << endl;
//类的右值引用
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;//属性改成右值
cout << f7(Plus(), 1.1, 1.1) << endl;//匿名对象本身就是右值 函数指针、仿函数、 lambda 等可调⽤对象的范例各不相同, function 的优势就是统 ⼀范例,对他们都可以进⾏包装,如许在很多地⽅就⽅便声明可调⽤对象的范例,如(作为map的范例参数并结合lambda利用)下面是一道逆波兰表达式的题:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
// function作为map的映射可调⽤对象的类型
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int x, int y){return x + y;}},
{"-", [](int x, int y){return x - y;}},
{"*", [](int x, int y){return x * y;}},
{"/", [](int x, int y){return x / y;}}
};
for(auto& str : tokens)
{
if(opFuncMap.count(str)) // 操作符
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();

int ret = opFuncMap(left, right);
st.push(ret);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
如许不就大大简化了我们繁琐的if 判断了,直接对应取出lambda调对象传参就可。
11.2bind:

bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对吸收 的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。 bind 也在这个头⽂件中。
调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的 参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰ newCallable的参数,它们占据了转达给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占 位符放到placeholders的⼀个定名空间中。
重点来了:
它可以调整参数的位置,增减传参个数,以及绑死参数的位置(我们常用末了一个)。
上面说的实在就是一些繁琐的概念,bind这个我们只要会用就好,下面简略的说一下用法:
在我们利用时间给它using一下:
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
auto 对象(这里雷同上面function的对象一样雷同仿函数形式)=bind(要绑定的函数名字,_1,_2,...)->(这里我们所调用的函数想给它传参就根据对应没被绑定的位置传参,假如要想给这个函数某个参数写死,调用时间这个形参是固定值,那么就可以在bind里对应函数位置给它写死,其次就是我们调用这个对象给它传参数,依次对应_1,_2...;然后去一样规则传给原函数,假如原函数有被写死的参数,就跳过,将当前的_跳过它继续往后传)->具体看下例子秒懂。
下面上例子就好了:
 https://i-blog.csdnimg.cn/direct/10ec529047bf43ff97ac838128d1255b.png
固然它还可以结合function 一起用,比如function包装类的平凡成员函数,必要传类指针之类的,比较贫苦,我们可以给它绑定死。
https://i-blog.csdnimg.cn/direct/ecb93b1134b543538bb4dae5febe30ff.png
如许每次就不消给f1传plus()了。 
举个例子(关于绑死参数位置的实际应用):
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
盘算增长函数的这个lambda中我们给它由固定年限把它的利率给绑定死了,如许不就符合我们实际的一些操作了嘛。 
十二·智能指针:

12.1智能指针引入背景:

首先我们以一个例子来引出下面我们要讲解的智能指针:
double Divide(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{

int* array1 = new int;
int* array2 = new int;
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...)
{
//1.处代码
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
delete[] array1;
delete[] array2;
throw;
}
//2处代码
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
   这里我们1处的代码,也就是这个catch处的代码是专门为Divide函数抛非常而写,而假如不抛非常就走了2处代码即可;但是假如是多个new在那里开辟空间呢?那我们就要写更多,但是最告急的是new也会抛非常:

这里我们假设 array1创建好了,然后array2抛了非常,因此我们就要在new背面对相应的new非常举行catch判断,但是假如是多个呢,不就贫苦了,因此智能指针就诞生了:(也就是说它是一个类的对象,我们在像如许的情况内里直接用智能指针如许,然后我们无需在内里涉及delete,因为它是一个对象,到结束自己会析构“它”所指向的空间)。
12.2智能指针设计条件: 

首先引入概念先容智能指针设计的思路:
首先就是RALL:
   RAII是Resource AcquisitionIs Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是 ⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指 针、⽹络毗连、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问, 资源在对象的⽣命周期内始终保持有效,末了在对象析构的时间释放资源,如许保障了资源的正常 释放,避免资源泄漏题目。 
这里为了适应RALL的特性,我们只能指针的这个类同样重载了operator*/operator->/operator[]  这些;但是并不是全部的智能指针都支持RALL,比如:我们背面要先容的weak_ptr这款智能指针它就不支持(就是为相识决shared_ptr循环引用题目而诞生的)。
 12.3智能指针先容:

C++11,引⼊了unique_ptr和shared_ptr和weak_ptr。必要留意的是unique_ptr对应boost的 scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
C++尺度库中的智能指针都在这个头⽂件<memory>下⾯,我们包含就可以是使⽤了, 智能指针有好⼏种,除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为,原理上⽽⾔重要是解 决智能指针拷⻉时的思路差别。
   四种指针简介大框架:
①auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给 拷⻉对象,这是⼀个⾮常糟糕的设计,因为他会到被拷⻉对象悬空,访问报错的题目,C++11设计 出新的智能指针后,强烈建议不要使⽤auto_ptr。其他C++11出来之前很多公司也是明令禁⽌使⽤ 这个智能指针的。
② unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不⽀持拷 ⻉,只⽀持移动。假如不必要拷⻉的场景就⾮常建议使⽤他。
③ shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是⽀持拷⻉, 也⽀持移动。假如必要拷⻉的场景就必要使⽤他了。底层是⽤引⽤计数的⽅式实现的。
④weak_ptr是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全差别于上⾯的智能指 针,他不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr 的⼀个循环引⽤导致内存泄漏的题目。具体细节下⾯我们再细讲。
其次,我们还要知道,这里开辟好空间析构都是调用的delete;也就是说这些智能指针都不支持开辟一连空间的指向,只能维护单个对象;但是它后来特化支持了“删除器”(可调用的对象);如许就支持了“string[]”数组形式。比如: unique_ptr和shared_ptr都特化了⼀份[]的版本,使⽤时 unique_ptr up1(new Date);shared_ptr sp1(new Date); 就可以管理new[]的资源。 
这里auto_ptr上面也说了存在风险题目,故这里我们就不做多说了,不要用它;因今背面讲解就不多说了。 

12.3.1 shared_ptr: 

对于它,实在只要记着可以拷贝(相当于资源共享),又可以移动;然后呢它底层有个计数器,专门纪录多少个指针指向你这块资源,当这种指针析构到这个计数器为0,就delete这块空间(因此我们背面模拟简朴实现它的时间这个计数器用整型指针模拟(因为如许每个对象拿到都是它的地址了,好操作,好维护))。
下面我们展示一下我们模拟实现的shared_ptr这个类(这里由于它可以转达这个delete(也就是这个删除器的仿函数或者lambda,我们多写了一个差别的,也就是两个参数的构造函数))
这里思路:个人认为重点在于这个引用计数的设计:
重要这⾥⼀份资源就必要⼀个 引⽤计数,以是引⽤计数才⽤静态成员的⽅式是⽆法实现的,要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计 数,shared_ptr对象析构时就--引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是末了⼀ 个管理资源的对象,则析构资源。
形象先容:
https://i-blog.csdnimg.cn/direct/e71bea4faf4445bdaa94e8eb7071067d.png
下面具体展示:
namespace ptr {
        class Fclose
        {
        public:
                void operator()(FILE* ptr)
                {
                        cout << "fclose:" << ptr << endl;
                        fclose(ptr);
                }
        };


        template<class T>
        class shared_ptr
        {
        public:
                explicit shared_ptr(T*ptr)
                        :_ptr(ptr)
                        , _pcount(new int(1)) {}

                template<class D>
        explicit        shared_ptr(T* ptr, D del)
                        : _ptr(ptr)
                        , _pcount(new int(1))
                        , _del(del)
                {}
                  
                shared_ptr(const shared_ptr<T>& sp) {
                        _ptr = sp._ptr;
                        _pcount = sp._pcount;
                        (*_pcount)++;
               }
               

                shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
                        if (_ptr != sp._ptr) {
                                if (--(*_pcount) == 0) {
                                        //delete _ptr;
                                        _del(_ptr);
                                        delete _pcount;
                                }
                                _ptr = sp._ptr;
                                _pcount = sp._pcount;
                                (*_pcount)++;
                        }
                        return *this;
               }

                ~shared_ptr() {
                        if (--(*_pcount) == 0) {
                                //delete _ptr;
                                _del(_ptr);
                                delete _pcount;

                        }
                }
                T& operator*()
                {
                        return *_ptr;
                }

                T* operator->()
                {
                        return _ptr;
                }

                int use_count()
                {
                        return *_pcount;
                }
        private:
                T* _ptr;
                int* _pcount;
                function<void(T*)>_del = [](T* ptr) {delete ptr; };//这里auto和decltype都不行,只能上包装器

        };

}   这里值得一说的也就是这个赋值重载的设计了:
首先我们分为1.赋值给自身(直接不走返回这个*this)
                      2.赋值给其他存在的对象,这里我们就还要分情况:
                                                                                 ①被赋值对象所指向的资源的count--后为1,那么此时直接释放掉这块空间,完成给被赋值对象拷贝并count++即可。
                                                                                 ②假如--count后不为0,那么说明此刻这段资源还有其他智能指针指向着,不能释放,故这块资源已经--了,只必要重新完成给被赋值对象拷贝并count++即可。(代码同上)
1·其次就是我们可以细节观察到这里的构造函数都用explicit修饰了,也就是不答应平凡指针隐式范例转换如:
ptr::shared_ptr<string> sp1 = new string("a");// 库里加了explicit不支持隐式转换
ptr::shared_ptr<string>sp1(new string("abc"));//只支持  2·再之shared_ptr 除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值 直接构造。(template <class T, class... Args>shared_ptr =make_shared (Args&&... args); )(这里我们没有模拟实现,故就调用库内里的了。)
如:
shared_ptr<string> sp1 = make_shared<string>("aaaaaa"); 相对于我们之前学的make_pair就是多了个模版实例化。 
3·固然它也支持了operator bool的范例转换(我们也就不实现了),假如智能指针对象是⼀个 空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断 是否为空。  
如:
if (sp3)cout << "no nullptr" << endl; 4·我们来讲述一下这个两个参数的构造函数的应用(但是不是说可以仿函数或者lambda):
这里我们假如利用仿函数,于是我们加了一个封装仿函数的类:
        class Fclose
        {
        public:
                void operator()(FILE* ptr)
                {
                        cout << "fclose:" << ptr << endl;
                        fclose(ptr);
                }
        };
下面就是我们特别的资源对它(这个删除器)的应用(固然库里也可以用函数指针,这里不常用我们就不消了(也就是说我们转达的这个对象能调用释放资源就好)):
//shared_ptr 可以lambda也可以仿函数;建议lambda
//ptr::shared_ptr<FILE> sp2(fopen("test.cpp", "r"), [](FILE* ptr) {fclose(ptr);    });
        //ptr::shared_ptr<FILE> sp2(fopen("test.cpp", "r"), ptr::Fclose());
//上面讲述到的那个特化[]版本的可调用对象:
//ptr::shared_ptr<string[]> sp1 ( new string );  
 12.3.2 weak_ptr:

此刻,它一出场,也就是我们shared_ptr无法解决的循环引用题目了:
下面我们先上图:
也就是我们的
https://i-blog.csdnimg.cn/direct/a773640855a54402b43e7e7e68248f10.png
 
这个图也就是我们一个自界说类包含了这个智能指针(图上是shared_ptr)范例的prev和next:
此时假如我们析构右边的节点,就要先析构next,然后就是要析构n1,析构n1就要先析构n2的prev,然后就是先析构n2;如许不就绕回来了。具体一下比如我们在n1指向的对象里析构了next 
那么引用计数变成了1,然后我们还要再析构一次n2的指向,达不到我们想要的目的(析构一次next就OK);因此这里把这个next和prev设计成了weak_ptr范例。
weak_ptr绑定到shared_ptr时不会增长它的 引⽤计数,next和prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的题目。
差不多就是如许:
https://i-blog.csdnimg.cn/direct/741a8a5673c6429985428ddfa4bfa3e3.png
我们到时间直接通过weak_ptr (next),它的值就是我们 这里n2值,也就是n2指向资源,直接销毁即可,也就是通过调用它的lock(背面我们会讲到)返回⼀个管理资源的shared_ptr,然后传删除器(lambda)直接完成删除(如许就完成了next和prev我们所期待的操作)。
weak_ptr具体先容:
这里我们用这个weak_ptr只必要留意以下几点就可以:
   ①weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么假如他绑定的 shared_ptr已经释放了资源,那么他去访问资源就是很伤害的。
②weak_ptr⽀持expired检查指向的 资源是否逾期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤ lock返回⼀个管理资源的shared_ptr,假如资源已经被释放,返回的shared_ptr是⼀个空对象,如 果资源没有释放,则通过返回的shared_ptr访问资源是安全的。
下面我们简朴模拟一下weak_ptr实现(它是不参与count管控的,故它use_count后返回的是它绑定的资源的count):
template<class T>
class weak_ptr
{
public:
weak_ptr()
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
private:
T* _ptr = nullptr;
};
}
然后我们测试一下它和shared_ptr联合利用的场景(请看图):
https://i-blog.csdnimg.cn/direct/f7a37dc34774484cb3bb8d3bc5a108fa.png 
 这里告诉们weak_ptr无地址,只是绑定资源,其次就是要留意lock转换成shared_ptr去访问绑定的资源,然后比如shared_ptr或者unique_ptr置nullptr相当于让它指向空,故原先资源的count--;它如今的count置为0。
auto sp2 = make_shared<string>("333333");
cout << wp.expired() << endl;
//这里它所绑定的资源是否还在,如果到期就返回1;否则0 因此关于weak_ptr大概就到这里。
12.3.3 unique_ptr: 

这里和上面讲的shared_ptr差不多,只不过从它的名字可以看出它是不能拷贝和赋值重载的(独特的,不支持资源共享),其他用法和shared_ptr差不太多了。
也就是说实现的时间把这两个函数直接delete掉了;我们就不实现了;有一个区别就是它的删除器操作和shared_ptr差别:
它给自己范例模版转达调用对象的范例而shared_ptr无需,故我们要说这里建议用仿函数;下面请看例子:
//unique_ptr 还是仿函数:
//unique_ptr<FILE,ptr::Fclose> up(fopen("test.cpp", "r"), ptr::Fclose());
auto u = [](FILE* ptr) {fclose(ptr);    };
unique_ptr < FILE, decltype(u) > up(fopen("test.cpp", "r"), u);//先要auto给lambda形成对象再decltype 其他常见用法就和shared_ptr一样了,其他就不多说了。

 12.4内存泄漏:

   什么是内存泄漏?:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释 放或者发⽣非常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消散,⽽是应⽤程序分 配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。
内存泄漏的危害?:平凡程序运⾏⼀会就结束了出现内存泄漏题目也不⼤,进程正常结束,⻚表的映射 关系排除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服 务、⻓时间运⾏的客⼾端等等,不停出现内存泄漏会导致可⽤内存不停变少,各种功能响应越来越 慢,最终卡死。
因此,我们上面所讲的智能指针就充分的得到了证明,它存在的价值也是不可忽略的。 

十三·处理范例:

13.1auto:

   ①auto就是可以帮我们推导参数范例(当参数范例长的时间常用);
②可以做返回值范例,但是不能做函数参数。
③不可以界说数组和其他遍历。
④在同一行auto的范例应该相同。
⑤在声明指针的时间auto和auto*都可以,但是引用必须auto&。
其次就是auto在推导的时间,会保留底层const而忽略掉顶层const;再者就是假如auto要去推导引用,我们必要去表明auto&,否则直接auto推导的是引用的对象的范例而非这个引用的范例。
下面请看例子:
int i = 0;
const int ci = 42; // 顶层const
int* const p1 = &i; // 顶层const
const int* p2 = &ci; // 底层const
const int& ri1 = ci; // 底层const
const int& ri2 = i; // 底层const
int& ri3 = i;
auto r1 = ci; // r1类型为int,忽略掉顶层const
r1++;
auto r2 = p1; // r2类型为int*,忽略掉顶层const
r2++;
auto r3 = p2; // r3类型为const int*,保留底层const
(*r3)++; // 报错
auto r4 = ri1; // r4类型为int,因为引⽤对象初始化auto类型时,推导为引⽤对象
的类型,其次会忽略掉顶层const
r4++;
auto r5 = ri2; // r5类型为int,因为引⽤对象初始化auto类型时,推导为引⽤对象
的类型
r5++;
auto r6 = ri3; // r6类型为int,因为引⽤对象初始化auto类型时,推导为引⽤对象
的类型
r6++;
const auto r7 = ci; // r7类型为const int
auto& r8 = ri1; // r8类型为const int&,因为const 因为为底层const 被保留
auto& r9 = ri2; // r9类型为const int&,因为const 因为为底层const 被保留
auto& r10 = ri3; // r8类型为int&
这写就把上面的要点都概括了。
13.2decltype:

decltype(declear type):它就是推导范例但是不会走这个()里的表达式子之类的。
int i = 0;
const int ci = 0;
const int& rci = ci;
decltype(i) m = 1; // m的类型是int
decltype(ci) x = 1; //x是const int
①decltype(f()) x; 必要留意的是编译器并不会实际调⽤f函数,⽽是⽤f的返回类 型作为x的范例(假如我们不想让它走初始化可以用它),这里也就和auto差别了。
②其次就是和auto对处理const的方式也差别,比如它推导会保留顶层const不忽略底层const而auto是保留底层const并且忽略顶层;比如要推导引用而auto必须用auto&,而decltype可以直接用(如int &a=1;decltype(a)->int& )。
decltype(rci) y = x; // y的类型是const int&
decltype(rci) z; //这里是const int&不能给它随机赋值,是常量故报错
int* p1 = &i;
decltype(p1) p2 = nullptr; // p2的类型是int*
③这里有一些特别操作,比如对*p它会识别成引用,(而auto对它的操作直接识别范例成引用对象范例了)而比如i变量 ;decltype((i))会被识别成引用;因为它把(i)当成可赋值的变量了。
// 特殊处理
decltype(*p1) r1 = i; // r1的类型是int&,解引⽤表达式推导出的内容是引⽤
decltype(i) r2; // r2的类型是int
decltype((i)) r3 = i; // r3的类型是int&, (i)是⼀个表达式,变量是⼀种可以赋
值特殊表达式,所以会推出引⽤类型 ④还有些对类内的操作,不盼望去初始化它(不能用auto),decltype就派上用场了:如我们的auto不是可以对范例的声明举行auto,但是假如我们这一个类内的private声明了一个变量,给它auto范例就会去初始化它,而此时不能举行它的初始化,因此我们就可以用decltype了:

#include <vector>
using namespace std;
template <typename T>
class A
{
public:
void func(T& container)
{
_it = container.begin();
}
private:
// 这⾥不确定是iterator还是const_iterator,也不能使⽤auto
typename T::iterator _it;

// 使⽤decltype推导就可以很好的解决问题
//decltype(T().begin()) _it;
};
如许我们decltype先不给它初始化等调用的时间就能推导出它的范例了。
因此可以总结一下:对于像类内特别长的范例可以用auto代替(不是变量),假如它是类的变量就只能用decltype了。 
⑤decltype的尾置推导;假如⼀个函数模板的范例跟是不确定的,跟某 个参数对象有关,必要进⾏推导,因此这里假如直接decltype是不可的(前面推导找不到),也就是auto才能做返回值但是由于auto一些特性又要结合decltype一起利用,也就是declatype解决函数尾值返回题目。
如:
//template<class Iter>
//auto Func(Iter it1, Iter it2)->decltype(*it1)//这里*it是引用类型(它的特殊转换)
//{
// auto& x = *it1;
// ++it1;
// while (it1 != it2)
// {
// x += *it1;
// ++it1;
// }
// return x;
//}
// 显⽰实例化能解决问题,但是调⽤就很⿇烦
/*auto ret1 = Func<decltype(*v.begin())>(v.begin(), v.end());
auto ret2 = Func<decltype(*lt.begin())>(lt.begin(), lt.end());*/
这里只有如许才能调用到迭代器指针的引用来操作了。 


13.3typedef和using: 

首先我们的typedef和c语言一样,给前者起别名叫后者;而这里的using 就是让前者代替后者出现;如using a=b;那么a的出如今哪就代表b了,相当于a是b的别名等同于typedef b a;。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C++11的简介