您的位置:首页 > 编程语言 > C语言/C++

个人C++11,14,17学习笔记

2020-04-07 12:30 2006 查看

第四课

autto,头文件防卫,引用,常量

1.变量初始化新方式
(1)https://blog.csdn.net/u012321968/article/details/int i = { 5 }; //等号可以省略
(2)https://blog.csdn.net/u012321968/article/details/int arr[] {1,2,3,4,5};

2.auto
(1)auto自动类型推断发生在编译期,所以不会造成程序效率降低

3.C++11中的constexpr关键字
(1)C++11引入,语义是“常量表达式”,跟const一样表示常量,但是它是在编译的时候求值,能提升性能
(2)c++11 规定constexpr函数不得包含多余一个可执行语句,也就是说constexpr函数的函数体只能有一条语句,可以用条件运算符?: 或者递归来编写逻辑

4.C++14规定constexpr修饰的函数内可以有多条语句

第五课

范围for,new内存动态分配,nullptr

1.范围for
(1)例子

https://blog.csdn.net/u012321968/article/details/int v[] {1,2,3,4,5};
for(auto x:v)
{
cout << x << endl;
}

数组v中每个元素,依次放入x中,会有拷贝操作。可以改进为auto& x : v,避免拷贝操作

2.nullptr关键字
(1)在C++11中引入,代表空指针
(2)与NULL相比,NULL实际是0,即https://blog.csdn.net/u012321968/article/details/int a = NULL是成立的,而nullptr是关键字,使用nullptr能够避免在整数和指针之间发生混淆
(3)typeid(nullptr).name() 的值是std::nullptr_t

第七课

函数新特性,内联函数,const详解

1.后置返回类型函数
(1)C++11中,后置返回类型就是把返回类型写在参数列表之后
(2)格式:auto 函数名(参数列表)->返回值类型{ //… }
(3)例子:

auto func(https://blog.csdn.net/u012321968/article/details/int a)->void{ cout << 1 << endl; }

2.inline
(1)内联函数在编辑阶段处理,系统尝试将函数调用动作替换为函数本体
(2)inline只是显示建议,编译器不一定会替换
(3)内联函数的定义最好在头文件中,这样需要用到这个内联函数的函数体的cpp文件都能通过include头文件来知道该内联函数的函数体代码是什么

3.给函数的形参加上const修饰会更灵活,因为它即可以接收非const类型的该类型变量,又可以接受const类型的该类型变量

第九课

vector类型介绍,范围for进一步学习

1.在使用范围for来遍历vector等容器时,不要在循环体里对容器进行添加和删除元素的操作
(1)错误例子

vector<https://blog.csdn.net/u012321968/article/details/int> v{1,2,3,4,5};
for(auto i:v)
{
v.push_back(6);
cout << i << endl;
}

这样打印会乱套

第十课

迭代器

1.反向迭代器

2.容器为空时,begin和end的迭代器指向同一个位置

3.end指向容器的最后一个元素的后一个位置

4.常量迭代器:当容器有const修饰的时候,只能用常量迭代器遍历

5.

第十五课

类内初始化,默认构造函数,=default

1.=default
(1)编译器能自动生成空的构造函数体,不能有参数,如

class Test
{
public:
Test()=default;
};

(2)只有特殊函数才能使用=default,如构造函数,拷贝构造函数,析构函数
(3)可以在头文件中Test();然后在cpp文件中Test::Test()=default;

2.=delete
(1)让程序员显式的禁用某个特殊函数,如默认的合成的构造函数,如

Test()=delete;

第十八课

派生类,调用顺序,访问等级,函数遮蔽

1.使用using来解决函数遮蔽问题,在子类中使用父类同名函数
(1)C++11引入
(2)用法:

class Child:public Parent
{
public:
using Parent::samenameFunc;
};

//这样想调用父类同名成员函数的时候,就不需要加上Parent::了,直接samenameFunc,这样就相当于函数重载了

第二十三课

左值,右值,左值引用,右值引用,move

1.引用分类
(1)左值引用:https://blog.csdn.net/u012321968/article/details/int i1 = 1; https://blog.csdn.net/u012321968/article/details/int& i2 = i1;
(2)常量引用:const https://blog.csdn.net/u012321968/article/details/int& i3 = i2;
(3)右值引用:https://blog.csdn.net/u012321968/article/details/int&& i4 = 5;

2.右值引用
(1)C++11中引入
(2)右值引用可以理解成一个对象的名字
(3)右值引用变量绑定到一个右值后,虽然它是右值的引用,但是它是一个变量,它有自己的地址,绑定到一个右值后,可以重新赋值,如
https://blog.csdn.net/u012321968/article/details/int&& i = 5; i = 6;
(4)临时变量一般被当作右值,如”I love China”,非const左值引用不能绑定到临时变量里,但是加了const就可以,如

const string s {“I love China”};

(5)右值引用不能绑定到左值
(6)个人理解:右值引用就是给一个还没有名字的字面常量起一个名字,并分配内存来保存字面常量值
(7)引入右值引用的目的:把拷贝对象变成移动对象来提高程序运行效率

3.std::move函数
(1)C++11标准库中引入
(2)作用:把一个左值强制转换成一个右值,这样就可以用一个右值引用变量来绑定到该左值上。对右值引用变量重新赋值,那么原来的那个左值也会跟着改变

第二十四课

临时对象深入探讨

1.临时对象情况一
(1)构造时的隐式转换
(2)示例代码

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_i;

Test(https://blog.csdn.net/u012321968/article/details/int i) :m_i(i) {}
};

https://blog.csdn.net/u012321968/article/details/int main()
{
Test t(6);
t = 9;

return 0;
}

在t=9这条语句中,实际上做的事情是:用9构造了一个新的临时对象,然后调用Test类的赋值运算符函数来把临时对象赋值给t,然后该临时对象析构

改进:Test t = 9; 这样写不会使产生的临时对象析构,相当于把临时对象放到t自己的内存空间里了。

2.临时对象情况二
(1)构造时的隐式转换
(2)示例代码

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_i;

Test(https://blog.csdn.net/u012321968/article/details/int i) :m_i(i)
{
cout << "调用了构造函数" << endl;
cout << "this == " << this << endl;
}

Test& operator=(const Test& obj)
{
cout << "调用了operator=" << endl;
return *this;
}
};

void func(Test t)
{
cout << "&t == " << &t << endl;
}

https://blog.csdn.net/u012321968/article/details/int main()
{
func(6);

return 0;
}


图中this!=&t,所以说明中间生成了临时对象,再用临时对象构造t

(3)注意:在func函数中,如果用引用来接,那么必须加上const,否则编译不过。因为t是临时对象的引用,如果不加const,那么编译器会认为你有修改临时变量内部的值的倾向。对于C++语言,只会为const引用产生临时对象,而不会会非const引用产生临时对象

3.临时对象情况三
(1)函数中返回临时变量
(2)示例代码

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_i;

Test(https://blog.csdn.net/u012321968/article/details/int i) :m_i(i)
{
cout << "调用了构造函数" << endl;
cout << "this == " << this << endl;
}

Test(const Test& obj)
{
cout << "调用了拷贝构造函数" << endl;
}

Test& operator=(const Test& obj)
{
cout << "调用了operator=" << endl;
return *this;
}

~Test()
{
cout << "调用了析构函数" << endl;
}
};

Test func()
{
Test tmp(1);
return tmp;
}

https://blog.csdn.net/u012321968/article/details/int main()
{
func();

return 0;
}

结论:对于在函数中返回局部对象,系统会认为局部对象不能被返回,所以它会用局部对象tmp构造一个临时对象(调用了拷贝构造函数),然后返回该临时对象

(3)如果在main函数中把func();改成Test result = func();那么不会再构造一次Test对象,这样写的效果是result直接接管了临时对象,相当于Test&& result = func();
(4)改进方法:在func函数中不先构造tmp,而是直接return Test(1),那么这样就能够少构造一次(构造tmp),只需要构造一个临时对象即可

4.临时对象情况四
(1)在类外重载operator+等运算符
(2)参考上述几个例子,比如在类外重载operator+的话,那么在函数内必须要有个局部变量来保存+操作后的值,因此也会产生临时变量

第二十五课

对象移动,移动构造函数,移动赋值运算符

1.拷贝构造函数和移动构造函数可以不止有多个参数,但第一个参数必须是只有一个参数的时候的那个参数,后面的参数必须要有默认值
(1)如Test(const Test& obj,https://blog.csdn.net/u012321968/article/details/int i = 6);

2.移动构造函数格式
(1)如:Test(Test&& obj){ //… }
(2)注意,形参没有const修饰
(3)写移动构造函数的函数体时,要把源对象变成即使要被销毁也没有任何安全隐患的状态

3.noexcept
(1)C++11引入
(2)作用:在函数形参列表的()后面加上,可以说明该函数不抛出异常
(3)移动构造函数最好加上noexcept关键字,如果声明和定义分开,那么声明和定义都要加上
(4)加了noexcept,那么编译器就知道该函数不会抛出异常,不用做相关准备,可以提高效率

4.移动构造被调用的情况
(1)只有当构造一个新对象的时候才会调用移动构造函数,如
Test t2(std::move(t1));
(2)不会调用移动构造函数的例子,如:
Test&& t2(std::move(t1));
这样只会等同于给t1起了个新别名,不会调用移动构造函数

5.移动赋值运算符
(1)格式:Test& operator=(Test&& obj);

6.合成的移动操作
(1)如果类中有显式定义了拷贝函数、拷贝赋值运算符、析构函数之一的话,编译器就不会自动合成移动构造函数和移动赋值运算符函数
(2)当类中没有显示定义移动构造函数和移动赋值运算符函数时,而且编译器不能自动生成时,当需要使用移动构造函数和移动赋值运算符函数时,编译器会使用拷贝构造函数和拷贝赋值运算符函数代替

第二十六课

继承的构造函数,多重继承,虚继承

1.一个类只继承其直接基类的构造函数。默认、拷贝、移动构造函数不能被继承

2.using新用法
(1)例子

class Parent
{
public:
Parent(https://blog.csdn.net/u012321968/article/details/int i, https://blog.csdn.net/u012321968/article/details/int j) {}
};

class Child : public Parent
{
public:
using Parent::Parent;
};

https://blog.csdn.net/u012321968/article/details/int main()
{
Child c(1,2);

return 0;
}

(2)例子中using Parent::Parent;的效果:
1)编译器依然会给Child合成一个默认构造函数
2)编译器会把直接基类的每个构造函数,都给Child生成一个与之对应的构造函数,形参列表一致,函数体为空,在初始化列表调用直接基类的对应构造函数。如上述例子可隐式生成
Child(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j):Parent(i,j){}
(3)如果直接基类的构造函数有默认参数的话,那么编译器遇到using Parent::Parent时,会自动生成多个构造函数
1)第一个构造函数是带有所有参数的构造函数
2)其余的构造函数,每个分别省略掉一个默认的参数

第二十七课

类型转换函数,运算符,类成员指针

1.using定义类型别名
(1)例子
using tfpohttps://blog.csdn.net/u012321968/article/details/int = void(*)(https://blog.csdn.net/u012321968/article/details/int); 等价于 typedef void(*tfpohttps://blog.csdn.net/u012321968/article/details/int)(https://blog.csdn.net/u012321968/article/details/int);

2.类中的普通成员函数指针和虚成员函数指针
(1)格式声明:返回值类型 (类名::*函数指针变量名)(形参列表);
(2)赋值:函数指针变量名 = &类名::成员函数名;
(3)使用:必须绑定到一个具体的类对象上,如

Test t;
https://blog.csdn.net/u012321968/article/details/int (Test::*pAdd)(https://blog.csdn.net/u012321968/article/details/int,https://blog.csdn.net/u012321968/article/details/int);
pAdd = &Test::add;
(t.*pAdd)(1,2);

如果t是指针,则

(t->*pAdd)(1,2);

3.类中的静态成员函数指针
(1)格式声明:返回值类型 (*函数指针变量名)(形参列表);
(2)赋值:函数指针变量名 = &类名::成员函数名;
(3)使用:直接调用,如

Test t;
https://blog.csdn.net/u012321968/article/details/int (*pAdd)(https://blog.csdn.net/u012321968/article/details/int,https://blog.csdn.net/u012321968/article/details/int);
pAdd = &Test::add;
pAdd(1,2);

4.类中的普通成员变量的指针
(1)如

https://blog.csdn.net/u012321968/article/details/int Test::* mp = &Test::m_a;

(2)类中的普通成员变量的指针不是真实地址,即不是0X…,而是它指向的类成员变量在类中的偏移量,如4,8…
(3)使用:
Test t; t.*mp = 666;

5.类中的静态成员变量的指针
(1)使用例子

https://blog.csdn.net/u012321968/article/details/int* p = &Test::m_staticI;
*p = 666;

第三十二课

using定义模板别名,显示指定目标参数

1.C++11中,如果要定义类型别名的时候,using可代替typedef

2.using的格式
(1)using 别名 = 类型;
(2)如

using map_s_i = std::map<string,https://blog.csdn.net/u012321968/article/details/int>;
using FuncType = https://blog.csdn.net/u012321968/article/details/int(*)(https://blog.csdn.net/u012321968/article/details/int,https://blog.csdn.net/u012321968/article/details/int);

3.显式指定模板参数
(1)显式指定时,顺序从左到右
(2)例子

template<typename T1,typename T2,typename T3>
T1 sum(T2 i,T3 j)
{
T1 result = i + j;
return result;
}

https://blog.csdn.net/u012321968/article/details/int main()
{
auto result = sum<https://blog.csdn.net/u012321968/article/details/int>(2000000000,2000000000);
}

上述代码为显式指定T1的类型,顺序为从左到右,T2和T3自动推导

第三十四课

可变参模板

1.可变参模板
(1)允许模板中有0到多个目标参数,而且每个参数的类型可以不同
(2)例子
a)

