目录

1:封装继承多态

2:多态的实现原理以及优点

3:虚函数是怎么实现的,什么时候生成

4:父类的构造函数和析构函数是否可以成为虚函数

5:讲一下面向过程和面向对象

6:成员变量的初始化顺序

7:空类中有什么函数

8:空类中的大小是多少

9:移动构造和拷贝构造的区别是什么

10:类中的static函数能否被声明为虚函数?

11:那些函数不能被声明为虚函数

12:什么是虚基类

13:类内普通成员函数可以调用类内静态变量吗,类内静态成员函数可以访问类内普通变量吗?

14:什么是this指针,为什么需要this指针?

15:什么是重载、重写、隐藏?

16:final标识符的作用是什么?

17:指针是什么

18:智能指针的本质是什么,他们的实现原理是什么

19:make_shared函数的优点,缺点?

20:weak_ptr真的不计数?是否有计数方式,在哪分配的空间。

21:传入一个指针,它如何确定具体要清理多少空间呢?

22:shared_ptr线程安全吗?

23:malloc和new的区别是什么?

24:weak_ptr是怎么实现的?

25:匿名函数的本质是什么?他的优点是什么?

26:右值引用是什么,为什么要引入右值引用,完美转发

27:左值引用和指针的区别?

28:malloc的内存分配的方式,有什么缺点?

29:内存对齐是什么?为什么要进行内存对齐?内存对齐有什么好处?

30:++i是左值还是右值,++i和i++哪个效率更高?

31:指针占用的大小是多少?

32:野指针和内存泄漏是什么?如何避免?

33:对比迭代器和指针的区别

34:介绍一下vector、list的底层实现原理和优缺点

35:push_back()左值和右值的区别是什么?emplace_back()?

36:map和unordered_map

37:为什么会有栈溢出,为什么栈会设置容量?

38:二叉树和平衡二叉树的区别

39:define和const的区别是什么?

40:动态链接和静态链接的区别,动态链接的原理是什么?

41:C++11中的auto是怎么实现自动识别类型的?模板是怎样实现转化成不同类型的?

42:讲一讲迭代器失效及其解决方法

43:回调函数是什么,为什么要有回调函数?有什么优缺点?回调的本质是什么?


1:封装继承多态

封装:将具体实现过程和数据封装成一个类,只能通过接口进行访问,降低耦合性,使类成为一个具有内部数据的自我隐藏能力、功能独立的软件模块。

意义:保护或防止代码在无意之中被破坏,保护类中的成员,不让类中以外的程序直接访问或者修改,只能通过提供的公共接口访问。

继承:子类继承父类的特征和行为,复用了基类的全体数据和成员函数,具有从基类复制而来的数据成员和成员函数(基类私有成员可被继承,但是无法被访问),其中构造函数、析构函数、友元函数、静态数据成员、静态成员函数都不能被继承。基类中成员的访问方式只能决定派生类能否访问它们。增强了代码耦合性,当父类中的成员变量或者类本身被final关键字修饰时,修饰的类不能被继承,修饰的成员函数不能重写或修改。

意义:基类的程序代码可以被派生类服用,提高了软件复用的效率,缩短了软件开发的周期

多态:不同继承类的对象对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现形式。

意义:对已存在的代码具有可替代性,对代码具有可扩充性,新增子类不会影响已存在类的各种性质,在程序中体现了灵活多样的操作,提高了使用效率,简化了对应用代码的编写和修改过程。

class与struct的区别:默认继承权限不同:class默认继承的是private继承,struct默认是public继承。

Class还可用于定义模板参数,但是关键字struct不能同于定义模板参数,C++保留struct关键字,原因是保证与C语言的向下兼容性,为了保证百分百的与C语言中的struct向下兼容,C++把最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性的限制。

2:多态的实现原理以及优点

实现方式:多态分为动态多态(动态多态是利用虚函数实现运行时的多态,即在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数。)和静态多态(又称编译期多态,即在系统编译期间就可以确定程序将要执行哪个函数),其中动态多态是通过虚函数实现的,虚函数是类的成员函数,存在存储虚函数指针的表叫做虚函数表,虚函数表是一个存储类成员虚函数的指针,每个指针都指向调用它的地方,当子类调用虚函数时,就会去虚表里面找自己对应的函数指针,从而实现“谁调用、实现谁”从而实现多态。而静态多态则是通过函数重载(函数名相同,参数不同,两个函数在同一作用域),运算符重载,和重定义(又叫隐藏,指的是在继承关系中,子类实现了一个和父类名字一样的函数,(只关注函数名,和参数与返回值无关)这样的话子类的函数就把父类的同名函数隐藏了。隐藏只与函数名有关,与参数没有关系.)来实现的。

