目录
15.function 和 bind 和 placeholders
1.类型推导
auto:
c++11中auto并不代表一种实际的数据类型,它只是一个类型声明的占位符,auto也并不是再所有场景下都能推导出变量的实际类型,使用auto不需要进行初始化,让编译器推导出它的实际类型,再编译阶段将auto占位符替换为真正的类型。
auto还可以和指针,引用以及const,在不同的场景下有对应的推导规则.
- 当变量不是指针或者引用时,推导的结果中不会保留const关键字
- 当变量是指针或者引用时,推导结果中会保留const关键字
auto的限制:
- auto 不能作为函数参数,因为函数参数只有在函数调用的时候才会将实参传入,auto要求必须给修饰的变量赋值
- 不能定义数组
- 不能用于类的非常量静态成员变量的初始化
auto经常应用在STL容器的遍历,其他地方尽量少用,不利于代码阅读
遍历哈希表时获取的是对象不是迭代器所以要用it.first或者it.second
decltype
2.范围for
3.nullptr
nullptr:空值指针
nullptr和NULL的区别:
nullptr可以被转换成任意其他的指针类型;而NULL在cpp种被宏定义为0,在遇到重载时可能会出现问题并且在cpp种不允许被转换.
4.内置类型的成员变量在类中声明时可以直接在类中初始阿化
5.列表初始化
{}的方式初始化
用户自定义类型单个对象直接支持{}的初初始化,如果一段空间的初始化--{}initializer_list
特点:
- 初始化参数列表中,成员初始化次序与他们在类中的声明次序相同,与初始化参数列表中的次序无关
- 数据成员的执行次序为:类内初始->初始化参数列表->构造函数体
- 常量成员,引用成员必须使用初始化列表进行初始化,因为常量和引用只能初始化不能赋值,没有默认构造的类作为父类时,子类必须在初始化参数列表中创建父类
6.可变模板参数列表
7.STL中新增加容器
序列式容器 array:静态类型顺序表 forward_list:带头结点单向循环链表
关联式容器 底层为哈希结构 unordered_map/set/...
8.final 和 override
final 修饰类:该类不能被继承 修饰虚函数:最好修饰子类的虚函数---作用:该虚函数不能被其子类再重写
override:只能修饰子类的虚函数,作用:让编译器在编译阶段检测该函数释放重写了基类的某个虚函数 明确的表明将会重写父类的虚函数
9.默认成员函数的控制
=default:跟在默认成员函数参数列表之后,作用:告诉编译器在编译阶段生成该默认的成员函数
=delete:根再默认成员函数参数列表之后,作用:告诉编译器在编译阶段删除该默认成员函数
10.智能指针
c++中不像java自带垃圾回收机制,必须释放掉分配的内存,否则机会造成内存泄漏。因此c++11加入了智能指针。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动的销毁分配的对象,防止内存泄漏。智能指针的核心实现技术是引用技术,每使用它一次内部引用计数+1,每析构一次内部引用技术-1,减为0时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件<memory>.
实现智能指针时需要考虑以下三个方面的问题:
在对象构造时获取资源,在对象析构的时候释放资源,利用对象的生命周期来控制程序资源,即RAII特性。
对*和->运算符进行重载,使得该对象具有像指针一样的行为。
智能指针对象的拷贝问题。
概念说明: RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、互斥量等等)的简单技术。
2. RAII: 资源获取即初始阿化---构造函数中放置资源,析构函数中销毁资源----在创建对象时编译器会自动调用构造函数和虚构函数
实现原理:
RAII:保证资源自动释放 具有指针的操作:T& operator*() 和 T* operator->()重载 解决浅拷贝的方式
生命周期:
智能指针的生命周期本质是函数栈帧 入栈时候用构造 出栈的时候用析构
智能指针的缺点:
1.性能开销:智能指针需要进行额外的内存管理和引用计数操作,这可能会导致程序的性能下降。相比于原始指针,智能指针需要更多的计算资源和时间来完成内存管理任务。
2.循环引用:如果智能指针被用于管理对象之间的循环引用,就可能会出现内存泄漏的问题。当两个对象相互引用时,它们的引用计数永远不会达到零,因此它们的内存也永远不会被释放。
3.难以调试:由于智能指针管理的内存是自动分配和释放的,因此在程序运行时,很难确定哪个指针指向哪个内存块,以及哪个指针可能导致内存泄漏或悬挂指针等问题。这使得调试非常困难。
4.不适用于某些场景:智能指针通常适用于单线程环境,但在某些多线程或异步环境中,智能指针的使用可能会导致竞态条件或死锁等问题。此外,智能指针也不适用于需要在不同的进程之间共享内存的场景。
各种不同版本的智能指针:
auto_ptr C++98: 资源转移 C++11之前:资源管理权限的转移----对资源释放的权限 C++11:又恢复到---资源转移
unique_ptr 资源独占---从跟上防止浅拷贝:将拷贝构造和赋值运算符重载禁止 优点:实现简单,效率高 缺陷:不能共享
std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值个另一个unique_ptr。
unqiue_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。
shared_ptr 可以共享,原理:引入引用计数---使用资源的对象的个数 优点:可能共享
缺点:实现复杂 考虑线程安全 效率第 循环引用
weak_ptr 不能单独管理对象,唯一作用:就是配合shared_ptr解决循环问题
弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理shared_ptr 内部的指针。 std::weak_prt没有重载操作符 * 和 ->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。
shared_ptr是线程安全的吗?
原子操作解决了引用计数的计数的线程安全问题, 但是智能指针指向的对象的线程安全问题,智能指针没有做任何的保证。 首先智能指针有两个变量,一个是指向的对象的指针,还有一个就是我们上面看到的引用计数管理对象。
weak_ptr是怎么解决循环引用的
循环引用比如是shared_ptr定义了ab两个变量 那么这两个变量在创建的时候就引用计数+1 但是如果a指向b b指向a 就会导致ab的引用计数都是2 即使a离开了作用域进行了析构减少了一次引用计数也无法到达0 就会造成内存泄漏 但是weak_ptr会取代其中的一个变量 weak_ptr在创建一个变量的时候 引用计数不会+1 所以在解除指向的时候引用计数就会变成0
11.右值引用
左值:
左值是一个表示数据的表达式,如变量名或解引用的指针。
- 左值可以被取地址,也可以被修改(const修饰的左值除外)。
- 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。
右值:
右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等。
- 右值不能被取地址,也不能被修改。
- 右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边。
- 右值本质就是一个临时变量或常量值,比如代码中的10就是常量值,表达式x+y和函数fmin的返回值就是临时变量,这些都叫做右值
- 这些临时变量和常量值并没有被实际存储起来,这也就是为什么右值不能被取地址的原因,因为只有被存储起来后才有地址。
- 但需要注意的是,这里说函数的返回值是右值,指的是传值返回的函数,因为传值返回的函数在返回对象时返回的是对象的拷贝,这个拷贝出来的对象就是一个临时变量。
c++11中右值分为两种情况:一个是将亡值,一个是纯右值.
将亡值:与右值引用相关的表达式,比如:T&&类型函数的返回值,std::move()的返回值等
纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字变量,lambda表达式等
为什么要有右值?
当我们要接受一个右值做返回值的时候,因为右值出了函数作用域就被析构了,所以要是想能接收到这个右值的话,就需要对这个将亡值进行一次拷贝 然后在把这个临时变量拷贝给接受变量 这就会造成多次拷贝 影响程序效率 但是C++11通过引入右值的方法增加了将亡值的生命周期 这样调用的时候就可以减少拷贝,而且右值是移动构造,但是左值是拷贝构造,移动构造的代价更低。
传统的C++语法中就有引用的语法,而C++11中新增了右值引用的语法特性,为了进行区分,于是将C++11之前的引用就叫做左值引用。但是无论左值引用还是右值引用,本质都是给对象取别名。
左值引用就是对左值的引用,给左值取别名,通过“&”来声明。
右值引用就是对右值的引用,给右值取别名,通过“&&”来声明。
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,如果不想让被引用的右值被修改,可以用const修饰右值引用。
左值引用不能引用右值,因为这涉及权限放大的问题(因为右值不能被修改但是左值可以),右值是不能被修改的,而左值引用是可以修改。
但是const左值引用可以引用右值,因为const左值引用能够保证被引用的数据不会被修改。
因此const左值引用既可以引用左值,也可以引用右值
右值引用只能引用右值,不能引用左值。
但是右值引用可以引用move以后的左值。
move:
move函数是C++11标准提供的一个函数,被move后的左值能够赋值给右值引用
c++11添加了右值引用,却不能左值初始化右值引用,在一特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move()函数。move()函数可以将左值比换为右值
move(移动语义)的底层实现
move的底层实现其实是一个强制类型转换 作用是把左值转换成右值 本质是一个static_case
右值引用的特点:
c++中,并不是所有情况下&&都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto&&,这两种情况下&&被称作未定的引用类型。另外constT&&表示一个右值引用,不是未定引用类型。因为T&&或者auto&&这种未定引用类型作为参数时,有可能被推导成右值引用,也有能被推导为左值引用,在进行类型推导时右值引用会发生变化,这种变化被称为引用折叠,折叠规则如下:
通过右值 推导T&&或者auto&&得到的是一个右值引用类型,constT&&表示一个右值引用
通过非右值(右值引用,左值,左值引用,常量右值引用,常量左值引用)推导T&&或者auto&&得到的是一个左值引用类型.
移动语义:借助String类分析operator+的实现的缺陷 指的是:资源转移---将将亡对象中的资源转移给其他对象
移动构造 T(T&& t)
{}
移动赋值 T& operator=(T&& t)
{}
所有的类型转换都要经过临时变量吗?
分片不需要
完美转发:
右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该产生给内部其他函数时,他就变成了一个左值(当右值被命名时编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的 std::forward()函数,该函数实现的功能称之为完美转发
12.lambda表达式
语法格式:
[捕获列表](参数列表)mutable->返回值类型{ .... }
[]必须要有,不能省略
如果没有参数,参数列表可以省略
mutable可以省略
->返回值类型可以省略
{...}不能省略
[]{}最简单的lambda表达式
捕获列表[]:作用:捕获lambda表达式父作用域中的变量,可以在lambda表达式内部使用
参数列表():和普通函数的参数列表一样,如果没有参数,参数列表可以省略不写
捕获列表:
捕获方式 [a,b]: 以值的方式捕获父作用域中的a和b [=]: 以值的方式捕获父作用域中的所有变量 [&a,&b]: 以引用的方式捕获父作用域中的a和b [&]: 以引用的方式捕获父作用域中的所有变量 [=,&b]: 以值的方式捕获父作用域中的所有变量,但是b以引用的方式捕获 不能重复捕获 [=,a] 或者[&, &a]属于重复捕获,编译失败
使用场景 之前需要传递函数指针的场景 或者 需要传递仿函数对象的场景,都可以采用lambda表达式来取代 sort(v.begin(), v.end(), [](int a, int b){ return a > b;});
底层实现方式:在代码层面实现的lambda表达式经过编译器编译之后将其转换为仿函数---捕获列表式中的内容实际就是该类的成员变量,lambda表达式实现体最终变成了类中的operator()的重载
比较一下函数指针方式以及lambda方式
第一点是函数定义在别的地方,比如很多行以前(后)或者别的文件中,这样的代码阅读起来不方便
第二点则是出于效率考虑,使用函数指针很可能导致编译器不对其进行inline优化(inline对编译器而言并非强制),在循环次数较多的时候,内联的lambda和没有能够内联的函数指针可能存在着巨大的性能差别,因此相比函数指针,lambda拥有无可替代的优势。
13.线程库
thread类
创建线程方式,线程函数的实现传递的方式 函数指针 仿函数 lambda表达式
线程函数如果需要以引用的方式传参,在传参时候必须使用std::ref(参数)
原子类型 所有的内置类型都有对应的原子类型 int--->atomic_int 自定义类型也可以通过atomic模板设置为原子类型
lock_guard 和 unique_lock区别
14.C++14和C++17引入了哪些新特性
C++14和C++17是C++标准的后续版本,继承了C++11的许多特性,同时也引入了一些新特性和语法,包括以下几个方面: C++14:
- 泛型Lambda表达式;
- 二进制字面量;
- 常量表达式函数;
- 可变参数模板参数类型推断;
- 数组类型的decltype;
- std::make_unique等新的库函数。
C++17:
- 类模板参数推断;
- 结构化绑定语法;
- if constexpr语句;
- 内联变量;
- constexpr lambda表达式;
- 并行算法等新的库函数。
- 什么是C++的多态性?
15.function 和 bind 和 placeholders
function
#include < functional>
定义:
在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象(即C++98中的functor)。C++11中,新增加了一个std::function对象,std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,对C++中现有的可调用实体的一种类型安全的包裹。
通过std::function对C++中各种可调用实体(普通函数、函数指针、Lambda表达式、仿函数、类成员函数以及类静态函数等)的封装,形成一个新的可调用的std::function对象,让我们不再纠结那么多可调用的实体,有种“万众归一”的调调,一切变得简单粗暴。
可以把function看成是一种表示函数的数据类型,就像函数对象一样。只不过普通的数据类型表示的是数据,function表示的是函数这个抽象概念。
简要来说:std::function是将所有可调用的实体封装起来,形成了一个新的std::function对象,用户在使用的时候不需要再去一一调用实体,只需要使用新的std::function来调用各实体、
原型说明:
template <class T> function; // undefined
template <class Ret, class... Args> class function<Ret(Args...)>;
通常std::function是一个函数对象类,它包装其它任意的函数对象,被包装的函数对象具有类型为T1, …,TN的N个参数,并且返回一个可转换到R类型的值。
std::function<void()> f1;
std::function<int (int , int)> f2;
关于可调用实体转换为std::function对象需要遵守以下两条原则:
- 转换后的std::function对象的参数能转换为可调用实体的参数;
- 可调用实体的返回值能转换为std::function对象的返回值。
举例:
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <string>
#include <algorithm>
#include <functional>
#include <memory>
using namespace std;
//声明一个模板
typedef std::function<int(int)> Functional;
//normal function
int TestFunc(int a)
{
return a;
}
//lambda expression
auto lambda = [](int a)->int{return a;};
//functor仿函数
class Functor
{
public:
int operator() (int a)
{
return a;
}
};
//类的成员函数和类的静态成员函数
class CTest
{
public:
int Func(int a)
{
return a;
}
static int SFunc(int a)
{
return a;
}
};
int main(int argc, char* argv[])
{
//封装普通函数
Functional obj = TestFunc;
int res = obj(0);
cout << "normal function : " << res << endl;
//封装lambda表达式
obj = lambda;
res = obj(1);
cout << "lambda expression : " << res << endl;
//封装仿函数
Functor functorObj;
obj = functorObj;
res = obj(2);
cout << "functor : " << res << endl;
//封装类的成员函数和static成员函数
CTest t;
obj = std::bind(&CTest::Func, &t, std::placeholders::_1);
res = obj(3);
cout << "member function : " << res << endl;
obj = CTest::SFunc;
res = obj(4);
cout << "static member function : " << res << endl;
return 0;
}
bind
#include < functional>
定义:
bind是一种机制,可以预先把指定的可调用的实体的某些参数绑定到已有的变量,产生一个新的可调用实体。
std::bind是一个函数模板,就像是一个函数适配器,接受一个可调用对象,而生成一个新的可调用对象来适配原来的参数列表,该模板返回的是一个std::function对象。用该函数我们也能实现参数顺序的调整和给将指定参数设置成固定值。
可调用对象:
普通函数
lambda表达式
函数指针
仿函数(functor 重载括号运算符实现)
类成员函数
静态成员函数
函数原型 :
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
f:一个可调用对象,它的参数将被绑定到args上。
args:绑定参数列表,参数会被值或占位符替换,其长度必须与f接收的参数个数一致。
调用std::bind的一般形式为:
auto newCallable = std::bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
placeholders
占位符,通过查看functional文件可以看到c++11中有29个占位符,分别是_1~_29,一般情况下是写std::placeholders::_1这样子的。占位符的作用就是用来代表参数的,std::placeholders::_1表示的是std::bind得到的std::function对象被调用时,传入的第一个参数,而std::placeholders::_2则是第二个参数。
我们调整std::placeholders::_x在std::bind时的顺序,就可以起到调整参数顺序的作用了。此外,我们也可以在std::bind的时候不用std::placeholders::_x,而直接写成固定的值,这样子调用std::function存储的对象时,对应位置的参数将是固定值。
举例:
#include <iostream>
using namespace std;
class A
{
public:
void fun_3(int k,int m)
{
cout<<k<<" "<<m<<endl;
}
};
void fun(int x,int y,int z)
{
cout<<x<<" "<<y<<" "<<z<<endl;
}
void fun_2(int &a,int &b)
{
a++;
b++;
cout<<a<<" "<<b<<endl;
}
int main(int argc, const char * argv[])
{
auto f1 = std::bind(fun,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
f1(); //print:1 2 3
auto f2 = std::bind(fun, placeholders::_1,placeholders::_2,3);
//表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f2 的第一,二个参数指定
f2(1,2);//print:1 2 3
auto f3 = std::bind(fun,placeholders::_2,placeholders::_1,3);
//表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f3 的第二,一个参数指定
//注意: f2 和 f3 的区别。
f3(1,2);//print:2 1 3
int n = 2;
int m = 3;
auto f4 = std::bind(fun_2, n,placeholders::_1);
f4(m); //print:3 4
cout<<m<<endl;//print:4 说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的
cout<<n<<endl;//print:2 说明:bind对于预先绑定的函数参数是通过值传递的
A a;
auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2);
f5(10,20);//print:10 20
std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
fc(10,20);//print:10 20
return 0;
}