template <typename... T>
void func(T... arg)
{
cout << sizeof...(arg) << endl;
}
https://blog.csdn.net/u012321968/article/details/int main()
{
func();  //0
func(1,2,3,4);  //4
func(“a”,2.66,3,9,’5’);  //5
return 0;
}

b)如果有引用的话,&要放在类型的右边而不是…的右边,如:

template <typename T,typename... U>
void func(const T& firstarg,const U&... othersargs){}

2.参数包的展开
(1)一般都是用递归函数的方式来展开参数,有个同名的无参终止函数
(2)一个参数和一包参数,最适合编写参数包的展开,形式如void func(const T& firstarg,const U&… othersargs)
(3)示例:
//同名的无参递归终止函数

void func() {}

void func(const T& firstarg,const U&... othersargs)
{
//用第一个参数做一些事情...
func(othersargs...);  //递归,别漏了...
}

3.可变参类模板
(1)原理是递归定义,继承
(2)例子:

template <typename... U>
class Test{};
template <typename T,typename... U>
class Test<T,U...> : private Test<U...>{};
https://blog.csdn.net/u012321968/article/details/int main()
{
Test<https://blog.csdn.net/u012321968/article/details/int,double,float> t;
return 0;
}

//一共会构造3次,Test<float>是顶层父类,它的直接子类是Test<double>,最后一个是Test<https://blog.csdn.net/u012321968/article/details/int>

第三十五课

可变参模板续,模板模板参数

1.通过递归组合方式展开参数包
(1)不以继承的方式展开,把父类做为子类的成员。
(2)例子:

template <typename... U>
class Test{};
template <typename T,typename... U>
class Test<T,U...>
{
public:
T m_t;
Test<U...> m_o;

Test(T t,U... o):m_t(t),m_o(o...){}
};
https://blog.csdn.net/u012321968/article/details/int main()
{
Test<https://blog.csdn.net/u012321968/article/details/int,double,float> t;
return 0;
}

2.通过tuple和递归调用展开参数包
(1)这种方式需要写类的特化版本
(2)实现思路:有个计数器,从0开水,每处理一个参数,计数器加一,一直到把所有参数都处理完,最后提供一个特化版本。
(3)tuple:元组,即一堆东西的组合。如:

tuple<https://blog.csdn.net/u012321968/article/details/int,double,float> mytuple(1,2.0,3.0f);
get<0>(mytuple);  //1

(4)例子:

template<https://blog.csdn.net/u012321968/article/details/int curCount,https://blog.csdn.net/u012321968/article/details/int maxCount,typename... T>
class Test
{
public:
static void mysfunc(const tuple<T...>& t)
{
cout << get<curCount>(t) << endl;
Test<curCount+1,maxCount,T...>::mysfunc(t);
}
};
//要提供特化版本
template<https://blog.csdn.net/u012321968/article/details/int maxCount,typename... T>
class Test<maxCount,maxCount,T...>
{
public:
static void mysfunc(const tuple<T...>& t){}
};

void func(const tuple<T...>& t)
{
Test<0,sizeof...(T),T...>::mysfunc(t);
}

https://blog.csdn.net/u012321968/article/details/int main()
{
tuple<float,https://blog.csdn.net/u012321968/article/details/int,double> mytuple(12.5f,6,52);
func(mytuple);
return 0;
}

3.模板模板参数
(1)目的是为了在模板类中使用Container,说得很晦涩,详细看例子。
(2)例子:

template <typename T,template<typename/class> typename/class Container>
class Test
{
public:
Container<T> m_con;
};

解释:template<typename/class> typename/class Container是固定的写法,这种是模板的模板,使用的时候typename和class选其一。
这样,m_con就可以作为一个类模板来使用,要想做到这一点,必须使用模板模板参数这种手段。
注意:使用模板模板参数,必须自己指定分配器。

使用方法:

using MyVector = vector<https://blog.csdn.net/u012321968/article/details/int,allocator<https://blog.csdn.net/u012321968/article/details/int>>;
https://blog.csdn.net/u012321968/article/details/int main()
{
Test<https://blog.csdn.net/u012321968/article/details/int,MyVector> t;
return 0;
}

第三十七课

new,delete探秘,智能指针概述,shared_ptr基础

1.当new一个数组时,如果是非基础类型而是自定义类型,那么new出来得到的内存会多出4个字节,那四个字节用来保存数组的大小,目的是使用delete[]的时候编译器知道要析构几个数组元素

2.对于基础类型,如https://blog.csdn.net/u012321968/article/details/int,new一个https://blog.csdn.net/u012321968/article/details/int数组后,要释放内存时使用delete和delete[]效果一样