优点:加强代码的可扩展性,可替换性,增强程序的灵活性,提高使用效率,简化对应用代码的编写和修改过程。

3:虚函数是怎么实现的,什么时候生成

在C++中,虚函数的实现原理基于两个关键概念:虚函数表和虚函数指针

虚函数表:每个包含虚函数的类都会生成一个虚函数表,其中存储着该类中所有虚函数的地址。虚函数表是一个由指针构成的数组,每个指针指向一个虚函数的实现代码。

虚函数指针:在对象的内存布局中,编译器会添加一个额外的指针,称为虚函数指针或虚表指针。这个指针指向该对象对应的虚函数表,从而让程序能够动态的调用虚函数。

当一个基类指针或引用调用虚函数时,编译器会使用虚表指针来查找该对象对应的虚函数表,并根据函数在虚函数表中的位置来调用正确的虚函数。

虚函数表在编译阶段生成,虚函数和普通函数一样存放在代码段,只是它的指针又存放在了虚表之中。

4:父类的构造函数和析构函数是否可以成为虚函数

构造函数不能为虚函数,虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的vptr指针指向,该指针存放在对象的内部空间之中,需要调用构造函数完成初始化,如果构造函数为虚函数,那么调用构造函数就需要去寻找vptr,但此时vptr还没有完成初始化,导致无法构造对象。

析构函数可以且经常为虚函数:当我们使用父类指针指向子类时,只会调用父类的析构函数,子类的析构函数不会被调用,容易造成内存泄漏。

5:讲一下面向过程和面向对象

面向对象:就是将问题分解为各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为,相比面向过程,代码更易维护和复用。但是代码效率相对较低。

面向过程:就是将问题分析出解决问题的步骤,然后将这些步骤一步一步的实现,使用的时候一个一个调用就好。代码效率更高但是代码复用率低,不易维护。

6:成员变量的初始化顺序

成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。类中const成员常量必须在构造函数初始化列表中初始化。类中static成员变量,只能在类外初始化。

顺序:基类的静态变量或全局变量,派生类的静态变量或者全局变量,基类的成员变量,派生类的成员变量。

7:空类中有什么函数

默认构造函数、默认拷贝构造函数、默认析构函数、默认赋值运算、符取值运算符、const取值运算符

8:空类中的大小是多少

一字节

9:移动构造和拷贝构造的区别是什么

移动构造函数本质上是基于指针的拷贝,实现对堆区内存所有权的移交,在一些特定场景下,可以减少不必要的拷贝。比如用一个临时对象或者右值对象初始化类实例时。我们可以使用move()函数,将一个左值对象转变为右值对象。

而拷贝构造则是将传入的对象复制一份然后放进新的内存中

10:类中的static函数能否被声明为虚函数?

不能,因为类中的static函数是所有类实例化对象所共有的,没有this指针,而虚函数依靠vptr和vtable来处理,vptr是一个指针,在类中的构造函数中生成,并且只能通过this指针访问,对于静态成员函数来说,他没有this指针,无法访问vptr,因此static函数无法声明为虚函数

11:那些函数不能被声明为虚函数

构造函数,内联函数(内联函数有实体,在编译时展开,没有this指针),静态成员函数,友元函数(C++不支持友元函数的继承),非类成员函数

12:什么是虚基类

虚基类是 C++ 中一种特殊的类,用于解决多继承所带来的“菱形继承”问题。如果一个派生类同时从两个基类派生,而这两个基类又共同继承自同一个虚基类,就会形成一个“菱形”继承结构,导致派生类中存在两份共同继承的虚基类的实例,从而引发一系列的问题。

为了解决这个问题,我们可以将虚基类作为共同基类,并在派生类中采用虚继承的方式。

虚继承会使得派生类中只存在一份共同继承的虚基类的实例,从而避免了多个实例之间的冲突。虚基类是可以被实例化的。

13:类内普通成员函数可以调用类内静态变量吗,类内静态成员函数可以访问类内普通变量吗?

