首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
AI 模拟面试
简历
求职
学习
基础学习课
实战项目课
求职辅导课
专栏&文章
竞赛
我要招人
发布职位
发布职位、邀约牛人
更多企业解决方案
AI面试、笔试、校招、雇品
HR免费试用AI面试
最新面试提效必备
登录
/
注册
牛客720769522号
门头沟学院 C++
发布于黑龙江
关注
已关注
取消关注
@Dreams0000:
🥭01-C++面试之C++11新特性总结
0 前述针对于自己在秋招的面试中,对于Cpp部分遇到的问题,其中大部分是以此为起点,尼克以基于这些点,将自己对于Cpp的学习,串联起来,无论面试官,问这一类问题中的那个点,你都应该可以将这一个珠子串联到自己一大串知识点上来讲。这是一种拓展知识的能力。在此专栏下面个人校招记录:回馈牛客,对CPP做一个小小的总结。本部分关于C++11新特性总结,挑选几个比较重要的点来展开即可,如**lambda表达式、右值引用和三种智能**指针来展开。下面对应的是之前发布的个人校招其他公司面试总结,希望可以更好的帮到你✍✍总结:以代码的方式回看自己23届校招 | 或恐慌、疑惑、好在坚持!🌋大疆DJI 嵌入式 ✨MINIEYE自动驾驶 软开 深圳 实习 🌟百度 嵌入式软开 上海 2022.07🌳深圳杰理科技嵌入式 2022.07.26🌵深圳 诺瓦星云提前批 嵌入式 2022.08.06🌴哲库科技 提前批 2022.08.07🌲经纬恒润 自动驾驶嵌入式 1,2面 2022.08.15😥丰疆智能 嵌入式软开 1,2,3面+4面 2022.08.09🤷♂️一次让我略感疲惫的英文面试--吉利极氪 嵌入式软开🙆♂️♂️【深信服校招】笔试+面试 软件开发工程师🌾ZTE中兴软件工程师 一面+二面 2022.08.29🌱紫光同芯 嵌入式软开 芯片 2022.09.19🌄美团校招面经 嵌入式 1,2面 2022. 09🎋360 软开 一面+二面+ 2022.09这里是Cpp一些面试问题整理🥭01-C++面试之C++11新特性总结🍏02-C++面试之static关键字🍑03-C++面试之四种强制类型转换🍋04-C++面试之析构函数和构造函数……未完待续1 关键字/语法层面nullptr替代 NULL引入了 auto 和 decltype 这两个关键字实现了类型推导:使得编译器在编译阶段就推导出变量的类型,可以通过=右边的类型推导出变量的类型。(比较方便,自己经常使用到的)继承关键字:final 和override,默认构造函数**=default**、=delete,内存对齐alignof和alignas,delete,using的使用。(这些完全都可以是个引子,展开再说)final一个规范化的关键字:如果后面不允许继承该类的话,后面应使用 final关键字来结束:// final 用法class A final { // };//声明为final,B再继承的话,编译报错class B : A { // };override(重写/覆盖)关键字:要求基类函数是寻函数,其派生类去override这个虚函数,需要保证与基类的虚函数名字、类型、参数等完全一致。好的写法应该是:在重写函数的后面加上关键字override。1)避免写错函数名,若写错的话,编译器会认为这是一个独立的函数,编译器不会检查这个错误。1 using和typedef的区别使用:using可以用于模版别名,typedef不可以用于模版别名。(自己认为,C++里面还是using用着比较方便)template<typename T>class A { public: A() { // ... }};template <typename T>using B =A<T>;template <typename T>typedef A<T> C;int main() { A<int> a; B<int> b; // Ok, B is an alias of class A C<int> c; // Syntax Error, C cannot be recognized as a type return 0;}基于范围的for循环,结合auto关键字,也使用的比较多for (auto& val : array) { // ... }2 标准库层面增加无序容器:哈希表hashtable;正则表达式1 Lambda表达式(展开讲讲)基本形式:[捕获列表](参数列表){函数体};其中捕获列表和函数体不能省略但是捕获列表可以为空,也就是最简单的lambda表达式:[]{};。lambda 表达式又叫匿名函数,C++中,一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数,特别注意,他们的返回值类型由函数体的return语句决定,因此一般使用auto关键字来自动推导。使用实例:int i =[] {return 1;}捕获列表[]使用:[=],表示所有捕获的参数都是以值传入的,不可修改[&],表示所有捕获的参数都是以应用传入的,可以修改,修改之后,对外部的变量产生影响。int p =100, q =200;auto func =[&] {q++, p+=2; return p +q;} // 带调用参数的lambda表达式// 返回和 int a, int b是调用时传递的参数auto myADD =[](int v1, int v2) { return a +b;}// 用法auto ans =myADD(2, 3);带明确的返回值的lambda表达式,使用->// 返回最大值auto myMAX =[](int a, int b) ->int { if (a >b) return a; else return b;}// 用法auto ans =myMAX(2, 3);利用lambda表达式可以编写内嵌的匿名函数,用以独立函数或者函数对象,在向算法传递函数的时候,比如sort排序的时候,我们按照什么顺序你,可以河面穿进去一元谓词(接受一个参数)或者二元谓词(接受两个参数)Lambda 的类型被定义为“闭包”的类,其通常用于STL 库中,在某些场景下可用于简化仿函数的使用,同时Lambda 作为局部函数,也会提高复杂代码的开发加速,轻松在函数内重用代码,无须费心设计接口。// 语法形式: // [capture list](parameter list) -> return type {function body}// [capture list]: 声明使用那些局部变量,但只能使用那些明确指明的变量,只有捕获了才能在{function body}中使用。参考网址C++中的lambda表达式3 右值引用1 右值引用概念右值引用也是别名,但是只能对右值进行引用。引用的形式是int&& ra =10,只是为了区分C++98里面的引用,C++11将该种方式称之为右值引用。2 左值与右值使用形式普通类型的变量,int a,因为有名字,可以取地址,都认为是左值。const修饰的常量,代表是不可修改,只读类型,但是我们可以取地址,C++11认为其是左值。也可以指向右值,比如vector的push_back (const int & val)函数的设计,直接插入push_back (5)就是这个原理。表达式的运行结果是一个临时变量或者对象,如(a+b),认为是右值;表达式的运行结果是单个变量或是一个引用,如(c=a+b)认为是左值;右值区分:C语言中的纯右值,比如说:a+b, 100将亡值,也就是生命周期即将结束的变量,比如临时变量:表达式的中间结果,函数按照值的方式进行返回,匿名变量。(后面展开说)3 实际用法介绍int main(){ // 1.普通类型引用只能引用左值,不能引用右值 int a = 10; int& ra1 = a; // ra为a的别名 //int& ra2 = 10; // 编译失败,因为10是右值 //const引用既可以引用左值也可以引用右值 const int& ra3 = 10; const int& ra4 = a; return 0;}普通引用只能引用左值,不能引用右值;const引用既可以引用左值,也可以引用右值;C++11中右值引用;只能引用右值,一般情况不能直接引用左值(需要使用move处理一下);int main(){ // 10纯右值,本来只是一个符号,没有具体的内存地址, // 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量 int&& r1 = 10; // 可以是常数,出来之后就可以表示一个左值来用 r1 = 100; int a = 10; // int&& r2 = a; // 编译失败:右值引用不能引用左值 int&& r3=std::move(a);// 作为函数&& 返回值是右值 ,直接声明出来的 && 是左值 return 0;}右值引用只能引用右值,不能引用左值;右值引用可以进行引用std::move()处理以后的左值,move表示将该变量(左值)识别为右值,并没有移动什么,只是将左值强制转换为右值;右值引用本质是将引用的右值内容存储到空间中,使得该右值引用变量具有名称和地址,所以右值引用变量一般是一个左值。作为函数&& 返回值是右值 ,直接声明出来的 && 是左值。4 右值引用和std::move的使用场景1)一点结论从性能上讲,左右值引用并没有区别,传参使用左右值引用都可以避免一次参数拷贝。右值引用可以直接指向右值,还可以通过std::move()指向左值;而左值引用只能指向左值(除去const左值引用,也能指向右值)作为函数的形参时,右值引用更加灵活,虽然const左值引用也可以做到左右值都接受,但它无法修改传进来的参数,有一定的局限性。2)右值实现移动语义在实际的场景中,右值引用和std::move别广泛用于在STL和自定义的类中实现移动语义,避免拷贝,从而提升程序性能在没有右值引用之前,一个数组类需要有构造函数、拷贝构造、赋值运算符重载、析构函数等。class Array {public: Array(int size) : size_(size) { data_ = new int[size_]; } // 深拷贝构造 Array(const Array& temp_array) { size_ = temp_array.size_; data_ = new int[size_]; for (int i = 0; i < size_; i ++) { data_[i] = temp_array.data_[i]; } } // 深拷贝赋值 Array& operator=(const Array& temp_array) { delete[] data_; size_ = temp_array.size_; data_ = new int[size_]; for (int i = 0; i < size_; i ++) { data_[i] = temp_array.data_[i]; } } // 析构函数 ~Array() { delete[] data_; } public: int *data_; int size_;};该类的拷贝构造、赋值运算符重载函数已经通过使用左值引用来避免一次多余的拷贝了,但是内部实现还是要深拷贝,无法避免。这时候,就感觉可不可以提供一个移动构造函数,把被拷贝者的数据移动过来,被拷贝者后边就不要了,这样就可以避免深拷贝。创造类似下面这样的情况: // 移动构造函数,可以浅拷贝,不优雅的实现方式 Array(const Array& temp_array, bool move) { data_ = temp_array.data_; size_ = temp_array.size_; // 为防止temp_array析构时delete data,提前置空其data_ temp_array.data_ = nullptr; }有2个问题不优雅,要与构造函数区分,需要引入额外的参数无法实现,const类型的左值引用,你不能修改参数类型。若改成非const类型的,直接插入的形式(直接常量,直接一个数组)就不能使用了。这样,左值引用就用着很不爽了,右值引用的出现解决了这个问题,STL的容器中,都实现了以右值引用为参数的移动构造函数和移动赋值重载函数;常见的比如vector的push_back()和emplace_back(),如下P5进行展示。参数为左值引用意味着拷贝,为右值引用意味着移动 // 优雅 Array(Array&& temp_array) { data_ = temp_array.data_; size_ = temp_array.size_; // 为防止temp_array析构时delete data,提前置空其data_ temp_array.data_ = nullptr; }5 右值引用中std::move语义在STL容器中的使用// 例2:std::vector和std::string的实际例子int main() { std::string str1 = "aacasxs"; std::vector<std::string> vec; vec.push_back(str1); // 传统方法,copy vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串 vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值 vec.emplace_back("axcsddcas"); // 当然可以直接接右值} // std::vector方法定义void push_back (const value_type& val);void push_back (value_type&& val); void emplace_back (Args&&... args);在vector和string这个场景,加个std::move会调用到移动语义函数,避免深拷贝。可用在对象在需要拷贝并且拷贝之后不再被需要的场合,建议使用std::move触发移动 语义,提升性能。另外,在STL中有些类是move-only的,比如unique_ptr只有移动构造函数。6 完美转发std::forward和std::move一样,它的兄弟std::forward也充满了迷惑性,forward意思是转发,但是它并不会做转发,同样也是类型转换,不过他更高级比兄弟move,``move只能转出右值,forward是左右值都可以。std::forward(u)有两个参数:T与 u。a. 当T为左值引用类型时,u将被转换为T类型的左值;b. 否则u将被转换为T类型右值看下面两个例子,进一步加深理解:例1:void B(int&& ref_r) { ref_r = 1;} // A、B的入参是右值引用// 有名字的右值引用是左值,因此ref_r是左值void A(int&& ref_r) { B(ref_r); // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败 B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过 B(std::forward<int>(ref_r)); // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值} int main() { int a = 5; A(std::move(a));}例2:void change2(int&& ref_r) { ref_r = 1;} void change3(int& ref_l) { ref_l = 1;} // change的入参是右值引用// 有名字的右值引用是 左值,因此ref_r是左值void change(int&& ref_r) { change2(ref_r); // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败 change1(ref_r); // ok 表示的左值就没问题 change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过 change2(std::forward<int &&>(ref_r)); // ok,std::forward的T是右值引用类型(int &&),符合条件b,因此u(ref_r)会被转换为右值,编译通过 change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过 change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int &),符合条件a,因此u(ref_r)会被转换为左值,编译通过 // 可见,forward可以把值转换为左值或者右值} int main() { int a = 5; change(std::move(a));}7 参考链接知乎-右值引用4 三个智能指针C++11引入智能指针的概念,方便管理堆内存。使用普通指针容易造成内存泄漏,忘记释放、或者是二次释放、程序发生异常是内存泄漏问题。使用智能指针能够更好的管理堆内存;包含在头文件#include <memory>中。智能指针是RAII(resource acquire instance initalize🙆♂️)最具代表性的实现。三种指针的大小测试说明:1 shared_ptr(正常指针大小的二倍):使用引用计数,每一个shared_ptr的拷贝都指向相同的内存地方。每次使用拷贝,内部的应用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。另外,shared_ptr的内部引用计数是线程安全的,但是对象的读取是需要加锁的。初始化的时候。智能指针是个模板类,1)可以指定类型,传入指针通过构造函数初始化。2)也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,因为一个是类,一个是指针。例如std::shared_ptr p4 =new int(1),需要使用构造函数的形式。2 unique_ptr(正常指针大小):表示唯一拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义,只用移动语义来实现)。相比于原始指针,unique_ptr指针用于其RAII(Resource Acquisition is Initialization)直译过来就是,资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源。由于C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。在unique_ptr的声明周期内,可以改变其所指对象,如创建智能指针是通过构造函数指定,中间通过reset方法重新指定,通过release方法释放所有权,通过移动语义转移所有权。3 weak_ptr(正常指针大小的二倍):是一种不控制对象生命周期的智能指针,它指向shared_ptr管理的对象,weak_ptr只是提供了对管理对象的一个访问手段。引入的目的是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只能从一个shared_ptr或者另一个weak_ptr进行对象构造,它的构造和析构不会引起引用计数的增加或者减少。解决shared_ptr循环引用形成的锁,问题如下:template <typename T>class Node {public: Node(const T& value) :_pre(NULL), _next(NULL), _value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; cout << "this:" << this << endl; } shared_ptr<Node<T>> _pre; shared_ptr<Node<T>> _next; T _value;};void fun_test() { shared_ptr<Node<int>> sp1(new Node<int>(1)); shared_ptr<Node<int>> sp2(new Node<int>(2)); cout << "sp1.use_count: " << sp1.use_count() << endl; cout << "sp2.use_count: " << sp2.use_count() << endl; sp1->_next =sp2; // sp2 引用+1 cout << "sp1.use_count: " << sp1.use_count() << endl; cout << "sp2.use_count: " << sp2.use_count() << endl; sp2->_pre =sp1; // sp1 引用+1 cout << "sp1.use_count: " << sp1.use_count() << endl; cout << "sp2.use_count: " << sp2.use_count() << endl;}int main() { fun_test(); system("pause"); // 导致离开作用域的时候,两个都不会释放,造成内存泄漏 return 0;}// 运行结果Node()Node()sp1.use_count: 1sp2.use_count: 1sp1.use_count: 1sp2.use_count: 2sp1.use_count: 2sp2.use_count: 2请按任意键继续. . .在实际的编程过程,应该尽量避免出现智能指针之间相互指向的情况,如果不可避免,可以使用weak_ptr它不增加引用计数,只要出了作用域就会自动析构。在使用的过程中,需要得知该std::weak_ptr指向的资源是否有效呢?STL提供一个expired的方法来检测,返回true的话,表示该资源有效,这时,可以使用std::weak_ptr的lock函数得到一个std::shared_ptr对象后继续操作资源。// weak_sp是一个weaked_ptrif (weak_sp.expired()) { return;}std::shared_ptr<int> shared_sp =weak.sp.lock();if (shared_sp) { //}// 这是不允许进行判断的if (weak_sp) { }注意这里不能像一般指针那样去判断weak_ptr是否有效,不能直接判断资源是否有效的方式进行解决。因为,std::weak_ptr没有重写operator->和operator*,因此,该指针不能直接操作对象,也没有重写bool()操作,不能通过判断本身是否为nullptr来确定该资源是否有效。4 shared_ptr的底层实现一个简单版本template <typename T>class shared_ptr { public: // constructor shared_ptr(T* ptr =NULL) : _ptr(ptr), _pcount(new int(1)) {} // copy constructor shared_ptr(const shared_ptr& s) : _ptr(s._ptr), _pcount(s._pcount) { *(_pcount)++; } // copy assignment shared_ptr<T>& operator=(const shared_ptr& s) { if (this !=&s) { if (--(*(this->_pcount)) ==0) { delete this->_ptr; delete this->_pcount; } _ptr =s._ptr; _pcount =s._pcount; *(_pcount)++; } return *this; } // overloading operator T& operator*() { return *(this->_ptr); } T* operator->() { return this->ptr; } // destruct constructor ~shared_ptr() { --(*(this->_pcount)); if (this->_pcount ==0) { delete _ptr; _ptr =NULL; delete _pcount; _pcount =NULL; } } private: T* _ptr; // 指向内存处的地址 int* _pcount; // 指向引用计数的指针};5 延伸一个问题:智能指针的应用计数在内存部分:堆上:hamster:在shared_ptr中会有引用计数的出现,unique_ptr和weak_ptr是没有使用引用计数的一句话:为了共享这个reference count值。首先一点需要明确:如果有多个智能指针指向同一对象(内存地址),他们的reference count必须相同。rf值的更改需要“广播”给所有对应的智能指针。考虑如下代码:shared_ptr<int> p1 =make_shared(3); // rf(p1) =1;shared_ptr<int> p2(p1); // copy constructor rf(p1) =rf(p2) =2p1 =nullptr;p2 =nullptr;如果引用计数在栈区,那么当p1重新改指向nullptr时,如何通知p2的rf该减1,如果各自有各自的rf,那么就不会统一指向同一资源的rf值了。就会造成一定的内存泄漏,就乱套了如果rf在堆区,每个智能指针保存的是它在堆上的地址。那么当p1更改指向时,就调用(*rf)--,其作用就直接扩散到p2 的rf上来了,其堆内存才能正确释放。所以引用计数分配在堆上,就是使得一个智能指针更新rf时,其它指向这块内存的智能指针的rf值都能一起更新。另一种说法:因为STL的shared_ptr想设计成“可以指向任何东西”,这就意味着你必须额外为引用计数来分配空间,不然引用计数放哪?放栈上的话函数return后引用计数就废了!换个思路,我看到的一些自研游戏引擎是这样做的:做一个基类ISharedObject,这里面放引用计数。所有能被智能指针(引擎自己实现的智能指针)指向的东西都继承自这个类(也就意味着不继承自这个类的对象都不能被智能指针指向)。这样这个引用计数就被放到了object上。就不用额外new一个引用计数了。其实这是牺牲了一点通用性但是换来了性能提升(不用额外new引用计数,cache也是连续的)作者:知乎用户7Az15m链接:https://www.zhihu.com/question/290143401/answer/471490511来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。s6 网址参考C++智能指针的引用计数为什么也要在堆区申请空间?C++智能指针的底层实现原理智能指针的原理及实现
点赞 16
评论 3
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
07-30 11:43
上海交通大学 运营
理想班车开始收费了,每月上班又多花200多
在北京上班的牛友都知道,理想在遥远的顺义区,在主要的地铁口有班车给大家送到公司,没想到这么快就开始收费了,来回10块,不亏是羊毛出在羊身上😂据说如果班车堵车,算员工迟到😱
投递理想汽车等公司10个岗位
点赞
评论
收藏
分享
昨天 14:10
门头沟学院 Java
那很敬业了
点赞
评论
收藏
分享
06-28 02:37
青岛工学院 Erlang
已老实,求内推
😇
一表renzha:
手写数字识别就是一个作业而已
点赞
评论
收藏
分享
06-26 11:08
北华航天工业学院 嵌入式软件开发
已经不知道该怎么办了,是简历有问题吗😭😭
半解316:
内容充实,细节需要修改一下。 1,整体压缩为一页。所有内容顶格。 2,项目描述删除,直接写个人工作量 修改完之后还需要建议,可以私聊
点赞
评论
收藏
分享
07-28 13:47
东南大学 Java
美团日常实习面经
同门面的美团日常,在这边记录一下。
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
都是 dirty work,为什么别人的简历上就能言之有物🤔
2.7W
2
...
【07.29更新】能救一个是一个!26届毁意向毁约裁员黑名单
4494
3
...
干活最少的实习生因为长得漂亮转正了
3363
4
...
虾皮后端一面(已挂)
3242
5
...
最近是各位大佬离职回去准备秋招了嘛,鼠鼠最近投的实习居然都有回应了,基本上当天投的两天之内都能有回应,要是秋招能有这样就太好了呜呜,简单记录一下吧。7月23日:快手-平台消费(一面)1.实习介绍(干了
2948
6
...
QQ提前批一面凉经
2891
7
...
7.30百度提前批一面
2866
8
...
令人心动的offer!!!
2762
9
...
26滴滴秋招提前批Java一面
2715
10
...
27双非百度offer timeline
2268
创作者周榜
更多
正在热议
更多
#
你遇到最难的面试题目是_
#
10447次浏览
129人参与
#
分享一个让你热爱工作的瞬间
#
32578次浏览
341人参与
#
中兴秋招
#
199492次浏览
2237人参与
#
工作中哪个瞬间让你想离职
#
54984次浏览
493人参与
#
工作压力大怎么缓解
#
94286次浏览
997人参与
#
你最讨厌面试问你什么?
#
18938次浏览
222人参与
#
26届的你,投了哪些公司?
#
26895次浏览
311人参与
#
多益网络求职进展汇总
#
31723次浏览
141人参与
#
我对___祛魅了
#
35719次浏览
335人参与
#
简历上的经历如何包装
#
16196次浏览
566人参与
#
你跟室友的关系怎么样?
#
4740次浏览
79人参与
#
第一份工作应该只看薪资吗
#
159010次浏览
1565人参与
#
如何快速融入团队?
#
12873次浏览
154人参与
#
和同事相处最忌讳的是__
#
17584次浏览
177人参与
#
leader认为你工作不认真怎么办
#
34990次浏览
163人参与
#
什么样的背景能拿SSP?
#
21090次浏览
149人参与
#
机械人的金三校招总结
#
35861次浏览
461人参与
#
饿了么求职进展汇总
#
64345次浏览
636人参与
#
打工人的精神状态
#
69111次浏览
1120人参与
#
我心目中的理想工作是这样的
#
72373次浏览
847人参与
#
百度秋招提前批进度
#
118082次浏览
1392人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务