3.析构函数对new和delete的影响
(1)如果new一个自定义类型的数组,如Test* t = new Test2;如果Test类中有自定义析构函数,那么直接使用delete t的话运行的时候会抛异常,delete[] t才行。如果类中没有自定义析构函数,那么delete t不会抛异常,但还是会内存泄露。
1)抛异常的原因:new一个带有自定义析构函数的自定义类型数组,得到的内存会多出4个字节,如果直接使用delete而不是delete[],那么可能在调用delete内部的operator delete函数的时候释放的是一个非法内存地址,内存非法跟那多出来的4个字节有关
(2)如果类中没有自定义析构函数,那么new一个自定义类的数组时,得到的内存不会多出4个字节

4.如果new出来的不是一个数组,但是却用delete[]去释放,那么会无限循环调用该对象的析构函数

5.make_shared
(1)C++11引入
(2)一个函数模板,安全,高效的分配和使用shared_ptr
(3)作用:在堆中分配并初始化一个对象,然后返回指向此对象的shared_ptr
(4)例子:

shared_ptr<string> p = make_shared<string>(5,’s’);

6.对shared_ptr对象重新赋值时,它会先把当前保存的内存先释放掉

第三十八课

shared_ptr常用操作,计数,自定义删除器等

1.shared_ptr常用操作
(1)use_count() :返回多少个智能指针指向某一段内存
(2)unique() :是否是只有一个智能指针对象指向某一段内存
(3)reset()
1)不带参数时:
a)若当前智能指针对象是唯一指向某一段内存的对象,那么释放该段内存,并将当前智能指针对象置空,即指向nullptr
b)若当前智能指针对象不是唯一指向某一段内存的对象,那么将引用计数减一,并将当前智能指针对象置空,即指向nullptr
2)带参数时:(参数一般是new出来的指针)
a)若当前智能指针对象是唯一指向某一段内存的对象,那么释放该段内存,并将当前智能指针对象指向新new出来的内存
b)若当前智能指针对象不是唯一指向某一段内存的对象,那么将引用计数减一,并将当前智能指针对象指向新new出来的内存
(4)get() :返回智能指针对象中保存的那段内存的首地址
(5)swap() :
1)方式一:std::swap(sp1,sp2);
2)方式二:sp1.swap(sp2);
(6)=nullptr :跟reset不带参数一样

2.指定删除器以及数组问题
(1)编译器默认使用delete来做为默认的资源释放方式,即使是数组默认释放时使用的也是delete而不是delete[]
(2)指定shared_ptr删除器的方式:构造智能指针对象的时候,第二个参数是一个函数指针,函数指针指向的函数的形参是智能指针对象保存的内存的指针类型,如