类内普通成员函数可以调用类内静态变量,因为类内静态变量在编译时就已经完成了初始化和内存分配,类内普通函数调用类内静态变量说明类已经完成实例化,所以可以调用。静态函数可以直接访问静态变量,静态函数不能直接访问非静态变量,但是可以通过将类实例化对象后,静态函数去访问对象的非静态成员变量。

14:什么是this指针,为什么需要this指针?

类和对象中的成员函数存储在公共的代码段,不同的对象调用成员函数时编译器为了知道具体操作的是哪一个对象给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象,在函数体中所有成员变量的操作,都是通过这个指针来完成的由编译器自动完成。

15:什么是重载、重写、隐藏?

重载:函数名相同,函数参数不同,两个函数在同一作用域

重写:两个函数分别在子类和父类中,函数名,返回值,参数均相同,函数必须为虚函数

隐藏:在继承关系中,子类实现了一个和父类名字名字一样的函数。这样子类的函数就把父类的同名函数隐藏了。隐藏只与函数名有关。

16:final标识符的作用是什么?

放在类的后面表示该类无法被继承,也就是阻止了从类的继承,放在虚函数后面该虚函数无法被重写,表示阻止虚函数的重载

17:指针是什么

指针全名为指针变量,计算机在存储数据是有序存放的,为了能够使用存放的地址,就需要一个地址来区别每个数据的位置,指针变量就是用来存放这些地址的变量。

18:智能指针的本质是什么,他们的实现原理是什么

智能指针本质是一个封装了一个原始C++指针的类模板,为了确保动态内存的安全性而产生的。

实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。

19:make_shared函数的优点,缺点?

优点:减少了内存分配的次数,降低了系统开销,提高了效率,使用new构造的话至少会进行两次内存分配,(一次为智能指针本身,一次为共享指针的控制块)

缺点:当构造函数是保护或者私有的时候无法使用make_shared函数。

会导致weak_ptr保持控制块的生命周期,连带着保持了对象分配的内存,只有当最后一个weakptr离开作用域时,内存才会被释放,对于内存要求高的场景来说,是一个需要注意的问题。

20:weak_ptr真的不计数?是否有计数方式,在哪分配的空间。

计数,控制块中有强弱引用计数,如果是使用make_shared初始化的函数则它所在的控制块空间是在所引用的shared_ptr中同一块的空间,若是new则控制器所分配的内存与shared_ptr本身所在的空间不在同一块内存。

21:传入一个指针,它如何确定具体要清理多少空间呢?

我们在申请内存的时候,会多分配16字节的内存,里面保存了内存块的详细信息,free会对传入的内存地址向左偏移16字节,然后分析出当前内存块的大小,就知道要释放多大的内存空间了。

22:shared_ptr线程安全吗?

智能指针中的引用计数是线程安全的,但是智能指针所指向的对象的线程安全问题,智能指针没有做任何保障,线程不安全。也就是说它所管理的资源可以线程安全的释放,只保证线程安全的管理资源的生命期,不保证其资源可以线程安全地被访问。

23:malloc和new的区别是什么?

Malloc/free是标准库函数,new/delete是C++运算符

Malloc分配内存失败返回空,new失败抛异常

New/delete会调用构造析构函数,malloc/free不会,所以他们无法满足动态对象的要求。

New返回有类型的指针,malloc返回无类型的指针

分配内存的位置:malloc从堆上动态分配内存,new是从自由存储区为对象动态分配内存(取决于operator new的实现,可以为堆还可以是静态存储区)

New申请内存的步骤:调用operator new函数,分配一块足够大,且原始的,未命名的内存空间来存储特定类型的对象。运行相应的构造函数来构造对象,并为其传入初值,返回一个指向该对象的指针。

Delete:先调用对象的析构函数,再调用operator delete函数释放内存空间

24:weak_ptr是怎么实现的?

实现依赖于计数器和寄存器实现的,计数器用来记录弱引用的数量,寄存器用来存储shared_ptr

25:匿名函数的本质是什么?他的优点是什么?

匿名函数本质上是一个对象,在其定义的过程中会创建出一个栈对象,内部通过重载()符号实现函数调用的外表。

优点:使用匿名函数,可以免去函数的声明和定义。这样匿名函数仅在调用函数的时候才会创建函数对象,而调用结束后立即释放,所以匿名函数比非匿名函数更节省空间。

