昆明门户网站建设,网站开发前景与趋势如何,微信小程序制作价格,城乡建设部网站安全员证书查询之前那个只是总结了一下常考点#xff0c;这个是纯手打记笔记加深理解 这里写目录标题 C的四种智能指针为什么要使用智能指针#xff1f;四种智能指针#xff1a; C中的内存分配情况C中的指针参数传递和引用参数传递C 中 const 和 static 关键字#xff08;定义#xff0…之前那个只是总结了一下常考点这个是纯手打记笔记加深理解 这里写目录标题 C的四种智能指针为什么要使用智能指针四种智能指针 C中的内存分配情况C中的指针参数传递和引用参数传递C 中 const 和 static 关键字定义⽤途static:控制变量的存储方式和可见性。const含义及实现机制 C 中重载重写的区别重载overload)重写override 指针和引用的区别野指针和空指针的区别函数传递参数的⼏种⽅式new / delete malloc / free 区别面向对象的三大特性并举例说明多态的实现虚函数相关虚函数表虚函数指针虚函数的实现原理编译器处理虚函数表应该如何处理析构函数一般协程虚函数的原因构造函数为什么一般不定义为虚函数析构函数的作用如何起作用构造函数的执行顺序析构函数的执行顺序构造函数顺序析构函数顺序 纯虚函数应用于接口继承和实现继承深拷贝和浅拷贝的区别什么情况下会调用拷贝构造函数为什么拷⻉构造函数必需时引⽤传递不能是值传递内存泄露的定义如何检测和避免说⼀下红⿊树RB-treeLambda表达式右值引用智能指针final和override C的四种智能指针
为什么要使用智能指针
智能指针的作用是管理一个指针避免程序员申请的空间在函数结束时忘记释放造成内存泄露这种情况的发生 使用智能指针可以很大程度的避免这个问题因为智能指针就是一个类当超出了类的作用域时类会自动调用析构函数析构函数会自动释放资源所以智能指针的作用原理就是在函数结束时自动释放内存空间不需要手动释放内存空间
四种智能指针
auto_ptr(C11已抛弃采用所有权模式存在潜在的内存崩溃问题
auto_ptrstd::string p1 (new string (hello));
auto_ptrstd::string p2;
p2 p1; //auto_ptr 不会报错.unique_ptr(替换auto_ptr):实现独占式拥有或严格拥有概念保证同一时间内只有一个智能指针可以指向该对象对于避免资源泄露特别有用
unique_ptrstring p3 (new string (auto));//#4
unique_ptrstring p4//#5
p4 p3;//此时会报错所有权模式但比auto_ptr更加安全
shared_ptr共享型强引用 实现共享式拥有概念多个智能指针可以指向相同的对象该对象和其相关资源会在最后一个引用被销毁时释放。从名字share就可以看出了资源可以被多个指针共享它使用计数机制来表明资源被几个指针共享
可以通过成员函数use_count()来查看资源的所有者个数除了可以通过new来构造还可以通过传入auto_ptr,unique_ptr,weak_ptr来构造。当我们调用release时当前指针会释放资源所有权计数减一。当计数等于0时资源会被释放
share_ptr是为了解决auto_ptr在对象所有权上的局限性在使用引用技术的机制上提供了可以共享所有权的智能指针
weak_ptr(弱引用 weak_ptr是一种不控制对象生命周期的智能指针它指向一个shared_ptr管理的对象。进行该对象的内存管理的是那个强引用的shared_ptr。
weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr设计的目的是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作它只可以从一个shared_ptr或另一个weak_ptr对象构造他的构造和析构不会引起引用计数的增加或减少
weak_ptr是用来解决shared_ptr互相引用时的死锁问题如果说两个shared_ptr相互引用那么这两个指针的引用计数永远不可能下降为0也就是资源永远不会释放。它是对对象的一种弱引用不会增加对象的引用计数和shared_ptr之间可以相互转化shared_ptr可以直接赋值给它它可以通过调用lock函数来获得
当两个智能指针都是 shared_ptr 类型的时候析构时两个资源引⽤计数会减⼀但是两者引⽤计数还是为 1导 致跳出函数时资源没有被释放的析构函数没有被调⽤解决办法把其中⼀个改为weak_ptr就可以。
C中的内存分配情况
栈由编译器分配管理和回收存放局部变量和函数参数 堆由程序员管理需要手动new malloc delete free 进行分配和回收空间较大但可能会出现内存泄露和空闲碎片的情况 全局/静态存储区分为初始化和未初始化两个相邻区域存储初始化和未初始化的全局变量和静态变量 常量存储区储存常量一般不允许修改 代码区存放程序的二进制代码
C中的指针参数传递和引用参数传递
指针参数传递本质上是值传递他所传递的是一个地址值。值传递过程中被调函数的形式参数作为被调函数的局部变量处理会在栈中开辟内存空间以存放由主调函数传递进来的参数值从而形成了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行的不会影响主调函数的实参变量的值
引用参数传递过程中被调函数的形式参数也作为局部变量在栈中开辟了内存空间但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参本体的任何操作都被处理成间接寻址即通过栈中存放的地址访问主调参数中的实参变量。因此被调函数对形参的任何操作都会影响主调函数的实参变量
引⽤传递和指针传递是不同的虽然他们都是在被调函数栈空间上的⼀个局部变量但是对于任何引用参数的处理都会通过一个间接寻址的方式操作到主调函数的相关变量。而对于指针传递的参数如果改变被调函数中的指针地址它将应用不到主调函数的相关变量如果想通过指针参数传递来改变主调函数中的相关变量那就得使用指向指针或者指针引用
C 中 const 和 static 关键字定义⽤途
static:控制变量的存储方式和可见性。
修饰局部变量一般情况下对于局部变量在程序中是存放在栈区的并且局部的生命周期在包含语句块执行结束时便结束了。但是如果用static关键词修饰的话该变量便会存放在静态数据区其生命周期会一直延续到整个程序执行结束但是要注意的时虽然用static对全局变量进行修饰之后其生命周期以及存储空间发生了变换但其作用域并没有改变作用域还是限制在其语句块修饰全局变量对于一个全局变量它既可以在本文件中被访问到也可以在同一个工程中其他源文件被访问。用static对全局变量进行修饰改变了其作用域范围由原来的整个工程可见变成了本文件可见修饰函数和全局变量类似也是改变了函数的作用域修饰类如果C中对类中的某个函数用static修饰则表示该函数属于一个类而不是属于此类的任何特定对象如果对类中的某个变量进行static修饰则表示该变量在存储空间中只存在一个副本可以通过类和对象去调用作用五:类成员/类函数声明 static
函数体内 static 变量的作用范围为该函数体不同于 auto 变量该变量的内存只被分配一次因此其值在下次调用时仍维持上次的值;在模块内的 static 全局变量可以被模块内所用函数访问但不能被模块外其它函数访问;在模块内的 static 函数只可被这一模块内的其它函数调用这个函数的使用范围被限制在声明它的模块内;在类中的 static 成员变量属于整个类所拥有对类的所有对象只有一份拷贝;在类中的 static 成员函数属于整个类所拥有这个函数不接收 this 指针因而只能访问类的 static 成员变量static 类对象必须要在类外进行初始化static 修饰的变量先于对象存在所以 static 修饰的变量要在类外初始化;由于 static 修饰的类成员属于类不属于对象因此 static类成员函数是没有 this 指针this 指针是指向本对象的指针正因为没有 this 指针所以 static 类成员函数不能访问非 static 的类成员只能访问 static修饰的类成员;static 成员函数不能被 vrtual 修饰static成员不属于任何对象或实例所以加上 vrtual 没有任何实际意义;静态成员函数没有 this 指针虚函数的实现是为每一个对象分配一个 vptr 指针而 vptr 是通过 this 指针调用的所以不能为 virtual;虚函数的调用关系this-vptr-ctable-virtual function。
const含义及实现机制
const 修饰基本类型数据类型:基本数据类型修饰符 const 可以用在类型说明符前也可以用在类型说明符后其结果是一样的。在使用这些常量的时候只要不改变这些常量的值即可。const 修饰指针变量和引用变量:如果 const 位于小星星的左侧、则 const 就是用来修饰指针所指向的变量即指 针指向为常量;如果 const 位于小星星的右侧则 const 就是修饰指针本身即指针本身是常量。const 应用到函数中:作为参数的 const 修饰符:调用函数的时候用相应的变量初始化,const 常量则在函数体中按照 const 所修饰的部分进行常量化保护了原对象的属性。[注意:参数 const 通常用于参数为指针或引用的情况: 作为函数返回值的 const 修饰符:声明了返回值后const按照修饰原则进行修饰起到相应的保护作用。const 在类中的用法:const 成员变量只在某个对象生命周期内是常量而对于整个类而言是可以改变的。因为类可以创建多个对象不同的对象其 const数据成员值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象在没有创建时候编译器不知道 const 数据成员的值是什么。const 数据成员的初始化只能在类的构造函数的初始化列表中进行。const 成员函数:const 成员函数的主要目的是防止成员函数修改对象的内容。要注意const 关键字和 static 关键字对于成员函数来说是不能同时使用的因为 static 关键字修饰静态成员函数不含有 this 指针即不能实例化const 成员函数又必须具体到某一个函数。const 修饰类对象定义常量对象:常量对象只能调用常量函数别的成员函数都不能调用
补充:const 成员函数中如果实在想修改某个变量可以使用 mutable 进行修饰。成员变量中如果想建立在整个类中都恒定的常量应该用类中的枚举常量来实现或者 static const。
C 中重载重写的区别
重载overload)
是指同一可访问区内被声明的几个具有不同参数列表的同名函数依赖于C函数名字的修饰会将参数加在后面可以是参数类型个数顺序的不同根据参数列表决定调用哪个函数重载不关心函数的返回类型
重写override
派生类中重新定义父类中除了函数体外完全相同的虚函数注意被重写的函数不能是static的一定要是虚函数且其他一定要完全相同。要注意一重写和被重写的函数是在不同的类当中的重写函数的访问修饰符是可以不同的尽管virtual中是private的派生类中重写可以改为public
指针和引用的区别
指针和引用都是一种内存地址的概念区别是指针是一个实体引用只是一个别名 指针指向一块内存指针的内容是所指向的内存的地址在编译的时候则是将“指针变量名-指针变量的地址”添加到符号表中所以说指针包含的内容是可以改变的允许拷贝和赋值有const和非const区别甚至可以为空sizeof得到的是指针类型的大小 而对于引用来说它只是一块内存的别名在添加到符号表的时候是将“引用变量名-引用对象的地址”添加到符号表中符号表一经完成不能改变所以引用必须而且只能在定义时被绑定到一块内存上后续不能更改也不能为空也没有const和非const的区别 sizeof引用得到代表对象的大小。而sizeof指针得到的是指针本身的大小。另外在参数传递中指针需要被解引用后才可以对对象进行操作而直接对引用进行的修改会直接作用到引用对象上 作为参数时也不同传指针的实质是传值传递的值是指针的地址传引用的是指是传地址传递的变量的地址
野指针和空指针的区别
野指针(wild pointer):就是没有被初始化过的指针。用 gcc -wa11 编译,会出现 used uninitialized 警告 空指针:是指针最初指向的内存已经被释放了的一种指针。无论是野指针还是空指针都是指向无效内存区域(这里的无效指的是不安全不可控)的指针。 访问不安全可控(invalid)的内存区域将导致Undefined Behavior。 如何避免使用野指针?在平时的编码中养成在定义指针后且在使用之前完成初始化的习惯或者使用智能指针。
函数传递参数的⼏种⽅式
值传递形参是实参的拷⻉函数内部对形参的操作并不会影响到外部的实参。 指针传递也是值传递的⼀种⽅式形参是指向实参地址的指针当对形参的指向操作时就相当于对实参本身进⾏操作。 引⽤传递实际上就是把引⽤对象的地址放在了开辟的栈空间中函数内部对形参的任何操作可以直接映射到外部的实参上⾯
new / delete malloc / free 区别
都可以用来在堆上分配和回收空间new/delete是操作符。malloc/free是库函数 执行new实际上执行两个过程1.分配未初始化的内存空间malloc2.使用对象的构造函数对空间进行初始化返回空间的首地址。如果在第一部分配空间中出现问题则抛出std::bad_alloc异常或被某个设定的异常处理函数捕获处理如果在第二部构造对象是出现异常则自动调用delete释放内存 执⾏ delete 实际上也有两个过程1. 使⽤析构函数对对象进⾏析构2.回收内存空间free。 以上也可以看出 new 和 malloc 的区别new 得到的是经过初始化的空间⽽ malloc 得到的是未初始化的空间。 所以 new 是 new ⼀个类型⽽ malloc 则是malloc ⼀个字节⻓度的空间。delete 和 free 同理delete 不仅释放 空间还析构对象delete ⼀个类型free ⼀个字节⻓度的空间。 为什么有了 mallocfree 还需要 newdelete因为对于⾮内部数据类型⽽⾔光⽤ mallocfree ⽆法满⾜动 态对象的要求。对象在创建的同时需要⾃动执⾏构造函数对象在消亡以前要⾃动执⾏析构函数。由于 malloc free 是库函数⽽不是运算符不在编译器控制权限之内不能够把执⾏的构造函数和析构函数的任务强加于 mallocfree所以有了 newdelete 操作符。
面向对象的三大特性并举例说明
C面向对象的三大特征是封装继承多态
封装就是把客观事物封装成抽象的类并且类可以把自己的数据和方法只让信任的类或者对象操作对不可信的进行信息隐藏一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部某些代码或某些数据可以是私有的不能被外界访问。通过这种方式对象对内部数据提供了不同级别的保护以防止程序中无关的部分以外的改变或错误的使用了对象的私有部分
继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力他可以使用现有的类的所有功能并在无需重新编写原来的类的情况下对这些功能进行拓展。通过继承创建的新类成为子类或派生类被继承的类称为基类父类或超类。继承的过程就是从一般到特殊的过程要实现继承可以通过继承和组合来实现 实现方式 实现继承实现继承是指直接使用基类的属性和方法而无需额外编码的能力 接口继承接口继承是指仅使用属性和方法的名称但是子类必须提供实现的能力
多态就是像不同的对象发送同一个消息不同对象在接收时会产生不同的行为即方法。即一个接口可以实现多种方法 多态与非多态的实质区别就是函数地址是早绑定还是晚绑定的。如果函数的调用在编译器编译期间就可以确定函数的调用地址并产生代码则是静态的即地址早绑定。而如果函数调用的地址不能在编译期间确定需要在运行时才确定这就属于晚绑定
多态的实现
多态其实⼀般就是指继承加虚函数实现的多态对于多态其实一般就是指继承加虚函数实现的多态对于重载来说实际上基于的原理是编译器为函数生成符号表时的不同规则重载只是一种语言特性与多态无关与面向对象也无关但这又是C中增加的新规则所以也算属于 C所以如果非要说重载算是多态的一种那就可以说:多态可以分为静态多态和动态多态。 静态多态其实就是重载因为静态多态是指在编译时期就决定了调用哪个函数根据参数列表来决定;动态多态是指通过子类重写父类的虚函数来实现的因为是在运行期间决定调用的函数所以称为动态多态 一般情况下我们不区分这两个时所说的多态就是指动态多态。 动态多态的实现与虚函数表虚函数指针相关。
虚函数相关虚函数表虚函数指针虚函数的实现原理
C中多态的表象在基类的函数前加上 virtual 关键字在派⽣类中重写该函数运行时将会根据对象的实际类型来调用相应的函数。如果对象类型时派生类就调用派生类的函数如果是基类就调用基类的函数。 实际上当一个类中包含虚函数时编译器会为该类生成一个虚函数表保存该类中虚函数的地址同样派生类继承基类派生类中自然一定有虚函数所以编译器也会为派生类生成自己的虚函数表。当我们定义一个派生类对象时编译器检测该类型有虚函数所以为这个派生类生成一个虚函数指针指向该类型的虚函数表这个虚函数指针的初始化是在构造函数中完成的 后续如果有⼀个基类类型的指针指向派⽣类那么当调⽤虚函数时就会根据所指真正对象的虚函数表指针去寻 找虚函数的地址也就可以调⽤派⽣类的虚函数表中的虚函数以此实现多态。
编译器处理虚函数表应该如何处理
编译器建立虚函数表的过程其实一共是三个步骤 拷贝基类的虚函数表如果是多继承就拷贝每个虚函数基类的虚函数表 还有一个基类的虚函数和派生类自身的虚函数共用了一个虚函数表也称为某个基类为派生类的主基类 查看派生类中是否有重写基类中的虚函数如果有就替换成已经重写的虚函数地址查看派生类是否有自身的虚函数如果有就追加自身的虚函数到自身的虚函数表中
析构函数一般协程虚函数的原因
是为了降低内存泄露的可能性。举例来说就是一个基类的指针指向一个派生类的对象在使用完毕准备销毁时如果基类的析构函数没有定义成虚函数那么编译器根据指针类型就会认为当前对象的类型是基类调用基类的析构函数仅执行基类的析构派生类的自身内容将无法被析构造成内存泄露
如果基类的析构函数定义成虚函数那么编译器就可以根据实际对象执行派生类的析构函数再执行基类的析构函数成功释放内存
构造函数为什么一般不定义为虚函数
虚函数调用只需要知道部分的信息即只需要知道函数接口而不需要知道对象的具体类型。但是我们要创建一个对象的花是需要知道对象的完整信息的。特别是需要知道创建对象的确切类型因此构造函数不应该被定义成虚函数
从目前编译器实现虚函数进行多态的方法来看虚函数的调用是通过实例化之后对象的虚函数表指针来找到虚函数的地址进行调用的如果说构造函数时虚的那么虚函数表指针则是不存在的无法找到队形的虚函数表来调用虚函数那么这个调用实际上也是违反了先实例化后调用的准则
析构函数的作用如何起作用
构造函数只是起初始化值的作用当实例化一个对象的时候可以通过实例区传递参数从主函数传递到其他的函数里面这样就使其他的函数里面有值了。规则只要你一实例化对象系统自动回调用一个构造函数哪怕不写编辑器也自动调用一次 析构函数与构造函数的作用相反用于撤销对象的一些特殊任务处理可以是释放对象分配的内存空间特点析构函数与构造函数同名但该函数前面加~ 析构函数没有参数也没有返回值而且不能重载在一个类中只能有一个析构函数。当撤销对象时编译器也会自动调用析构函数。每一个类必须有一个析构函数用户可以自定义析构函数也可以是编译器自动生成默认的析构函数。一般析构函数定义为类的公有成员
构造函数的执行顺序析构函数的执行顺序
构造函数顺序
基类构造函数。如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是他们在成员初始化表中的顺序成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是在类中被声明的顺序而不是它们出现在成员初始化表中的顺序派生类构造函数
析构函数顺序
调用派生类的析构函数调用成员类对象的析构函数调用基类的析构函数
纯虚函数应用于接口继承和实现继承
实际上纯虚函数的出现就是为了让继承可以出现多种情况 有时我们希望派生类只继承成员函数的接口 有时我们又希望派生类既继承成员函数的接口又继承成员函数的实现而且可以在派生类中重写成员函数以实现多态 有的时候我们又希望派生类在继承成员函数接口和实现的情况下不能重写缺省的实现 其实声明一个纯虚函数的目的就是为了让派生类只继承函数的接口而且派生类中必须提供一个这个纯虚函数的实现否则含有纯虚函数的类将是抽象类不能进行实例化 对于纯虚函数来说我呢吧其实是可以给他提供实现代码的但是由于抽象类不能实例化调用这个实现的唯一方式是在派生类中指出其class名称来调用
深拷贝和浅拷贝的区别
当出现类的等号赋值时会调用拷贝函数在未定义显示拷贝构造函数的情况下系统会调用默认的拷贝函数——即浅拷贝它能够完成成员的一一复制。当数据成员中没有指针时浅拷贝是可行的 但当数据成员中有指针式如果采用简单的浅拷贝则两类中的两个指针指向同一个地址当对象快要结束时会调用两次析构函数而导致野指针的问题 所以这时必须采用深拷贝。深拷贝与浅拷贝之间的区别就在于深拷贝会在堆内存中另外申请空间来存储数据从而也就解决了野指针的问题 简而言之当数据成员中有指针时必须要用深拷贝更加安全
什么情况下会调用拷贝构造函数
类的对象需要拷贝时拷贝构造函数将会被调用以下的情况都会调用拷贝构造函数
一个对象以值传递的方式传入函数体需要拷贝构造函数创建一个临时对象押入到栈空间中一个对象以值传递的方式从函数返回需要执行拷贝构造函数创建一个临时对象作为返回值一个对象需要通过另一个对象进行初始化
为什么拷⻉构造函数必需时引⽤传递不能是值传递
为了防⽌递归调⽤。当⼀个对象需要以值⽅式进⾏传递时编译器会⽣成代码调⽤它的拷⻉构造函数⽣成⼀个副 本如果类 A 的拷⻉构造函数的参数不是引⽤传递⽽是采⽤值传递那么就⼜需要为了创建传递给拷⻉构造函数 的参数的临时对象⽽⼜⼀次调⽤类 A 的拷⻉构造函数
内存泄露的定义如何检测和避免
内存泄露简单的说就是申请了一块内存空间使用完毕后没有释放掉。它的一般表现方式是程序运行事件越长占用内存越多最终用尽全部内存整个系统崩溃。由程序申请的一块内存且没有任何一个指针指向它那么这块内存就泄露了
说⼀下红⿊树RB-tree
红⿊树的定义 性质1每个节点要么是⿊⾊要么是红⾊。 性质2根节点是⿊⾊。 性质3每个叶⼦节点NIL是⿊⾊。 性质4每个红⾊结点的两个⼦结点⼀定都是⿊⾊。 性质5任意⼀结点到每个叶⼦结点的路径都包含数
Lambda表达式
Lambda表达式实际上就是提供了一个类似匿名函数的特性而匿名函数则是在需要一个函数但是又不想费力去命名一个函数的情况下去使用的 利用Lambda表达式可以编写内嵌的匿名函数用以替换独立函数或者函数对象并且是代码更可读 从本质上来讲Lambda表达式只是一种语法糖因为所有其能完成的工作都可以用其它稍微复杂的代码来实现但是它简便的语法却给C带来了深远的影响 从广义上说Lambda表达式产生的是函数对象。在类中可以重载函数调用运算符此时类的对象可以将具有类似函数的行为我们称这些对象为函数对象或者仿函数。相比Lambda表达式函数对象有自己独特的优势 Lambda表达式一般都是从方括号开始[]然后结束于花括号{}花括号里面就像定义函数那样包含了Lambda表达式体一个简单例子如下
auto basicLambda[]{coutHello,world!endl;};
basicLamba();上面是最简单的lambda表达式没有参数。如果需要参数那么就要像函数那样放在圆括号里面如果有返回值返回类型要放在-后面即拖尾返回类型当然也可以忽略返回类型lambda会帮你自动推断返回类型
//拖尾返回类型
auto add[](int a,iut b)-int {return ab; };
//自动判断返回类型
auto multiply[](int a,int b){return ab; };
int sumadd(2,5);
int productmultiply(2,5);最前面的[]时Lambda表达式的一个很重要的功能就是闭包。
Lambda表达式的大致原理每当你定义一个Lambda表达式后编译器会自动生成一个匿名类这个类当然重载了运算符我们称为闭包类型
那么在运行时这个lambda表达式就会返回一个匿名的闭包实例其实一个右值。所哟我们上面的lambda表达式的结果就是一个个闭包实例
闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量前方的方括号就是用来定义捕捉模式以及变量我们又将其成为lambda捕捉块
int main(){
int x 10;
auto add x[x](int a){return ax;};//复制捕捉xlambda表达式无法修改此变量
auto multiply_x[x](int a){return a*x;}; //引用捕捉xlambda表达式可以修改此变量
cout add x(10) multiply x(10) endl;//输出:20 100
return 0;
捕获的⽅式可以是引⽤也可以是复制但是具体说来会有以下⼏种情况来捕获其所在作⽤域中的变量
[]默认不捕获任何变量;[]默认以值捕获所有变量;[]默认以引用捕获所有变量;[x]仅以值捕获x其它变量不捕获;[x]仅以引用捕获x其它变量不捕获;[,x]默认以值捕获所有变量但是x是例外通过引用捕获;[,x]:默认以引用捕获所有变量但是x是例外通过值捕获;[this]通过引用捕获当前对象(其实是复制指针);。[*this1] 通过传值方式捕获当前对象; 而 lambda 表达式一个更重要的应用是其可以用于函数的参数通过这种方式可以实现回调函数。其实最常用的是在STL算法中比如你要统计一个数组中满足特定条件的元素数量通过 lambda 表达式给出条件传递给count_if函数:
int val 3;
vectorint v {1, 8, 5, 3, 6, 10};
int count std::count_if(v.beigin(), v.end(), [val](int x) { return x val; });
// v中⼤于3的元素数最后给出Lambda表达式的完整语法
[ capture-list ] ( params ) mutable(optional) constexpr(optional)(c17) exception
attribute - ret { body }
// 可选的简化语法
[ capture-list ] ( params ) - ret { body }
[ capture-list ] ( params ) { body }
[ capture-list ] { body }capture-list捕捉列表这个不⽤多说前⾯已经讲过它不能省略 params参数列表可以省略但是后⾯必须紧跟函数体 mutable可选将 lambda 表达式标记为 mutable 后函数体就可以修改传值⽅式捕获的变量 constexpr可选 C17 可以指定 lambda 表达式是⼀个常量函数 exception可选指定lambda表达式可以抛出的异常 attribute可选指定lambda表达式的特性 ret可选返回值类型 body:函数执行体
右值引用
C11 引入了右值引用的概念通过使用 符号来声明右值引用这样可以直接操作临时对象右值而不需要复制。这样可以大幅提升性能。
C03 及之前的标准中右值是不允许被改变的实践中也通常使用 const T的方式传递右值。然而这是效率低下的做法例如:
Person get(){Person p;return p;
}
Person pget();上述获取右值并初始化p的过程包含了 Person 的3个构造过程和2个析构过程。 这是C 广受诟病的一点但C11 的右值引用特性允许我们对右值进行修改。借此可以实现 move语义即从右值中直接拿数据过来初始化或修改左值而不需要重新构造左值后再析构右值。一个 move 构造函数是这样声明的:
class Person{
public:Person(Person rhs){...}...
};右值引用使得“移动”move语义成为可能这允许开发者从一个右值中直接获取资源而不进行深拷贝。这样可以避免不必要的内存开销。
class Person {
public:Person(Person rhs) {// 转移资源this-data rhs.data; // 假设 data 是指向某些资源的指针rhs.data nullptr; // 把右值的资源置为空防止双重释放}// ...
};
智能指针
C11 对于 C 标准库的变更。C11 把 TR1 并⼊了进来废弃了 C98 中的 auto_ptr 同时 将 shared_ptr 和 uniq_ptr 并⼊ std 命名空间。
int main() {// 创建一个 std::shared_ptr 指向一个动态分配的 double 类型变量std::shared_ptrdouble p_first(new double);{// 在这个作用域内创建一个新的 shared_ptr指向相同的 double 变量std::shared_ptrdouble p_copy p_first;// 使用 p_copy 修改 double 的值*p_copy 21.2; } // 这里结束了 p_copy 的作用域p_copy 被销毁// 此时 p_copy 被销毁但 p_first 仍然存在所指向的 double 仍然有效return 0; // 当 p_first 离开作用域时它所指向的 double 也会被销毁
}
final和override
C借由虚函数实现运行时多态但 C 的虚函数又很多脆弱的地方
无法禁止子类重写它。可能到某一层级时我们不希望子类继续来重写当前虚函数了。容易不小心隐藏父类的虚函数。比如在重写时不小心声明了一个签名不一致但有同样名称的新函数。.
C11 提供了 final 来禁止虛函数被重写/禁止类被继承override 来显示地重写虚函数。这样编译器给我们不小心的行为提供更多有用的错误和警告。
struct Basel final{};
struct Derivedl:Basel{};
// 编译错:Base1不允许被继承
struct Base2{virtual void fl()final;virtual void f2();
};
struct Derived2:Base2{virtual void fl();// 编译错:f1不允许重写virtual void f2(int)override; // 编译错:父类中没有 void f2(int)
};