void myDelete(https://blog.csdn.net/u012321968/article/details/int* p)
{
//做一些自救需要的额外的事
delete p;
}

https://blog.csdn.net/u012321968/article/details/int main()
{
auto p = shared_ptr<https://blog.csdn.net/u012321968/article/details/int>(new https://blog.csdn.net/u012321968/article/details/int(6),myDelete);

return 0;
}

(3)删除器不单可以是一个函数,还可以是lambda表达式

auto p = shared_ptr<https://blog.csdn.net/u012321968/article/details/int>(new https://blog.csdn.net/u012321968/article/details/int(6), [](https://blog.csdn.net/u012321968/article/details/int* p) {delete p; });

(4)有些情况下,默认删除器处理不了(用shared_ptr管理动态数组时),需要我们提供自定义删除器,如

shared_ptr<Test> p(new Test[https://blog.csdn.net/u012321968/article/details/10],[](Test* t){delete[] t;});

(5)可以用default_delete来做删除器,它是标准库里的模板类

shared_ptr<Test> p(new Test[https://blog.csdn.net/u012321968/article/details/10],default_delete<A[]>());

(6)另外一种不用自定义删除器的方式,在<>中加上[]

shared_ptr<Test[]> p(new Test[https://blog.csdn.net/u012321968/article/details/10]);

此外,这种方法定义的智能指针对象可以通过[]来使用下标来操作数组元素

3.指定删除器的额外说明
(1)就算是两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个智能指针对象的类型相同,如

auto lambda1 = [](https://blog.csdn.net/u012321968/article/details/int* p) {delete[] p; };
auto lambda2 = [](https://blog.csdn.net/u012321968/article/details/int* p) {delete[] p; };
shared_ptr<https://blog.csdn.net/u012321968/article/details/int> p1(new https://blog.csdn.net/u012321968/article/details/int[https://blog.csdn.net/u012321968/article/details/100],lambda1);
shared_ptr<https://blog.csdn.net/u012321968/article/details/int> p2(new https://blog.csdn.net/u012321968/article/details/int[200],lambda2);
p2 = p1;

在定义p1和p2的时候,那段https://blog.csdn.net/u012321968/article/details/int[https://blog.csdn.net/u012321968/article/details/100]内存已经和lambda1绑定在一起了,即使后来p2也指向那段内存,但是当最后要释放那段内存的时候,使用的是一开始就绑定在一起的lambda1来释放它
(2)使用make_shared来生成共享智能指针对象时无法指定删除器,所以如果要管理数组那么就不要使用make_shared

第三十九课

weak_ptr概述,常用操作,尺寸

1.weak_ptr概述
(1)weak_ptr用来指向shared_ptr指向的内存,但是weak_ptr的指向和不指向不会改变原来shared_ptr的引用计数
(2)作用:可以监视shared_ptr的生命周期,是对shared_ptr的扩充

2.weak_ptr的创建
(1)例子

auto pi = make_shared<https://blog.csdn.net/u012321968/article/details/int>()6;
weak_ptr<https://blog.csdn.net/u012321968/article/details/int> piw(pi);

3.weak_ptr常用操作
(1)lock() :检查weak_ptr所指向的智能指针对象所指向的内存是否为空,如果不为空,则返回指向那段内存的shared_ptr对象;如果不存在,则返回一个指向nullptr的shared_ptr对象
(2)use_count() :获得当前资源的强引用计数,即有多少个shared_ptr对象指向内存资源
(3)expired() :如果所监视的内存资源已被释放,那么返回true,否则返回false
(4)reset() :将该弱引用指针置空,不影响指向该内存资源的强引用数量,但弱引用计数会减一

4.尺寸问题
(1)weak_ptr占用的内存大小是裸指针的两倍,如
https://blog.csdn.net/u012321968/article/details/int*占4个字节,则weak_ptr占8字节


(2)控制块是由shared_ptr创建的

第四十课

shared_ptr使用场景,陷阱,性能分析,使用建议

1.shared_ptr陷阱分析
(1)慎用裸指针

void func(shared_ptr<https://blog.csdn.net/u012321968/article/details/int> s){}
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int* p = new https://blog.csdn.net/u012321968/article/details/int(6);
func(shared_ptr<https://blog.csdn.net/u012321968/article/details/int>(p));
*p = 666;
return 0;
}

给func函数传参的时候,使用的是临时的shared_ptr变量,这样func函数返回后,p指向的内存已被释放,所以执行*p = 666时结果不可预料。
解决方法:先定义一个main函数的局部智能指针变量,再把该局部变量传给func函数。
(2)不能使用同一个裸指针去初始化多个智能指针,会被多次释放
(3)不要用this来构造shared_ptr返回,如果有需要,则使用enable_shared_from_this类模板
1)错误范例

class Test
{
public:
shared_ptr<Test> getself()
{
return shared_ptr<Test>(this);
}
};

https://blog.csdn.net/u012321968/article/details/int main()
{
shared_ptr<Test> s1(new Test());
shared_ptr<Test> s2 = s1->getself();

return 0;
}

这样会造成内存被释放两次

2)解决方法

class Test : public enable_shared_from_this<Test>
{
public:
shared_ptr<Test> getself()
{
return shared_from_this();
}
};

https://blog.csdn.net/u012321968/article/details/int main()
{
shared_ptr<Test> s1(new Test());
shared_ptr<Test> s2 = s1->getself();

return 0;
}

enable_shared_from_this类模板中有个weak_ptr,在执行shared_from_this()的时候实际上是调用的是weak_ptr的lock()

(4)避免循环引用

class TestB;
class TestA
{
public:
shared_ptr<TestB> m_bps;
};

class TestB
{
public:
shared_ptr<TestA> m_aps;
};

https://blog.csdn.net/u012321968/article/details/int main()
{
shared_ptr<TestA> pca(new TestA);
shared_ptr<TestB> pcb(new TestB);

pca->m_bps = pcb;
pcb->m_aps = pca;

return 0;
}

上述代码会发送死循环引用,pca和pcb要想释放自己的内存资源,必须要先等对方先析构,这好像多线程死锁,导致内存资源无法释放。

解决方法:将TestA或者TestB中的共享智能指针变成weak_ptr

2.shared_ptr的尺寸
(1)shared_ptr占用的内存大小为所指向的裸指针的两倍
(2)控制块创建时机:
a)make_shared函数返回
b)用裸指针来创建一个shared_ptr对象时

3.移动语义
(1)示例代码

shared_ptr<https://blog.csdn.net/u012321968/article/details/int> p1(new https://blog.csdn.net/u012321968/article/details/int(6));
shared_ptr<https://blog.csdn.net/u012321968/article/details/int> p2(std::move(p1));

p1不再指向该段内存,现在只有p2指向该段内存

4.要优先使用make_shared模板函数,因为它只需要分配一次内存
(1)例子

shared_ptr<string> ps1(new string(“I Love China!”));
//上述语句会分配两次内存,第一次是为string分配内存,第二次是为控制块分配内存
shared_ptr<string> ps2 = make_shared<string>(“I Love China!”);
//上述语句只需要分配一次内存,内存中同时包含了string的内容和控制块

5.分配器allocator
(1)是一个函数模板,我们可以把allocator看成一个简易的内存池,其主要适用于在使用容器时,对内存空间的动态分配,如果是我们平常要申请一块动态内存时,不推荐使用allocator,应该使用new-delete(malloc-free),主要原因是allocator不好用(使用不方便,容器例外),在内存释放的时候还需要提供对象的个数,因为我们在动态分配内存时候基本上都是对指针所指向的内存空间进行操作,而不会去记录空间中构造了多少个对象
(2)allocator的特点是将内存分配(allocate)与对象构造(construct)分离,这样使得对内存的管理更加灵活,性能较高
(3)示例代码

#include <iostream>
#include <memory>
using namespace std;

https://blog.csdn.net/u012321968/article/details/int main(https://blog.csdn.net/u012321968/article/details/int argc, char const *argv[])
{
allocator<https://blog.csdn.net/u012321968/article/details/int> a;
https://blog.csdn.net/u012321968/article/details/int *ptr=a.allocate(5);  //是5个https://blog.csdn.net/u012321968/article/details/int大小而不是5个字节
a.construct(ptr,3);
a.construct(ptr+1,-3);
a.construct(ptr+2,3);
a.construct(ptr+3,-3);
a.construct(ptr+4,3);
for(https://blog.csdn.net/u012321968/article/details/int i=0;i<5;i++)
{
cout<<*(ptr+i)<<" ";
a.destroy(ptr+i);
}
a.deallocate(ptr,5);
return 0;
}

第四十一课

unique_ptr概述,常用操作

1.unique_ptr概述
(1)同一时刻,只能有一个unique_ptr对象指向同一段内存

2.make_unique函数模板
(1)C++14中引入
(2)示例
unique_ptr p1 = make_unique(200);
(3)make_unique不能指定删除器

3.unique_ptr不支持的操作
(1)拷贝构造
(2)赋值操作

4.unique_ptr支持的常用操作
(1)移动操作
(2)release() :放弃对内存的控制权并返回该段内存地址,并将自己置空

unique_ptr<https://blog.csdn.net/u012321968/article/details/int> ps1(new https://blog.csdn.net/u012321968/article/details/int(6));
unique_ptr<https://blog.csdn.net/u012321968/article/details/int> ps2(ps1.release());

(3)reset() :
a)不带参数:释放智能指针指向的内存,并将智能指针置空
b)带参数:释放智能指针指向的内存,并将智能指针重新执行参数所指的新内存
(4)=nullptr :同不带参数的reset()
(5)指向一个数组
a)示例

unique_ptr<https://blog.csdn.net/u012321968/article/details/int[]> parray(new https://blog.csdn.net/u012321968/article/details/int[https://blog.csdn.net/u012321968/article/details/10]);  //在<>中加了[],不会发生内存泄露

(6)get() :返回裸指针
(7)*解引用
(8)swap() :交换两个unique_ptr所指向的内存

std::swap(u1,u2);
u1.swap(u2);

(9)智能指针名字做为判断条件

unique_ptr<string> ps1(new string(“I Love China!”));
if(ps1)
{
cout << “ps1不为空” << endl;
}

(https://blog.csdn.net/u012321968/article/details/10)转换成shared_ptr : 如果unique_ptr为右值,那么可以转换成shared_ptr
a)例子

auto func()
{
return unique_ptr<string>(new string(“I Love China!”));
}

https://blog.csdn.net/u012321968/article/details/int main()
{
shared_ptr<string> sp1 = func();
return 0;
}

临时变量都是右值,在func函数中返回临时的unique_ptr,可以转换成shared_ptr,同时会创建控制块

第四十二课

返回unique_ptr,删除器,尺寸,智能指针总结

1.decltype
(补充)来自https://www.cnblogs.com/cauchy007/p/4966485.html
(1)C++11引入
(2)意义:有时我们希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量(如果要初始化就用auto了)。为了满足这一需求,C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
(3)基本用法

https://blog.csdn.net/u012321968/article/details/int getSize();

https://blog.csdn.net/u012321968/article/details/int main(void)
{
https://blog.csdn.net/u012321968/article/details/int tempA = 2;

/*1.dclTempA为https://blog.csdn.net/u012321968/article/details/int*/
decltype(tempA) dclTempA;
/*2.dclTempB为https://blog.csdn.net/u012321968/article/details/int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize,*/
decltype(getSize()) dclTempB;

return 0;
}

(4)与const结合

double tempA = 3.0;
const double ctempA = 5.0;
const double ctempB = 6.0;
const double *const cptrTempA = &ctempA;

/*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/
decltype(ctempA) dclTempA = 4.1;
/*2.dclTempA为const double,不能对其赋值,编译不过*/
dclTempA = 5;
/*3.dclTempB推断为const double * const*/
decltype(cptrTempA) dclTempB = &ctempA;
/*4.输出为4(32位计算机)和5*/
cout<<sizeof(dclTempB)<<"    "<<*dclTempB<<endl;
/*5.保留顶层const,不能修改指针指向的对象,编译不过*/
dclTempB = &ctempB;
/*6.保留底层const,不能修改指针指向的对象的值,编译不过*/
*dclTempB = 7.0;

(5)与引用结合

https://blog.csdn.net/u012321968/article/details/int tempA = 0, &refTempA = tempA;

/*1.dclTempA为引用,绑定到tempA*/
decltype(refTempA) dclTempA = tempA;
/*2.dclTempB为引用,必须绑定到变量,编译不过*/
decltype(refTempA) dclTempB = 0;
/*3.dclTempC为引用,必须初始化,编译不过*/
decltype(refTempA) dclTempC;
/*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/
decltype((tempA)) dclTempD = tempA;

const https://blog.csdn.net/u012321968/article/details/int ctempA = 1, &crefTempA = ctempA;

/*5.dclTempE为常量引用,可以绑定到普通变量tempA*/
decltype(crefTempA) dclTempE = tempA;
/*6.dclTempF为常量引用,可以绑定到常量ctempA*/
decltype(crefTempA) dclTempF = ctempA;
/*7.dclTempG为常量引用,绑定到一个临时变量*/
decltype(crefTempA) dclTempG = 0;
/*8.dclTempH为常量引用,必须初始化,编译不过*/
decltype(crefTempA) dclTempH;
/*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/
decltype((ctempA))  dclTempI = ctempA;

(6)与指针结合

https://blog.csdn.net/u012321968/article/details/int tempA = 2;
https://blog.csdn.net/u012321968/article/details/int *ptrTempA = &tempA;
/*1.常规使用dclTempA为一个https://blog.csdn.net/u012321968/article/details/int *的指针*/
decltype(ptrTempA) dclTempA;
/*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,引用必须初始化,故编译不过*/
decltype(*ptrTempA) dclTempB;

(7)decltype总结
decltype和auto都可以用来推断类型,但是二者有几处明显的差异:
1.auto忽略顶层const,decltype保留顶层const;
2.对引用操作,auto推断出原有类型,decltype推断出引用;
3.对解引用操作,auto推断出原有类型,decltype推断出引用;
4.auto推断时会实际执行,decltype不会执行,只做分析。
总之在使用中过程中和const、引用和指针结合时需要特别小心。

2.指定unique_ptr的删除器
(1)默认的删除器为delete
(2)指定删除器的格式:unique_ptr<类型,删除器类型> 变量名(new 类型,具体的可调用对象);
a)例子

void myDeleter(string* s)
{
delete s;
s = nullptr;
}

https://blog.csdn.net/u012321968/article/details/int main()
{
using fp = void(*)(string*);
unique_ptr<string,fp> ps1(new string(“I Love China!”),myDeleter);
return 0;
}

方法二:

typedef decltype(myDeleter)* fp2;
unique_ptr<string,fp2> ps1(new string(“I Love China!”),myDeleter);

记得加个*,因为decltype(myDeleter)的结果为void(string*)

方法三:

unique_ptr<string,decltype(myDeleter)*> ps1(new string(“I Love China!”),myDeleter);

方法四:

auto myDella = [](string* s){
delete s;
s = nullptr;
};
unique_ptr<string,decltype(myDella)> ps1(new string(“I Love China!”),myDella);

3.指定删除器额外说明
(1)不同于shared_ptr,两个unique_ptr的删除器类型不相同时,两个unique_ptr的类型就不同,就不能放入同一个容器(如vector)里

4.尺寸问题
(1)unique_ptr的尺寸跟裸指针一样
(2)如果自定义了删除器,则unique_ptr的尺寸可能会变化。
a)如果删除器为lambda表达式,那么尺寸不会发生变化。
b)如果删除器为函数指针,那么尺寸会变大

5.auto_ptr
(1)来自C++98,有unique_ptr的一部分功能
(2)缺陷:
a)不能在容器中保存
b)不能当作函数返回值
c)可以相互赋值,相互赋值后,容易使用原有已置空的auto_ptr,程序崩溃

第五十六课

new,delete进一步认识

1.new 类名和new 类名()的区别
(1)当类中没有构造函数时:
a)new 类名的结果:类成员变量是随机值
b)new 类名()的结果:类成员变量值清零
(2)当类中有构造函数时:两种写法的效果一样,成员变量是随机值