26:右值引用是什么,为什么要引入右值引用,完美转发

右值引用是为一个临时变量取别名,它只能绑定到一个临时变量或表达式(将亡值)上。实际开发中我们可能需要对右值进行修改(实现移动语义时就需要)而右值引用可以对右值进行修改。

为什么:

1.为了支持移动语义,右值引用可以绑定到临时对象、表达式等右值上,这些右值在生命周期结束后就会被销毁,因此可以在右值引用中窃取其资源,从而避免昂贵的复制操作,实现高效的移动语义。

2.完美转发:右值引用可以绑定到任何类型的右值上,可以将其作为参数传递给函数,并在函数内部将其“转发”到其他函数中,从而实现完美转发。

3.拓展可变参数模板,实现更加灵活的模板编程。

移动语义:Move的功能是将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义,从实现原理上讲基本等同一个强制类型转换。

优点:可以将左值变成右值而避免拷贝构造,将对象的状态所有权从一个对象转移到另一个对象,只是转移,没有内存搬迁或者内存拷贝。

完美转发是指函数模板可以将自己的参数完美的转发给内部调用的其他函数,完美是指不仅能够准确的转发参数的值,还能保证被转发参数的左、右值属性不变,使用引用折叠的规则,将传递进来的左值以左值传递出来,将传递进来的右值以右值的方式传出。

27:左值引用和指针的区别?

是否初始化:指针可以不用初始化,引用必须初始化

性质不同:指针是一个变量,引用是对被引用的对象取一个别名

占用内存单元不同:指针有自己的空间地址,引用和被引用对象占同一个空间。

28:malloc的内存分配的方式,有什么缺点?

malloc并不是系统调用,而是C库中的函数,用于动态内存分配,在使用malloc分配内存的时候会有两种方式向操作系统申请堆内存

方式1:当用户分配的内存小于128KB时通过brk()系统调用从堆分配内存,实现方式:将堆顶指针向高地址移动,获取内存空间,如果使用free释放空间,并不会将内存归还给操作系统,而是会缓存在malloc的内存池中,待下次使用

方式2:当用户分配的内存大于128KB时通过mmap()系统调用在文件映射区域分配内存,实现方式为:使用私有匿名映射的方式,在文件映射区分配一块内存,也就是从文件映射区拿了一块内存,free释放内存的时候,会把内存归还给操作系统,内存得到真正释放

缺点:容易造成内存泄漏和过多的内存碎片,影响系统正常运行,还得注意判断内存是否分配成功,而且内存释放后(使用free函数之后指针变量p本身保存的地址并没有改变),需要将p的赋值为NULL拴住野指针。

为什么不全部都用brk

如果全部使用brk申请内存那么随着程序频繁的调用malloc和free,尤其是小块内存,堆内将产生越来越多的不可用的内存碎片。

为什么不全部使用mmap来分配内存?

因为向操作系统申请内存的时候,是要通过系统调用的,执行系统调用要进入内核态,然后再回到用户态,状态的切换会耗费不少时间,所以申请内存的操作应该避免频繁的系统调用,如果都使用mmap来分配内存,等于每次都要执行系统调用。另外,因为mmap分配的内存每次释放的时候都会归还给操作系统,于是每次mmap分配的虚拟地址都是缺页状态,然后在第一次访问该虚拟地址的时候就会触发缺页中断。

29:内存对齐是什么?为什么要进行内存对齐?内存对齐有什么好处?

内存对齐是处理器为了提高处理性能而对存取数据的起始地址所提出的一种要求。

有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定的地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时将进行对齐,这就具有平台的移植性。CPU每次寻址有时需要消耗时间的,并且CPU访问内存的时候并不是逐个字节访问,而是以字长为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐内存,处理器需要做多次内存访问,而对齐的内存访问可以减少访问次数,提升性能。

优:提高程序的运行效率,增强程序的可移植性。

30:++i是左值还是右值,++i和i++哪个效率更高?

++i是左值,因为++i返回的是一个左值没有发生拷贝,所以效率更高。(++i是左值,i++是右值。因为++i返回i本身,而i++返回i的值。)

31:指针占用的大小是多少?

64位电脑上占8字节,32位的占4字节,我们平时所说的计算机多少位是指计算机CPU中通用寄存器一次性处理、传输、暂时保存的信息的最大长度。即CPU在单位时间内能一次处理的二进制的位数,因此CPU所能访问的内存所有地址由多少位组成,而8比特位表示1字节,就可以得出在不同位数的机器中指针的大小。

32:野指针和内存泄漏是什么?如何避免?

内存泄漏:是指程序中以动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

避免:使用智能指针管理资源,在释放对象数组时使用delete[],尽量避免在堆上分配内存

野指针:指向一个已删除的对象或未申请访问受限内存区域的指针。

避免:对指针进行初始化,用已合法的可访问内存地址对指针初始化,指针用完释放内存,将指针赋值nullptr。

33:对比迭代器和指针的区别

迭代器不是指针,是一个模板类,通过重载了指针的一些操作符模拟了指针的一些功能,迭代器返回的是对象引用而不是对象的值。

指针能够指向函数而迭代器不行迭代器只能指向容器。

34:介绍一下vector、list的底层实现原理和优缺点

Vector优点:可使用下标随机访问,尾插尾删效率高。

缺点:前面部分的插入删除效率低,扩容有消耗,可能存在一定的空间浪费。

底层是由一块连续的内存空间组成,由三个指针实现的分别是头指针(表示目前使用空间的头),尾指针(表示目前使用空间的尾)和可用空间尾指针实现

List优点:按需申请内存,不需要扩容,不会造成内存空间浪费。在任意位置的插入删除下效率高。

缺点:不支持下标随机访问

底层是由双向链表实现的

35:push_back()左值和右值的区别是什么?emplace_back()?

如果push_back()的参数是左值,则使用它拷贝构造新对象,如果是右值,则使用它移动构造新对象。

当使用Push_back时会先调用类的有参构造函数创建一个临时变量,再将这个元素拷贝或者移动到容器之中,而emplace_back则是直接在容器尾部进行构造比push_back少进行一次构造函数调用。在大部分场景中emplace_back可以替换push_back,但是push_back会比emplace_back更加安全,emplace_back只能用于直接在容器中构造新元素的情况,如果要将现有的对象添加到容器中则需要使用push_back

使用resize改变的是vector的大小(size),可能会添加或删除元素。
使用reserve改变的是vector的容量(capacity),不会改变当前元素的数量,仅仅是为了优化内存使用和性能。

当vector内存不够时本身内存会以1.5或者2倍的增长,以减少扩容次数

由当前容器内元素数量的大小和容器最大大小进行比较如果二者相等就会进行扩容,一般是1.5倍,部分的有两倍

引入了reserve,自定义vector最大容量

36:map和unordered_map

Map内部实现是一个红黑树,内部所有的元素都是有序的,而hashmap则是内部实现了一个哈希表,内部存储元素是无序的

Map优点:有序性,其次是内部实现的是一个红黑树,使得很多操作都可以在logn的复杂度下可以实现效率较高。

Map缺点:空间占用率高

Unorderedmap优点:查找效率非常高。

缺点:哈希表的建立比较费时间

unordered_map的扩容过程(可以参考redis)

当unordered_map中的元素数量达到桶的负载因子(0.75)时,会重新分配桶的数量(通常会按照原有桶的数量*2的方式进行扩容,但是具体的增长策略也可以通过修改容器中的max_load_factor成员变量来进行调整),并将所有的元素重新哈希到新的桶中。

map和set的区别和底层实现是什么?map取值的 find,[],at方法的区别(at有越界检查功能)
都是红黑树,find查找需要判断返回的结果才知道有没有查询成功。[]不管有没有就是0,如果原先不存在该key,则插入,如果存在则覆盖插入,at方法则会进行越界检查,这会损失性能,如果存在则返回它的值,如果不存在则抛出异常。

37:为什么会有栈溢出,为什么栈会设置容量?

栈空间是预设的,它通常用于存放临时变量,如果你在函数内部定义一个局部变量,空间超出了设置的栈空间大小,就会溢出。不仅如此,如果函数嵌套太多,也会发生栈溢出,因为函数没有结束前,函数占用的变量也不被释放,占用了栈空间。

原因:是栈的地址空间必须连续,如果任其任意成长,会给内存管理带来困难。对于多线程程序来说,每个线程都必须分配一个栈,因此没办法让默认值太大。 

38:二叉树和平衡二叉树的区别

