在面向对象中,多态是指通过基类的指针或引用,在运行时动态调用实际绑定对象函数的行为,与之相对应的编译时绑定函数称为静态绑定。
静态多态
静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。
动态多态
动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向父类对象时,就调用父类中定义的虚函数;当父类指针(引用)指向子类对象时,就调用子类中定义的虚函数。
同样的调用语句在实际运行时有多种不同的表现形态
要有继承关系;要有虚函数重写(被virtual声明的函数叫虚函数);要有父类指针(父类引用)指向子类对象
在类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构,虚函数表是由编译器自动生成与维护的。virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。在多态调用时,vptr指针就会根据这个对象在对应类的虚函数表中查找该调用函数,从而找到函数的入口地址。
面向过程的思想
完成一个需求的步骤。面向过程编程,就是面向具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能函数相互调用,完成需求。
面向对象思想
尽可能模拟人类的思维方式,使得软件的开发方式与过程尽可能接近人类认识世界、解决现实问题的方法和过程,把客观世界中的实体抽象为问题域中的对象。面向对象以对象为核心,该思想认为程序由一系列对象组成。
封装、继承、多态
封装:将事物属性和行为封装在一起,类,便于管理,提高代码的复用性。事物的属性和行为分别对应类中的成员变量和成员方法。
继承:继承使类与类之间产生关系,提高代码的复用性以及维护性。
多态:调用成员函数时,会根据调用方法的对象的类型来执行不同的函数。
面向过程和面向对象解决问题举例-以洗衣服为例
面向对象的方式解决问题更加简单一些,但面向对象还是基于面向过程的。
封装、继承、多态
将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现,仅对外公开接口来和对象进行交互。
C++通过private、protected、public三个关键字来控制成员变量和成员函数的访问权限,分别表示公有的、受保护的、私有的,成员访问限定符。
private修饰的成员只能在本类中访问;protected表示受保护的权限,修饰的成员只能在本类或者子类中访问;public修饰的成员是公共的,哪儿都可访问。
封装的好处:隐藏实现细节,提供公共的访问方式;提高代码的复用性;提高安全性。
代码复用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个B类继承于A类,或称从类A派生类B。类A成员基类(父类),类B成为派生类(子类)。
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过来的表现其共性,而新增的成员体现其个性。
继承的好处:提高代码的复用性;提高代码的拓展性;是多态的前提。
在面向对象中,多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。多态是指在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。
当父类指针(引用)指向父类对象时,就调用父类中定义的虚函数;当父类指针(引用)指向子类对象时,就调用子类中定义的虚函数。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性。
浅拷贝和深拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是“引用”,一般在拷贝构造函数和赋值运算符重载函数中涉及到。
浅拷贝:
值拷贝,将源对象的值拷贝到目标对象中,如果对象中有某个成员是指针类型数据,并且是在堆区创建,则使用浅拷贝仅拷贝这个指针变量的值,在目标对象中该指针类型数据和源对象中的该成员指向同一块堆空间。带来问题:在析构函数中释放该堆区数据,会被释放多次。默认的拷贝构造函数和默认的赋值运算符重载函数都是浅拷贝。
深拷贝:
深拷贝在拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,指针成员就指向了不同的内存位置,并且里面的内容是一样的,不仅达到拷贝目的,还不会出现问题,两个对象先后去调用析构函数,分别释放自己指针成员所指向的内存。
每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。
1. 智能指针实现原理
建立所有权(ownership)概念,对于特定的对象,只有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格,unique_ptr能够在编译期识别错误。
跟踪引用特定对象的智能指针计数,称为引用计数。如:赋值时,计数将加1,而指针过期时,计数将减1,仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。
2.使用场景
如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr;如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr;如果使用new[]分配内存,应选择unique_ptr;如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。
3.线程安全
shared_ptr智能指针的引用计数在手段上使用了atomic原子操作,只要shared_ptr在拷贝或赋值时增加引用,析构时减少引用就可以了。首先原子是线程安全的,所以shared_ptr智能指针在多线程下引用计数也是线程安全的。shared_ptr智能指针在多线程下传递使用时引用计数是不会有线程安全的。但是指向对象的指针不是线程安全的,使用shared_ptr智能指针访问资源不是线程安全的,需要手动加锁解锁。智能指针的拷贝也不是线程安全的。
static_assert编译时断言
新增加类型:long long,unsigned long long,char16_t,char32_t,原始字符串
auto
decltype
委托构造函数
constexpr
模板别名
alignas alignof
原子操作库
nullptr
显式转换运算符
继承构造函数
变参数模板
列表初始化
右值引用
Lambda表达式
override
final
unique_ptr、shared_ptr
初始化列表
array、unordered_map、unordered_set
线程支持库
容器并非排序的,元素的插入位置同元素的值无关,包含vector、deque、list deque list 元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树(红黑树)的方式实现,包含set、multiset、map、multimap。 queue priority_queue array、vector、deque、list、forward_list map、multimap、set、multiset unordered_map、unordered_multimap、unordered_set、unordered_multiset stack、deque、priority_queue 1、定义和性质不同 重载 重写 重载 虚函数作用 C++和C中struct的区别 struct和class的区别 各数据类型sizeof是多少 防止内存泄漏 作用 哈希冲突的原因 哈希扩容 区别 适用场景 1、导入的头文件不同 2、原理及特点 可以一起使用 原因 static是一个关键字,可以用来修饰局部变量、全局变量、成员变量、函数、和成员方法。主要作用有:限制数据的作用域、延长数据的生命周期、修饰成员可以被该类所有对象共享。 所有没有加static的全局变量和函数都具有全局可见性,其他源文件中也可以访问。被static修饰的全局变量和函数只能在当前源文件中访问,其他源文件访问不了,利用这个特性可以在不同的文件中定义同名变量和同名函数,而不必担心命名冲突。 普通的局部变量出了作用域就会释放,而静态变量存储在静态区,直到程序运行结束才会释放。 static关键字可以修饰类中成员变量和成员方法,静态成员变量和静态成员方法,静态成员拥有一块单独的存储区,不管创建多少个该类的对象,所有对象都共享这一块内存。静态成员本质上属于类,可以通过类名直接访问。 静态变量默认初始化值为0,如果没有显示初始化静态变量或者初始化为0的静态变量会存储在BSS段,而显示初始化的静态变量存储在DATA段。 未知内存、未初始化、置为nullptr 野指针 产生原因 如何避免野指针 作用、编译阶段、预处理阶段、简单替换、类型检查、内存 申请空间、拷贝数据、释放旧空间 因为vector扩容需要申请新的空间,所以扩容以后它的内存地址会发送改变。vector扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时vector都会申请比用户需求量更多的内存空间(vector的容量capacity>size),以便后期使用。 一般2倍扩容,也有1.5倍扩容。 哈希表、链地址法 但由于unordered_map容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。 底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是”链地址法(开链法)”。 C++STL标准库中,将图中的各个链表称为桶(bucket),每个桶都有自己的编号(从0开始)。当有新键值对存储到无序容器中时,整个存储过程分为如下几步: 哈希表存储结构的一个重要属性:负载因子(load factor)。该属性同样适用于无序容器,用于衡量容器存储键值对的空/满程度,负载因子越大,意味着容器越满,即各链表中挂载着越多的键值对,会降低容器查找目标键值对的效率;反之,负载因子越小,容器越空,但并不一定各个链表中挂载的键值对就越少。 如果设计的哈希函数不合理,使得各个键值对的键代入该函数得到的哈希值始终相同(所有键值对始终存储在同一链表上)。这种情况下,即便增加桶数使得负载因子减小,该容器的查找效率依旧很差。 无序容器中,负载因子的计算方式: 默认情况下,无序容器的最大负载因子为1.0。如果操作无序容器过程中,使得最大负载因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此减小负载因子。此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效。这也解释了为啥我们在操作无序容器过程中,键值对的存储顺序有时会莫名的发生变动。 所有权、private、delete unique_ptr中把拷贝构造函数和拷贝赋值运算符声明为private或delete。这样就不可以对指针指向进行拷贝了,也不会产生指向同一个对象的指针。 使用场景 Lambda表达式: 1、【外部变量访问方式说明符】捕获列表 外部变量指的是和当前lambda表达式位于同一作用域的所有局部变量。 2、(参数)参数列表 3、mutable 4、noexcept/throw() 5、 ->返回值类型 6、函数体 编译器实现Lambda表达式步骤: 概念 作用 特点 定义格式 特点 有纯虚函数的类称为抽象类,有纯虚函数的类不能实例化,派生类必须实现纯虚函数才可以实例化,否则也是抽象类。 作用 动态数组、连续存储空间、扩容 vector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问,由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢。 当vector的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么vector就需要扩容。 vector扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时vector都会申请比用户需求量更多的内存空间(capacity>size),2倍扩容也有1.5倍扩容,以便后期使用。 分段连续内存、中控器 deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。 deque是分段连续内存空间,有中央控制维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。 红黑树 map中的元素是按照二叉树(二叉查找树、二叉排序树)存储的,左子树上所有结点的键值都小于根结点的键值,右子树所有的键值都大于根节点的键值,使用中序遍历可将键值按照从大到小遍历出来。 2、各操作的时间复杂度 引用计数 shared_ptr底层是采用引用计数的方式实现的。引用计数器,用于记录多少个shared_ptr共享同一个对象。每当创建一个新的shared_ptr对象时,该计数器就会加1,当shared_ptr对象被销毁时,计数器就会减1.当计数器的值变为0时,表示没有任何shared_ptr对象引用该对象,此时shared_ptr会自动释放该对象的内存。这种机制可以避免内存泄漏和悬空指针等问题。 声明外部变量和函数、静态存储区(全局区)、BSS、DATA、C/C++混合开发 由于C++支持函数重载,编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。 C++出现之前,很多代码都是C语言写的,而且很多底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能地支持C,**extern"C"**就是其中一个策略。 红黑树、排序 set底层使用红黑树实现,一种高效的平衡检索二叉树。set容器中每一个元素就是二叉树的每一个节点,对于set容器的插入删除操作,效率都比较高,原因是二叉树的删除插入元素并不需要进行内存拷贝和内存移动,只是改变了指针的指向。 对set进行插入删除操作都不会引起迭代器的失效。因为迭代器相当于一个指针指向每一个二叉树的节点,对set的插入删除并不会改变原有内存中的节点。 set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。 shared_ptr、循环引用、weak_ptr 为了解决循环引用导致的内存泄漏,引入弱指针weak_ptr。weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但是不会指向引用计数的共享内存,但是可以检测到所管理的对象是否已经被释放,从而避免非法访问。 1、左值 3、左值引用 最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符,即不能给他赋值,但可获取其地址: 4、右值引用 5、右值引用的使用场景 使用C风格的类型转换可以把想要的任何东西转换成我们需要的类型,但是这种类型转换太过松散。C++提供了更严格的类型转换,可以提供更好的控制转换过程,并添加4个类型转换运算符,使转换过程更规范: 2、dynamic_cast动态转换 3、const_cast常量转换 4、reinterpret_cast重新解释类型转换 引用计数 变量、指针、函数参数、成员、成员方法 2、结合指针一起使用 3、const用于函数参数 const用于形参时说明形参在函数内部不能被改变,有时候函数参数传递指针或者引用,在函数内部不希望对指针和引用指向的数据进行修改,可以加上const this指针只作用在非静态成员函数的隐式形参里。 类的成员变量可以分为静态和非静态的,如果const修饰的是非静态成员变量,可以在构造函数中对该变量进行初始化;如果const修饰的是静态成员变量,则需要在类外对该变量进行初始化。 inline、函数调用开销、寻址、展开代码、提高效率、宏定义 1、内联函数和函数的区别 2、内联函数的作用 C++11提供了多种简化声明的功能,尤其在使用模板时。 防止内存泄漏 1、概念 2、作用 1、拷贝构造函数的调用时机 2、赋值操作的调用时机 C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,它对参数进行高度泛化,允许模板定义中包含0到任意个、任意类型的模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变动更加强大,能够很有效的提升灵活性。 2、可变参数类模板语法 3、展开参数包的方式 1、智能指针 2、指针 3、智能指针和普通指针的区别
vector
时间复杂度:插入o(n),查找o(1),删除o(n)
动态数组,元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。
一般2倍扩容,也有1.5倍扩容
若size>capacity,寻找一块新的空间,将原来的对象拷贝到新的内存空间。释放旧空间。内部有3个数据成员(三个指针,指向数组头部,尾指向现有元素的尾后,指向整个capacity容量大小的尾后)
扩容优化:定义元素的移动操作,采用deque(分段缓冲区)来存储
提前扩容:避免频繁扩容
迭代器失效问题
1、size>capacity,所有迭代器都失效
2、size
插入o(n) 查找o(1),删除o(n)
双向队列,元素在内存连续存放。随机存取任何元素都能在常数时间完成(仅次于vector)。在两端增删元素具有较佳的性能。
分段缓冲(分段数组),可以头部插入、尾部插入,vector存储指针,一个指针指向一个定长数组(按照哈希函数,计算元素的索引,同一个索引位置有多个元素)
插入o(1) 查找o(n),删除o(1)
双向链表,本质上是环形链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成,不支持随机存取。
插入o(logn) 查找o(logn),删除o(logn)
set中不允许相同元素,multiset中允许存在相同元素。
map中存放的元素有且有两个成员变量,first、second。map根据first值对元素从小到大排序,并可快速地根据first来检索元素。map与multimap的不同在于是否允许相同first值的元素。
unordered_set、unordered_map:哈希
插入o(1) ,最坏情况:o(n)
查找o(1) ,最坏情况:o(n),
删除o(1) ,最坏情况:o(n)
-容器适配器
封装一些基础容器,使之具备新的函数功能,包含stack、queue、priority_queue
stack
后进先出,删除、检索、修改的只能是栈顶元素
队列 插入只可以在尾部进行,删除、检索和修改只允许从头部进行,先进先出
优先队列,内部维护某种有序,然后确保优先级最高的元素总是位于头部,最高优先级元素总是第一个出列,默认大顶堆,底层默认vector8、请你说说STL中容器的类型,每种分别有哪些容器
9、请你说说指针和引用的区别
指针是一种数据类型,用于保存地址类型的数据,而引用是变量的别名。指针定义格式:数据类型 *;引用的定义格式为:数据类型&
2、引用不可以为空,当被创建的时候必须初始化,而指针变量可以是控制,在任何时候初始化
3、指针可以有多级,但引用只能是一级
4、引用使用时无需解引用,指针需要解引用
5、指针变量的值可以是NULL,而引用的值不可以是NULL
6、指针的值在初始化后可以改变,即指向其他的存储单元,而引用在进行初始化后就不会再改变
7、sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针变量本身的大小
8、指针作为函数参数传递时传递的是指针变量的值,而引用作为函数参数传递时传递的是实参本身,而不是拷贝副本。
9、指针和引用进行++运算意义不一样。10、简述一下C++的重载和重写
11、请你说说重载、重写、隐藏的区别
在同一作用域中,同名函数的形式参数(参数个数、类型或顺序)不同时,构成函数重载,与返回值类型无关
重写
派生类中与基类同返回值、同名和同参数列表的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
隐藏
12、简述一下虚函数的实现原理
实现动态多态机制。父类指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,让父类的指针有”多种形态“。泛型技术
虚函数实现原理
编译器处理虚函数时,给每个对象添加一个隐藏成员(一个指针类型的数据),指向的是函数地址数组,这个数组被称为虚函数表。虚函数表中存储的是类中虚函数的地址。如果派生类重写了基类中的虚函数,则派生类对象的虚函数表中保存的是派生类的虚函数地址。如果派生类没有重写基类中的虚函数,则派生类对象的虚函数表中保存的是父类的虚函数地址。
使用虚函数时,对于内存和执行速度方面会有一定的成本:13、说一说C++和C中struct的区别以及和class的区别
14、请你说说各数据类型sizeof是多少,sizeof指针是多少,sizeof原理
bool 1 char 1 short 2 int 4 long 4 long long 8
sizeof指针是多少
32位平台下是4字节,在64位平台下是8字节
sizeof原理
sizeof是在编译的时候,查找符号表,判断类型,然后根据基础类型来取值。如果sizeof运算符的参数是一个不定长数组,则还需要在运行时计算数组长度。15、为什么将析构函数设置成虚函数
概念
虚析构函数,将基类的析构函数声明为virtualclass Base{
public:
Base(){}
virtual ~Base(){}
}
防止内存泄漏。如果基类中的构造函数没有声明为虚函数,基类指针指向派生类对象时,则当基类指针释放时不会调用派生类对象的析构函数,而是调用基类的析构函数,如果派生类析构函数中做了某些释放资源的操作,则这时会造成内存泄漏。16、请你说说导致哈希冲突的原因和影响因素,哈希冲突的解决方法
按照哈希函数,计算元素的索引,同一索引位置有多个元素
哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值,产生哈希冲突。
产生哈希冲突的影响因素
装填因子(数据总数/哈希表长)、哈希函数、处理冲突的方法
哈希冲突的解决方法
哈希表维护元素过多,链表长度/维护数组长度>1需进行哈希扩容,一般也是两倍扩容,4条链表->8条链表,将元素重新用哈希函数计算索引,重新放在相应链表中进行管理17、说一说vector和list的区别,分别适用于什么场景
18、请你说说map、unordered_map的区别
#include //map
#include
19、请你说说volatile可以和const同时使用嘛
20、请你说说红黑树的特性,为什么要有红黑树
平衡树解决了二叉查找树退化为近似链表的特点,能够把查找时间控制在o(logn),不过也不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入和删除节点的时候,几乎都会破坏平衡树的第2个规则,我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。如果在插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,有了红黑树。
红黑树特点21、说一说static关键字的作用
静态成员函数不能访问普通成员变量,只能访问静态成员变量,并且在静态成员函数中没有this指针。22、说一说什么是野指针,怎么产生的,如何避免
野指针是指指向的位置是随机的、不可知的、不正确的23、说说const和define的区别
const在C语言中表示只读,编译器禁止对它修饰的变量进行修改,在C++中增加了常量的语义。而define用于定义宏,而宏也可以用于定义常量。
区别:24、请你说说vector和扩容机制,扩容以后,它的内存地址会变化吗
当vector的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么vector就需要扩容。vector容器扩容的过程分为3步:
扩容优化:定义元素的移动操作,采用deque(分段缓冲区)来存储
提前扩容:避免频繁扩容25、请你说说unordered_map实现原理
unordered_map容器和map容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。
当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。
负载因子=容器存储的总键值对/桶数26、请你说说unique_ptr的实现原理及使用场景
实现原理
建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。27、请你说说C++Lambda表达式用法及实现原理
sort(v.begin(),v.end(),[](int a,int b)->bool{return a<b;});
[外部变量访问方式说明符](参数)mutable noexcept/throw() ->返回值类型{函数体;}
[]方括号用于向编译器表明当前是一个Lambda表达式,其不能被省略。在方括号内部,注明当前Lambda函数的函数体可以使用哪些“外部变量”。
[外部变量]的定义方式:
和普通函数的定义一样,Lambda匿名函数也可以接收外部传递的多个参数,和普通函数不同的是,如果不需要传递参数,可以连同()小括号一起省略
此关键字可以省略,如果使用则之前的()小括号将不能省略(参数个数可以为0)。默认情况下,对于以值传递方式引入的外部变量,不允许在Lambda表达式内部修改它们的值(可以理解为这部分变量都是const变量)。而如果想修改它们,就必须使用mutable关键字。对于以值传递方式引入的外部变量,Lambda表达式修改的是拷贝的那一份,并不会修改真正的外部变量。
可以省略,如果使用,在这之前的()小括号将不能省略(参数个数可以为0)。默认情况下,Lambda函数的函数体可以抛出任何类型的异常。而标注noexcept关键字,则表示函数体内不会抛出任何异常;使用throw()可以指定Lambda函数内部可以抛出的异常类型。
指明Lambda匿名函数的返回值类型。如果Lambda函数体只有一个return语句,或者该函数返回void,则编译器可以自行推断返回值类型,此情况下可以直接省略->返回值类型
和普通函数一样,Lambda匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量28、什么是纯虚函数,有什么作用
纯虚函数是一种特殊的虚函数,它的格式是:虚函数不给出具体的实现,也就是后面没有大括号实现体,而在后面加上“=0”class 类名{
virtual 返回值类型 函数名(参数列表)=0;
};
很多情况下,在基类中不能对虚函数给出具体的有意义的实现,就可以把它声明为纯虚函数,它的实现留给该基类的派生类去做。
例如猫类和狗类的基类是动物类,动物类中有一个吃饭的函数eat(),那这个eat()函数可以是纯虚函数,因为并不能够确定动物吃的东西是什么,具体吃的内容由不同的派生类去实现。
如果一个类中有纯虚函数,那么这个类也被称为抽象类。这个类不能实例化对象,也就是不能创建该类的对象。除非在派生类中完全实现基类中的所有纯虚函数,否则派生类也是抽线类,不能实例化对象。29、请你说说虚函数和纯虚函数的区别
//虚函数
virtual 返回值类型 函数名(参数列表){
//函数体
}
//纯虚函数
virtual 返回值类型 函数名(参数列表)=0;
虚函数可以有具体的实现,纯虚函数没有具体的实现。对于虚函数来说,父类和子类都有各自的版本,由多态方式调用的时候动态绑定。
虚函数是C++中用于实现动态多态的机制。很多情况下,在基类中不能对虚函数给出具体的有意义的实现,就可以把它声明为纯虚函数,它的实现留给该基类的派生类去做。30、简述vector的实现原理
vector容器扩容的过程需要经历以下步骤:31、请你说说deque的实现原理
deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或尾端。
deque采用一小块连续的内存空间作为主控,其中每一个元素(结点)都是一个指针,指向另一段连续内存空间(缓存区),缓冲区才是deque的存储空间的主体。
32、请你说说map实现原理,各操作的时间复杂度是多少
1、map实现原理
map内部实现了一个红黑树(红黑树是非严格平衡的二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树有自动排序功能,因此map内部所有元素都是有序的,红黑树的每一个结点都代表着map的一个元素。因此对map进行的查找、删除、添加等一系列的操作相当于是对红黑树进行的操作。
插入、查找、删除的时间复杂度均为:o(logn)33、shared_ptr怎么知道跟它共享对象的指针释放了
34、请你说说extern的作用,extern变量在哪个数据段,为什么要extern C
35、请你说说set的实现原理
不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。
不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。36、请你回答一下智能指针有没有内存泄漏的情况
当两个类对象中各自有一个shared_ptr指向对方时,会造成循环引用,使引用计数失效,从而导致内存泄漏。37、请你说说左值、右值、左值引用、右值引用的使用场景
C++中可以取地址的、有名字的就是左值
2、右值
不能取地址的、没有名字的就是右值int a=10;//a就是左值,10就是右值
对一个左值进行引用。
传统的C++引用(左值引用)使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或解除引用的指针),程序可以获取其地址。int n;
int *pt=new int;
const int b=101;
int &rn=n;
int &rt=*pt;
const int &rb=10;
右值引用就是对一个右值进行引用
C++11新增了右值引用(rvalue reference)。这种引用可指向右值(即出现在赋值表达式右边的值),但不能对其应用地址运算符。右值包括字面常量、诸如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用),右值引用使用&&声明int x=10;
int y=23;
int &&r1=13;
int &&r2=x+y;
double &&r3=sqrt(2.0);
右值引用可以实现移动语义、完美转发38、说说C语言和C++语言的区别
39、简述一下C++中的4种类型转换
1、static_cast静态转换40、weak_ptr如何解决shared_ptr的循环引用问题
41、请你说说const的用法
1、用在变量身上
表示变量只读,不能对它的值进行修改const int a=10;
a=20;//编译会报错,因为a只读,不能对它进行修改
const int* p;//底层cosnt,表示指针变量p所指向的内容不能修改,指针变量p的内容可以修改
int * const p;//顶层const,表示指针变量p的指向不能修改,指针变量p所指向的内容可以修改
const int * const p;//指针p指向和所指向的内容都不可以修改
void foo(const int* p);
void foo(const int& p);
4、在类中修饰成员方法
防止在方法中修改非static成员class A{
public:
int a;
void func()const{
a=20;//错误,const修饰的成员方法中不能修改非静态成员变量
}
};
5、const修饰类的成员变量class T{
public:
T():a(10);
private:
const int a;
static const int b;
};
const int T::b=20;
42、请你说说C++引用的概念
typename &ref=varname;
43、说说内联函数和函数的区别,内联函数的作用
因为函数调用时需要创建时间、参数传入等操作,造成时间和空间的额外开销。通过编译器预处理,在调用内联函数的地方将内联函数内的语句复制到调用函数的地方,也就是直接展开代码执行,从而提高了效率,减少了一些不必要的开销。同时内联函数还能解决宏定义的问题。44、请你说说虚函数可以是内联函数嘛
45、请你说说迭代器失效原因,有哪些情况
46、请你说说auto和decltype如何使用?
auto a=12;
auto pt=&a;
double fm(double a,int b){
return a+b;
}
auto pf=fm;
//简化模板声明
for(std::initializer_list<double>::iterator p=il.begin();p!=il.end();p++)
for(auto p=il.begin();p!=il.end();p++)
decltype(expression) var;
decltype(x) y;//让y的类型和x相同,x是一个表达式
double x;
int n;
decltype(x*n) q;
decltype(&x) pd;
template<typename t="">
void ef(T t,U u){
decltype(T*U) tu;
}
</typename></double>
47、虚析构函数有什么作用
虚析构函数,是将基类的析构函数声明为virtualclass Base{
public:
Base(){}
//虚析构函数
virtual ~Base(){}
};
48、请你说说什么情况下会调用拷贝构造,什么时候会调用赋值操作
49、简述一下C++11中可变参数模板新特性
1、可变参数函数模板语法
模板参数中,typename后跟…表明T是一个可变模板参数,它可以接收多种数据类型,称为模板参数包。template<typename ...t="">
//func()函数中,args参数的类型用T...表示,表示args参数可以接收任意个参数,函数参数包
void fun(T ...args){
//函数体
}
template<typename ...types="">
class test;
50、说说C++智能指针和指针的区别