2.new的内部调用流程
(1)调用operator new
(2)在operator new中调用malloc
(3)调用构造函数

3.delete的内部调用流程
(1)调用析构函数
(2)调用operator delete
(3)在operator delete中调用free

第五十七课

new细节探秘

第六十二课

容器分类,array,vector容器精讲

1.STL的容器分类
(1)顺序容器
(2)关联容器(如键值对):如set,multiset,map,multimap
(3)无序容器:C++11推出

2.array容器
(1)#include
(2)是个顺序容器,其实是个数组,内存空间连续,大小固定

3.
4.(1)从上图可以看出,array大小固定,不支持改变大小
(2)vector从末尾插入、删除时效率高,但是如果从中间插入、删除时效率非常低,要挪动元素,就会涉及拷贝构造和析构

第六十三课

容器的说明和简单应用

1.

2.deque
(1)双端队列

(2)deque的内部空间并不是全部连续的,而是分段连续

3.list
(1)双向链表

4.forward_list
(1)C++11引入
(2)单向链表
5.map
(1)用法示例

map<https://blog.csdn.net/u012321968/article/details/int,string> m;
m.insert(std::make_pair(1,”A”));
m.insert(pair<https://blog.csdn.net/u012321968/article/details/int,string>(2,”B”));
m.insert(pair<https://blog.csdn.net/u012321968/article/details/int,string>(2,”C”));
auto iter = m.find(2);
if(iter != m.end())
{
//找到了
prhttps://blog.csdn.net/u012321968/article/details/intf(“key == %d,value = %s\n”,iter->first,iter->second); //2,B
//C不会覆盖B
}

(2)查找速度快

6.set
(1)不存在键值对的说法,每个元素都是一个value,元素不允许重复

7.unordered_set
(1)基于哈希表实现的set

(2)常用操作
a)bucket_count() :当前篮子的数量(容量)
b)max_bucket_count() :最大容量
c)size() :当前容器里有多少个元素
d)bucket_size(https://blog.csdn.net/u012321968/article/details/int i) :第i个篮子里有多少个元素
e)find(元素)

第六十四课

分配器概述,使用,工作原理

1.内存分配器allocate
(1)C++标准库提供的默认allocate没有采用内存池工作机制

第六十五课

迭代器的概念和分类

1.

2.

第六十六课

算法概述,内部处理,使用范例

1.STL里算法的使用方法一般为前两个参数为迭代器,左闭右开,第三个参数为可调用对象,如

for_each(v.begin(),v.end(),func);

2.sort
(1)默认从小到大排序
(2)可自定义回调函数,函数返回值为bool,参数为两个,把函数指针传入set的第三个参数,如

bool myFunc(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j)
{
return (i > j);
}
sort(v.begin(),v.begin() + 5,myFunc);

第六十七课

函数对象回顾,系统函数对象及范例

1.

2.标准库中提供的函数对象
(1)#include

第六十八课

适配器概念,分类,范例及总结

1.适配器
(1)把一个既有的东西,通过添加或减少些功能,就得到一个适配器

2.容器适配器
(1)deque通过删除一些功能,就得到stack和queue

3.算法适配器
(1)绑定器bind,C++11引入
(2)bind
a)示例

auto bf = bind(less<https://blog.csdn.net/u012321968/article/details/int>(),40,placeholders::_1);
bf(19);  //相当于bf(40,19);

https://blog.csdn.net/u012321968/article/details/int c = count_if(v.begin(),v.end(),bind(less<https://blog.csdn.net/u012321968/article/details/int>(),40,placeholders::_1));

4.迭代器适配器
(1)比如reverse_iterator

第六十九课

函数调用运算符,function类模板

1.function类模板
(1)#include
(2)用法示例

https://blog.csdn.net/u012321968/article/details/int func(https://blog.csdn.net/u012321968/article/details/int i){ return i; }
function<https://blog.csdn.net/u012321968/article/details/int(https://blog.csdn.net/u012321968/article/details/int)> f1 = func;
f1(5);
map<string,function<https://blog.csdn.net/u012321968/article/details/int(https://blog.csdn.net/u012321968/article/details/int)>> mymap = {
{“1”,func1},
{“2”,func2}
};
mymap[“2”](6);

注意:比如function<https://blog.csdn.net/u012321968/article/details/int(https://blog.csdn.net/u012321968/article/details/int)> f1 = func;那么func不能有重载版本,否则编译器不知道要取哪个重载版本,出现二义性

第七十课

万能引用/未定义引用universal reference

1.万能引用/未定义引用
(1)例子

https://blog.csdn.net/u012321968/article/details/int func(https://blog.csdn.net/u012321968/article/details/int&& i){ return i; }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
func(i);  //编译报错,右值不能绑定到左值
return 0;
}

修改后:

template <typename T>
https://blog.csdn.net/u012321968/article/details/int func(T&& i){ return i; }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
func(i);  //编译成功
return 0;
}

(2)万能引用语境
a)必须是函数模板
b)必须是发生了模板类型推断并且形参为T&&字样,注意:T和&&直接不能再有别的符号,空格是可以的
(3)万能引用作用:既能接收左值,又能接收右值,且能保存实参的const属性

2.不是万能引用的例子
(1)

template <typename T>
void func(vector<T>&& v){ //... }   // V是右值引用,而不是万能引用,因为没有发生模板类型推断,T和&&之间有>
(2)template <typename T>
class Test
{
public:
void func(T&& i){ //... }
};
Test<https://blog.csdn.net/u012321968/article/details/int> t;
https://blog.csdn.net/u012321968/article/details/int i = 6;
t.func(i);  //编译错误,func成员函数的形参是右值引用,因为没有发生类型推断,T确定了之后,t对象的类型就确定了,所以func成员函数的形参也确定了。

修改:

class Test
{
public:
template <typename T2>
void func(T2&& i){ //... }
};
Test<https://blog.csdn.net/u012321968/article/details/int> t;
https://blog.csdn.net/u012321968/article/details/int i = 6;
t.func(i);  //是万能引用

3.万能引用资格的剥夺
(1)加了const属性,那么就不再是万能引用,如

template <typename T>
https://blog.csdn.net/u012321968/article/details/int func(const T&& i){ return i; }

第七十一课

理解模板类型推断,查看类型推断结果

1.当形参是指针或引用时
(1)如果实参是引用,那么进行模板类型推断时,引用会被忽略
a)有关引用的例子(指针同理)

template <typename T>
void func(T& i){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
const https://blog.csdn.net/u012321968/article/details/int& j = i;
func(j);

return 0;
}

执行func(j)时,推断出来T是const https://blog.csdn.net/u012321968/article/details/int,形参i是const https://blog.csdn.net/u012321968/article/details/int&
(2)如果模板形参有const修饰,那么进行模板类型推断时,实参的const会被忽略

2.当形参是万能引用时
(1)例子

template <typename T>
void func(T&& tmp){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
const https://blog.csdn.net/u012321968/article/details/int j = i;
const https://blog.csdn.net/u012321968/article/details/int& k = i;
func(i);  //T = https://blog.csdn.net/u012321968/article/details/int& , tmp = https://blog.csdn.net/u012321968/article/details/int&
func(j);  //T = const https://blog.csdn.net/u012321968/article/details/int& , tmp = const https://blog.csdn.net/u012321968/article/details/int&
func(k);  //T = const https://blog.csdn.net/u012321968/article/details/int& , tmp = const https://blog.csdn.net/u012321968/article/details/int&
func(https://blog.csdn.net/u012321968/article/details/100);  //T = https://blog.csdn.net/u012321968/article/details/int , tmp = https://blog.csdn.net/u012321968/article/details/int&&

return 0;
}

3.传值方式时的类型推断
(1)例子

template <typename T>
void func(T tmp){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
const https://blog.csdn.net/u012321968/article/details/int j = i;
const https://blog.csdn.net/u012321968/article/details/int& k = i;
func(i);  //T = https://blog.csdn.net/u012321968/article/details/int , tmp = https://blog.csdn.net/u012321968/article/details/int
func(j);  //T = https://blog.csdn.net/u012321968/article/details/int , tmp = https://blog.csdn.net/u012321968/article/details/int
func(k);  //T = https://blog.csdn.net/u012321968/article/details/int , tmp = https://blog.csdn.net/u012321968/article/details/int

return 0;
}

(2)如果传递的实参类型是const char* const,那么T = const char* , tmp = const char *

4.数组做实参时的类型推断
(1)形参不是引用时
a)例子

template <typename T>
void func(T tmp){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
char s[] = “China”;
func(s);  //T = char* , tmp = char*
return 0;
}

(2)当形参是引用时
a)例子

template <typename T>
void func(T& tmp)
{
//...
}
https://blog.csdn.net/u012321968/article/details/int main()
{
char s[] = “China”;
func(s);  //T = char (&) [6] , tmp = char (&) [6]

return 0;
}

5.函数名做实参时的类型推断
(1)形参不是引用时
a)例子

template <typename T>
void func(T tmp){ //... }

void testFunc(void){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
func(testFunc);  //T = void(*)(void) , tmp = void(*)(void)
return 0;
}

(2)当形参是引用时
a)例子