二叉树没有平衡因子的限制,而平衡二叉树有。

二叉树可能退化为链表,而平衡二叉树不会。

平衡二叉树的优缺点

优点:避免了二叉排序树可能出现最极端情况(退化为链表),其平均查找的时间复杂度为logN

缺点:对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

39:define和const的区别是什么?

编译阶段:define是在编译预处理阶段进行简单的文本替换,const是在编译阶段确定其值

安全性:define定义的宏常量没有数据类型,只是进行简单的替换,不会进行类型安全检查;const定义的常量是有类型的,是要进行类型判断的

内存占用:define定义的宏常量,在程序中使用多少次就会进行多少次替换,内存中有多个备份,占用的是代码段的内存;const定义常量占用静态存储区域的空间,程序运行过程中只有一份

调试:define定义的宏常量不能调试,因为在预编译阶段就已经进行替换了;const定义的常量是可以进行调试的。

40:动态链接和静态链接的区别,动态链接的原理是什么?

区别:他们的最大区别就是在于链接的时机不同,静态链接是在形成可执行程序前,而动态链接的进行则是程序执行时。

静态库:就是将库中的代码包含到自己的程序之中,每个程序链接静态库后,都会包含一份独立的代码,当程序运行起来时,所有这些重复的代码都需要占用独立的存储空间,显然很浪费计算机资源。

动态库:不会将代码直接复制到自己程序中,只会留下调用接口,程序运行时再去将动态库加载到内存中,所有程序只会共享这一份动态库,因此动态库也被称为共享库。

动态链接原理:是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件

41:C++11中的auto是怎么实现自动识别类型的?模板是怎样实现转化成不同类型的?

auto仅仅只是一个占位符,在编译期间它会被真正的类型替代,或者说C++中变量必须要有明确类型的,只是这个类型是由编译器自己推导出来的。函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式具体类型函数的模具,所以模板其实就是将原本应该我们做重复的事情交给了编译器。

42:讲一讲迭代器失效及其解决方法

序列式容器迭代器失效:当当前元素的迭代器被删除后,后面所有元素的迭代器都会失效,他们都是一块连续存储的空间,所以当使用erase函数操作时,其后的每一个元素都会向前移动一个位置,此时可以使用erase函数操作可以返回下一个有效的迭代器。

Vector迭代器失效问题总结:

1.当执行了erase方法时,指向删除节点的迭代器全部失效,指向删除节点之后的全部迭代器也失效。

2.当进行push_back方法时,end操作返回的迭代器肯定失效。

3.当插入一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操作返回的迭代器失效。

4.当插入一个元素后,如果空间未重新分配,指向插入位置之前的元素的迭代器依然有效,但指向插入元素之后元素的迭代器全部失效。

Deque迭代器失效总结:

1.对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用都会失效,如果在首尾位置添加元素,迭代器会失效,但是指针和引用不会失效。

2.如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器都会失效。

3.如果在其首部和尾部删除元素则只会使指向被删除元素的迭代器失效。

关联型容器迭代器失效:删除当前的迭代器,仅仅会使当前的迭代器失效,只要erase时,递增当前迭代器即可。

43:回调函数是什么,为什么要有回调函数?有什么优缺点?回调的本质是什么?

回调函数是指使用者自己定义一个函数,实现这个函数的程序内容,然后别人把这个函数(入口地址)作为参数传入别人的函数中,由别人的函数在运行时来调用的函数,简单说就是放发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。

可以把调用者和被调用者分开。调用者不关心谁是被调用者,所以它只需要知道的,只是一个存在某种特定类型原型,某些限制条件的被调用函数。

优点:

可以让实现方根据回调方的多种形态进行不同的处理和操作

可以让实现方,根据自己的需要定制回调方的不同形态

可以将耗时的操作隐藏在回调方,不影响实现方其他信息的展示。

让代码的逻辑更加集中,更加易读。

缺点:

回调函数过多会导致代码难以维护

回调函数容易造成资源竞争:如果回调函数中有共享资源访问,容易出现资源争抢,导致程序出错

代码可读性差,可能会破坏代码的结构和可读性

本质:是将函数当作参数使用,目的是为了使程序更加普适。

本篇文章是对此篇文章的一个整理,本人在看的时候,觉得有点分散,于是将一些大类按着顺序保存了下来,阅读起来会方便一点。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