template <typename T>
void func(T& tmp){ //... }

void testFunc(void){ //... }
https://blog.csdn.net/u012321968/article/details/int main()
{
func(testFunc);  //T = void(void) , tmp = void(&)(void)
return 0;
}

第七十二课

引用折叠,转发,完美转发,forward

1.引用折叠
(1)问题的引出

template <typename T>
void func(T &&tmp)
{
//...
}

https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
https://blog.csdn.net/u012321968/article/details/int& j = i;
func(j);
func(6);
return 0;
}

分析:j的类型是https://blog.csdn.net/u012321968/article/details/int&,按理说func的形参tmp的类型应该是https://blog.csdn.net/u012321968/article/details/int& &&,但是编译器在执行类型推断的时候,如果传进去的实参是左值,那么T的类型是T&,tmp的类型是T&&;如果传进去的实参是右值,那么T的类型是T,tmp的类型是T&&。这其中发生了引用折叠。
(2)引用折叠分析
a)把func的形参T &&tmp分成两部分,第一部分是T,T只跟我们传进去的实参类型有关;第二部分是&&tmp。
b)引用折叠规则:如果第一部分和第二部分其中之一为左值引用,那么结果为左值引用,否则为右值引用。
(3)引用的引用是非法的,不存在引用的引用

2.转发
(1)示例

void func(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j)
{
//...
}

template <typename F,typename T1,typename T2>
void TemFunc(F f,T1 t1,T2 t2)
{
f(t1,t2);
}
https://blog.csdn.net/u012321968/article/details/int main()
{
TemFunc(func,1,2);
return 0;
}

上述就是一个转发的例子。TemFunc模板函数通过转发T1和T2的类型给函数f,从而调用函数f。
(2)非完美转发的例子

void func(https://blog.csdn.net/u012321968/article/details/int&& i,https://blog.csdn.net/u012321968/article/details/int& j)
{
//...
}

template <typename F,typename T1,typename T2>
void TemFunc(F f,T1 t1,T2 t2)
{
f(t1,t2);
}
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
TemFunc(func,1,i);
return 0;
}

这个例子中,func函数的形参中第一个形参为右值引用,第二个形参为左值引用。在main函数中调用TemFunc(func,1,2);这条语句会编译报错:不能从https://blog.csdn.net/u012321968/article/details/int(TemFunc中t1为左值)转换到https://blog.csdn.net/u012321968/article/details/int&&(func中i要绑定右值)。
万能引用能解决左值引用的问题,即把T2改成T2&&,但是万能引用解决不了右值引用的问题。即使把T1变成T1&&,T1绑定的是右值,这一点没错,但是T1本身是个变量,是个左值,传给func的i的时候,肯定转换不了。

3.完美转发std::forward
(1)使用格式:std::forward<类型>(变量名)
(2)作用:std::forward(u)有两个参数:T 与 u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。
(3)使用完美转发的条件:
a)调用模板函数
b)模板函数参数是万能引用
(4)例子

void func(https://blog.csdn.net/u012321968/article/details/int&& i,https://blog.csdn.net/u012321968/article/details/int& j)
{
//...
}

template <typename F,typename T1,typename T2>
void TemFunc(F f,T1&& t1,T2&& t2)
{
f(std::forward<T1>(t1),std::forward<T2>(t2));
}
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
TemFunc(func,1,i);
return 0;
}

实参中1是右值,前面学习七十一课的时候已经学过,进行类型推断的时候推断出T1的类型信息是https://blog.csdn.net/u012321968/article/details/int,非左值引用(https://blog.csdn.net/u012321968/article/details/int&),所以把t1转成右值;进行类型推断的时候推断出T2是https://blog.csdn.net/u012321968/article/details/int&类型,是左值引用,所以把t2转成左值。

第七十三课

理解auto类型推断,auto应用场合

1.auto概述
(1)auto在C++98已经有了,在C++11中进行了升级,可用于自动类型推断
(2)auto自动类型推断发生在编译器,不会影响程序性能
(3)auto自动类型推断和函数模板推断非常相似

2.auto传值方式
(1)以传值方式用auto时,会抛弃const属性和引用属性
(2)例子
const https://blog.csdn.net/u012321968/article/details/int& i = 6;
auto i2 = i; //i2的类型为https://blog.csdn.net/u012321968/article/details/int,没有const和引用属性

3.auto指针或引用
(1)不会抛弃原本的const、指针属性,但会抛弃引用属性
(2)例子

const https://blog.csdn.net/u012321968/article/details/int& i1 = 6;
auto &i2 = i1;  //auto = const https://blog.csdn.net/u012321968/article/details/int
const auto *p1 = &i1;
auto* p2 = p1;  //auto = const https://blog.csdn.net/u012321968/article/details/int

4.auto与万能引用
(1)像函数模板一样,也会发生引用折叠,规则一样。而且能保留const属性。
(2)auto版的万能引用为auto&&。
(3)例子

https://blog.csdn.net/u012321968/article/details/int x = 6;
auto&& yy = x;  //x是左值,auto = https://blog.csdn.net/u012321968/article/details/int& ,yy = https://blog.csdn.net/u012321968/article/details/int&

5.auto对于数组和函数的推断
(1)跟函数模板类型推断一样

6.auto类型std::initializer_list的特殊推断
(1)例子

auto x1 = https://blog.csdn.net/u012321968/article/details/10;  //x1 = https://blog.csdn.net/u012321968/article/details/int
auto x2(20);  //x2 = https://blog.csdn.net/u012321968/article/details/int
auto x3 = { 30 };  //x3 = std::initializer_list<https://blog.csdn.net/u012321968/article/details/int>
auto x4{ 40 };  //x4 = https://blog.csdn.net/u012321968/article/details/int

(2)std::initializer_list为C++11引入,是类模板,表示某种特定的值的数组。这种类型推导只适合auto,不适合模板类型。

7.在C++14中,auto可以做为函数返回类型

8.auto不适合使用的场景举例
(1)不能用于函数参数类型,如void func(auto x,https://blog.csdn.net/u012321968/article/details/int y);//编译不过
(2)类的普通成员变量类型,如

class Test
{
public:
auto m_i = 6;  //编译不过
static const auto m_si = 666;  //static const可以,但必须在定义的时候初始化
};

第七十四课

decltype详解,主要用途

1.decltype
(1)作用:返回操作数的数据类型
(2)decltype的自动类型推断发生在编译期
(3)decltype不会真正计算表达式的值

2.decltype特点
(1)如果decltype(变量),那么变量的const和引用属性会保留
(2)如果decltype(表达式),那么decltype会返回表达式的结果的类型;
a)注意:如果表达式的结果能够作为赋值语句左边的左值,那么decltype返回的是个引用,例如:

https://blog.csdn.net/u012321968/article/details/int i = 6;
https://blog.csdn.net/u012321968/article/details/int* pi = &i;
decltype(*pi) di = i;  //di = https://blog.csdn.net/u012321968/article/details/int&

(3)如果decltype()里面又有个(),比如decltype((i)),那么编译器会把里层的(i)看成个表达式,然后就是(2)中的情况,所以decltype((i))的结果是https://blog.csdn.net/u012321968/article/details/int&。
(4)如果decltype(函数调用),那么decltype的结果是函数的返回值。例如:

https://blog.csdn.net/u012321968/article/details/int func(https://blog.csdn.net/u012321968/article/details/int i){}
decltype(func(6)) i = 8;  //i = https://blog.csdn.net/u012321968/article/details/int,且func函数不会真的被调用

(5)如果decltype(函数名),那么decltype的结果是函数类型,而不是函数指针。例如:

https://blog.csdn.net/u012321968/article/details/int func(https://blog.csdn.net/u012321968/article/details/int i){}
decltype(func) pf;  //pf = https://blog.csdn.net/u012321968/article/details/int(https://blog.csdn.net/u012321968/article/details/int)

3.decltype主要用途
(1)应付可变类型,主要用于模板编程中
a)例子:

template <typename T>
class Test
{
public:
typename T::iterator m_begin;
void getBegin(T& tmp)
{
m_begin = tmp.begin();
}
};
https://blog.csdn.net/u012321968/article/details/int main()
{
using v_https://blog.csdn.net/u012321968/article/details/int = vector<https://blog.csdn.net/u012321968/article/details/int>;
v_https://blog.csdn.net/u012321968/article/details/int v = {https://blog.csdn.net/u012321968/article/details/10,20,30};
Test<v_https://blog.csdn.net/u012321968/article/details/int> t;
t.getBegin(v);
return 0;
}

上述代码使用时没问题,但是如果v_https://blog.csdn.net/u012321968/article/details/int的类型为const vector,使用Test类模板时就有错误,因为Test类的m_begin要改为typename T::const_iterator才行。在C++98时,解决这个问题可以通过类模板特化的方式。这个方法有个明显的缺陷,就是要重新专门写一遍Test类的代码。
到了C++11,可以通过decltype解决。代码如下:

template <typename T>
class Test
{
public:
decltype(T().begin()) m_begin;
void getBegin(T& tmp)
{
m_begin = tmp.begin();
}
};
https://blog.csdn.net/u012321968/article/details/int main()
{
using v_https://blog.csdn.net/u012321968/article/details/int = const vector<https://blog.csdn.net/u012321968/article/details/int>;
v_https://blog.csdn.net/u012321968/article/details/int v = {https://blog.csdn.net/u012321968/article/details/10,20,30};
Test<v_https://blog.csdn.net/u012321968/article/details/int> t;
t.getBegin(v);
return 0;
}

像上述代码那样写的话,无论是const还是非const,都可以使用。

(2)auto与decltype结合,函数返回类型后置写法。例子:

https://blog.csdn.net/u012321968/article/details/int func(https://blog.csdn.net/u012321968/article/details/int i){}
double func(double i){}

template <typename T>
auto temFunc(T& tmp)->decltype(func(tmp)){ return func(tmp); }

https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
double di = 6.0;
temFunc(i);   //调用func(https://blog.csdn.net/u012321968/article/details/int)
temFunc(di);  //调用func(double)
return 0;
}

(3)C++14中,decltype(auto)可以结合。例子:

auto mydouble(T& tmp){ tmp*= 2; return tmp; }
https://blog.csdn.net/u012321968/article/details/int main()
{
https://blog.csdn.net/u012321968/article/details/int a = https://blog.csdn.net/u012321968/article/details/100;
mydouble(a) = 20;  //编译报错,因为auto类型推导把引用去掉了,mydouble(a)是右值。
return 0;
}

解决方法:

decltype(auto) mydouble(T& tmp){ tmp*= 2; return tmp; }

现在,我们可以理解为auto是我们要推导的类型,推导过程使用decltype方式,所以引用属性得以保留。

注意:这种结合有隐患,例子:

decltype(auto) func()
{
https://blog.csdn.net/u012321968/article/details/int i = 6;
return (i);
}

因为(i)是个表达式,且i可以做为左值,所以decltype((i))的结果是https://blog.csdn.net/u012321968/article/details/int&,即函数的局部变量的引用,这样后续很有可能会使用i的引用,这有很大的安全隐患。

第七十五课

可调用对象,std::function,std::bind

1.可调用对象
(1)函数指针
(2)仿函数(具有operator()的类对象)
(3)可被转换为函数指针的类对象(有类型转换函数,且是转换成函数指针)。(第三章第十六节)。例子:

class Test
{
public:
using tfFunc = void(*)(https://blog.csdn.net/u012321968/article/details/int);
static void my_func(https://blog.csdn.net/u012321968/article/details/int i){ //... }
operator rfFunc()
{
return my_func;
}
};
https://blog.csdn.net/u012321968/article/details/int main()
{
Test t;
t(6);  //相当于t.operator Test::tfFunc()(6);
return 0;
}

(4)类成员函数指针

2.std::function
(1)#include
(2)作用:可调用对象包装器。以统一的方式来调用可调用对象。不能装类成员函数指针,因为调用类成员函数指针依赖于类对象。
(3)用法示例
a)绑定普通函数:std::function<返回值类型(参数列表)> 变量名 = 函数名;

void func(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j){}
std::function<void(https://blog.csdn.net/u012321968/article/details/int,https://blog.csdn.net/u012321968/article/details/int)> pf = func;
pf(1,2);

b)绑定类的静态成员函数

class Test
{
public:
static void s_func(string s){}
};
std::function<void(string)> pf = Test::s_func;
pf(“Hello”);

c)绑定仿函数

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_i;
Test(https://blog.csdn.net/u012321968/article/details/int i = 0):m_i(i){}
void operator()(){}
};
Test t;
std::function<void()> pf = t;
pf();

d)绑定有类型转换函数的对象

class Test
{
public:
using tfFunc = void(*)(https://blog.csdn.net/u012321968/article/details/int);
static void my_func(https://blog.csdn.net/u012321968/article/details/int i){ //... }
operator rfFunc()
{
return my_func;
}
};
https://blog.csdn.net/u012321968/article/details/int main()
{
Test t;
std::function<void(https://blog.csdn.net/u012321968/article/details/int)> pf = t;
pf(6);
return 0;
}

3.std::bind
(1)C++11引入,是类模板
(2)作用:能将对象以及相关参数绑定到一起,绑定完后可以直接调用,也可以用std::functionn保存。实际上将可调用对象和参数绑定在一起,然后构成一个有仿函数的对象,所以bind函数返回值类型是一个有仿函数的对象。
(3)格式:std::bind(可调用对象,参数绑定值1,参数绑定值2 …参数绑定值n);
(4)例子
a)绑定所有参数

void func(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j,https://blog.csdn.net/u012321968/article/details/int k){}
auto bf1 = std::bind(func,1,2,3);
bf1();

b)只绑定部分参数

void func(https://blog.csdn.net/u012321968/article/details/int i,https://blog.csdn.net/u012321968/article/details/int j,https://blog.csdn.net/u012321968/article/details/int k){}
auto bf2 = std::bind(placeholders::_1,placeholders::_2,3);
bf2(1,2);  //i = 1,j = 2,k = 3
auto bf3 = std::bind(placeholders::_1,placeholders::_1,3);
bf2(1,2);  //i = 1,j = 1,k = 3

4.bind的一个坑
(1)例子:

class Test
{
public:
Test()
{
cout << "调用了构造函数" << endl;
}

Test(const Test& obj)
{
cout << "调用了拷贝构造函数" << endl;
}

Test(Test&& obj)
{
cout << "调用了移动构造函数" << endl;
}

Test& operator=(const Test& obj)
{
cout << "调用了Test& operator=(const Test& obj)" << endl;
return *this;
}

Test& operator=(Test&& obj)
{
cout << "调用了Test& operator=(Test&& obj)" << endl;
return *this;
}
};

void func(Test& a, Test& b)
{

}

https://blog.csdn.net/u012321968/article/details/int main()
{
Test a;
Test b;
auto bf1 = std::bind(func,a,placeholders::_1);
bf1(b);

return 0;
}

结果说明:bind对于预先绑定的函数参数会先拷贝一份副本再传给函数形参,即例子中func函数的形参a并不是实参a的引用,而是实参a的副本的引用。

5.bind绑定类的成员函数
(1)格式:std::bind(&类名::成员函数名,类的实例对象名,参数绑定值1,参数绑定值2 …参数绑定值n);
(2)例子:

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_x = 0;
void myFunc(https://blog.csdn.net/u012321968/article/details/int x,https://blog.csdn.net/u012321968/article/details/int y)
{
m_x = x;
}
};

https://blog.csdn.net/u012321968/article/details/int main()
{
Test t;
auto bf1 = std::bind(&Test::myFunc,t,placeholders::_1,6);
bf1(7);
return 0;
}

上述只是示范了如果用bind绑定类的成员函数。但是跟第4点说的bind的坑一样,在执行auto bf1 = std::bind(&Test::myFunc,t,placeholders::_1,6);的时候,第二个参数t实际上是t的副本,是执行了Test类的拷贝构造函数,调用了bf1(7)后,main函数中t对象的m_x的值还是0而不是7。
解决方法:auto bf1 = std::bind(&Test::myFunc,&t,placeholders::_1,6);在t之前加&

6.function与bind结合
(1)bind还能绑定类的成员变量。
(2)把类成员变量地址当函数一样绑定,绑定的结果放在function中,然后通过可调用对象的方式来修改成员变量的值:

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_i = 0;
};
https://blog.csdn.net/u012321968/article/details/int main()
{
Test t;
std::function<https://blog.csdn.net/u012321968/article/details/int&(void)> f = std::bind(&Test::m_i,&t);
f() = 6;  //t.m_i变成了6
return 0;
}

如果把std::bind(&Test::m_i,&t);改成std::bind(&Test::m_i,t);那么会多出两次拷贝构造。第一次:创建一个临时变量(t的副本);第二次:bind函数返回一个带有仿函数的Test类对象时。

第七十六课

lambda表达式,for_each,find_if

1.lambda表达式:
(1)来自C++11,也是一种可调用对象
(2)格式
[捕获列表](形参列表)->返回值类型{ //… }; #最后的分号不能少!
例如:auto f = [](https://blog.csdn.net/u012321968/article/details/int a)->https://blog.csdn.net/u012321968/article/details/int{ return a+1 };
https://blog.csdn.net/u012321968/article/details/int i = f(1); // i == 2
(3)lambda表达式可以在函数内部定义,这是普通函数办不到的
(4)lambda表达式可以理解为“可调用的代码单元”,或未命名的内联函数
(5)某些简单的情况下,lambda表达式返回值类型特别明显,所以有时候允许lambda表达式的返回值类型省略,编译器可以自动推导
(6)lambda表达式的形参列表的形参可以给默认值
(7)没有参数的时候,参数列表也可以省略不写,甚至()也可以省略,如
auto f1 = { return 1; };
auto f2 = []{ return 2; };
(8)lambda表达式可以不返回任何类型,就是void

2.lambda表达式的捕获列表
(1)[]表示不捕获任何变量,但不包括静态局部变量,即局部静态变量是不需要捕获就可以在lambda表达式里使用
(2)[&]表示捕获外部作用域中此lambda表达式前面的所有变量,并作为引用在函数体里使用
(3)[=]表示捕获外部作用域中此lambda表达式前面的所有变量,并作为副本(按值)在函数体里使用,只能使用捕获变量的值,不能给捕获的变量赋值
(4)[this]一般用于类中,捕获当前类中this指针,让此lambda表达式拥有跟类成员函数一样的权限。注意:在类中使用lambda表达式时,如果[]中已经使用了&或者=,那么默认就已经使用了this
(5)[变量名]表示按值捕获变量名代表的变量,同时不捕获其它变量。如果有多个变量,那么彼此之间用逗号分隔
(6)[&变量名]表示按引用捕获变量名代表的变量,同时不捕获其它变量。如果有多个变量,那么彼此之间用逗号分隔
(7)[=,&变量名]表示按值捕获所有外部变量,但按引用捕获&中所指的变量。注意,=必须放在开头,因为开头表示默认的捕获方式。即不能[=,&x,y],这样写会出错,因为默认是按值捕获的,那么后面的y就不能再显示的表示按值捕获,必须要跟按值捕获不一样才行
(8)[&,变量名]表示默认按引用捕获外部所有变量,但按值捕获变量名所指的变量。注意,&必须放在开头。跟(7)一样,不能[&,x,&y]
(9)若捕获列表为空,那lambda表达式就相当于一个函数指针,如
https://blog.csdn.net/u012321968/article/details/int (*p)(https://blog.csdn.net/u012321968/article/details/int) = { return 1; };

3.lambda表达式中的mutable
(1)如果捕获列表使用[=],那么加了mutable后,按值捕获的变量可以被赋值,mutable的位置在参数列表()的后面,如
https://blog.csdn.net/u012321968/article/details/int x = 5;
auto f = =mutable{ x = 6; };

4.lambda表达式的类型及存储
(1)lambda表达式在C++11中为闭包类型,即函数内的函数,本质上是lambda表达式创建的在运行时期的对象,是一种比较特殊的匿名的闭包类类对象,可以认为它是带有一个operator()的类类型对象,也就是仿函数。所以,我们可以用std::function和std::bind来调用和保存lambda表达式

5.语法糖
(1)语法糖是指基于语言的特性,构建出一个东西,程序员用起来会很方便,但它没有增加原有语言的功能。如https://blog.csdn.net/u012321968/article/details/int a[5]; a[2] = 1; 这就是一个语法糖,因为最本质是*(a + 2) = 1;
(2)lambda表达式可以看成是定义仿函数闭包的语法糖

6.for_each简介
(1)需要包含头文件#include <algorithm>
(2)用法示例

void func(https://blog.csdn.net/u012321968/article/details/int i){  (void)i;  }
vector<https://blog.csdn.net/u012321968/article/details/int> v = { 1,2,3,4,5 };
for_each(v.begin(),b.end(),func);

(3)结合lambda表达式

vector<https://blog.csdn.net/u012321968/article/details/int> v = { 1,2,3,4,5 };
https://blog.csdn.net/u012321968/article/details/int sum = 0;
for_each(v.begin(),b.end(),[&sum](https://blog.csdn.net/u012321968/article/details/int val){
sum += val;
});
cout << sum << endl;  //15

7.find_if简介
(1)需要包含头文件#include
(2)用法示例

vector<https://blog.csdn.net/u012321968/article/details/int> v = { 1,2,3,4,5 };
auto result = find_if(v.begin(),v.end(),[=](https://blog.csdn.net/u012321968/article/details/int val){
if(val > 3)  return true;
else return false;
});

解释:如果find_if的第三个参数(可调用对象)返回false,那么find_if就继续不停的遍历迭代器,如果返回true,那么就停止遍历,find_if返回指向第一个满足返回true的容器的元素的迭代器,即指向v中的4的迭代器。如果最终没有满足返回true的元素,那么find_if返回指向v.end()的迭代器

第七十七课

lambda表达式捕获模式的陷阱分析和展示

1.lambda表达式的捕获列表[&]的坑
(1)示例

std::vector<function<bool(https://blog.csdn.net/u012321968/article/details/int)>> gv;
void func()
{
srand((unsigned https://blog.csdn.net/u012321968/article/details/int)time(NULL));
https://blog.csdn.net/u012321968/article/details/int tmpValue = rand() % 6;
gv.push_back([&](https://blog.csdn.net/u012321968/article/details/int val){
if(val % tmpValue == 0)
return true;
return false;
});
}

https://blog.csdn.net/u012321968/article/details/int main()
{
func();
cout << gv[0](https://blog.csdn.net/u012321968/article/details/10) << endl;

return 0;
}

上述程序会出错,因为:func函数中的lambda表达式是用&捕获变量,在main函数中使用gv0调用的时候会调用func函数中的lambda表达式,就会使用到func函数中的局部变量tmpValue,而tmpValue变量的作用域在func函数中,main函数不是它的作用域,所以这样会出错

2.auto与lambda表达式中的形参列表
(1)在C++14中,允许lambda表达式的形参列表使用auto

3.成员变量的捕获问题
(1)示例

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

vector<std::function<bool(https://blog.csdn.net/u012321968/article/details/int)>> gv;

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_value = 6;

void addItem()
{
gv.push_back([=](https://blog.csdn.net/u012321968/article/details/int val) {
cout << "m_value == " << m_value << endl;
if (val % m_value == 0)
return true;
return false;
});
}
};

https://blog.csdn.net/u012321968/article/details/int main()
{
Test* t = new Test();

t->addItem();
delete t;

cout << gv[0](https://blog.csdn.net/u012321968/article/details/10) << endl;

return 0;
}

结果:打印出m_value的值为随机值。

疑问:在addItem成员函数的lambda表达式中,是用[=]来捕获成员变量的,按理说该lambda表达式中已经保存了m_value的副本值,在delete t之后应该不妨碍该lambda表达式使用其内部保存的副本值才对。

事实上:lambda表达式里捕获这个概念,首先说一下lambda表达式是函数中的函数,它捕获列表捕获的是针对于包含它的函数(此例中是addItem成员函数)里的局部的、非静态的变量,而m_value这个类成员变量不是addItem成员函数的局部、非静态变量,所以该lambda表达式实际上捕获不到m_value。之所以可以使用m_value是因为[=]实际上捕获的是this的值,使用m_value本质上是this->m_value。在main函数中delete t之后,t所指向的Test对象的内存已被回收,这时候访问到的m_value就是不确定值了。

解决办法:在addItem成员函数中定义一个局部变量先保存m_value的值,然后在lambda表达式中使用局部变量而不是m_value

4.广义的lambda捕获
(1)在C++14中引入了广义的lambda捕获
(2)使用方法,直接在捕获列表用赋值语句来按值捕获所需变量,示例

class Test
{
public:
https://blog.csdn.net/u012321968/article/details/int m_value = 6;

void addItem()
{
gv.push_back([abc = m_value](https://blog.csdn.net/u012321968/article/details/int val) {
cout << "m_value == " << abc << endl;
if (val % abc == 0)
return true;
return false;
});
}
};

abc名字随便起,按变量命名规范就行,不用说明它的类型,它相当于局部变量。这样在lambda表达式中使用的就是abc的副本,这是安全的

5.静态局部变量与lambda表达式
(1)静态局部变量不需要捕获,在lambda表达式中可以直接使用
(2)在lambda表达式中使用静态局部变量实际上是用静态局部变量的引用,所以在调用该lambda表达式的时候,使用的是静态局部变量最新的值

第七十八课

可变参数函数,initializer_list,省略号形参

1.initializer_list
(1)C++11引入,是个类模板,理解成某种数值类型的数组
(2)#include <iostream>或#include <initializer_list>
(3)使用的条件:所有实参类型相同
(4)initializer_list中的元素是常量,初始化之后不允许被改变
(5)initializer_list支持用迭代器遍历,有size()方法
(6)有initializer_list做为形参的函数,也可以有其他类型的形参
(7)拷贝、赋值一个initializer_list对象,不会拷贝列表中的元素,原来对象和新对象共享列表中的元素

2.省略号形参(…)
(1)#include <stdarg.h>
(2)省略号形参一般无法正确处理类类型对象
(3)省略号形参的参数数量不固定,但是函数的所有参数是存放在线性连续的栈空间的
(4)…前面必须要有个普通类型的参数。
(5)例子:

void func(https://blog.csdn.net/u012321968/article/details/int num,...)  //...前的逗号可以省略
{
va_list valist;  //va_list是个宏
va_start(valist,num);  //使valist指向起始的参数,即i后面的第一个可变参数
https://blog.csdn.net/u012321968/article/details/int sum = 0;
for(https://blog.csdn.net/u012321968/article/details/int i = 0;i < num;i++)
{
sum += va_arg(valist,https://blog.csdn.net/u012321968/article/details/int);
}

va_end(valist);
}

第七十九课

萃取(traits)技术概念,范例等

1.萃取
(1)C++11引入
(2)参考网站:https://en.cppreference.com/w/cpp/types
(3)通过萃取接口的值是true还是false,就能萃取出许多有用信息

2.萃取的例子

template <typename T>
void traitsInfo(const T& tmp)
{
cout << “类型的名字:” << typeid(T).name() << endl;
cout << “is_void :” << is_void<T>::value << endl;  //是否是void
cout << “is_class :” << is_class<T>::value << endl;  //是否是一个class
cout << “is_object :” << is_object<T>::value << endl;  //是否是一个对象类型
cout << “is_default_constructible :” << is_default_constructible<T>::value << endl;  //是否有缺省构造函数
cout << “is_copy_constructible :” << is_copy_constructible<T>::value << endl;  //是否有拷贝构造函数
cout << “is_move_constructible :” << is_move_constructible<T>::value << endl;  //是否有移动构造函数
cout << “is_destructible :” << is_destructible<T>::value << endl;  //是否有析构函数
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
苏瓜皮 发布了17 篇原创文章 · 获赞 1 · 访问量 1484 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: