学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

682

积分

0

好友

85

主题
发表于 前天 14:01 | 查看: 43| 回复: 0
1.讲一讲封装、继承、多态是什么?
封装:将具体实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性,使类成为一个具有内部数据的自我隐藏能力、功能独立的软件模块。意义:保护或防止代码在无意之中被破坏,保护类中的成员,不让类中以外的程序直接访问或者修改,只能通过提供的公共接口访问。

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

多态:不同继承类的对象对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现形式。意义:对已存在的代码具有可替代性,对代码具有可扩充性,新增子类不会影响已存在类的各种性质,在程序中体现了灵活多样的操作,提高了使用效率,简化了对应用代码的编写和修改过程。

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

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

3.final标识符的作用是什么?
放在类的后面表示该类无法被继承,也就是阻止了从类的继承,放在虚函数后面该虚函数无法被重写,表示阻止虚函数的重载

4.虚函数是怎么实现的?它存放在哪里在内存的哪个区?什么时候生成的
在C++中,虚函数的实现原理基于两个关键概念:虚函数表和虚函数指针

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

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

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

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

5.智能指针的本质是什么,它们的实现原理是什么?
智能指针本质是一个封装了一个原始C++指针的类模板,为了确保动态内存的安全性而产生的。实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。

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

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

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

为什么:

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

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

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

8.左值引用和指针的区别?
是否初始化:指针可以不用初始化,引用必须初始化

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

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

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

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

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

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

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

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

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

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

12.传入一个指针,它如何确定具体要清理多少空间呢?
我们在申请内存的时候,会多分配16字节的内存,里面保存了内存块的详细信息,free会对传入的内存地址向左偏移16字节,然后分析出当前内存块的大小,就知道要释放多大的内存空间了。

13.define和const的区别是什么?
编译阶段:define是在编译预处理阶段进行简单的文本替换,const是在编译阶段确定其值

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

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

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

14.程序运行的步骤是什么
预编译:将头文件编译,进行宏替换,输出.i文件

编译:将其转化为汇编语言文件,主要做词法分析,语义分析以及检查错误,检查无误后将代码翻译成汇编语言,生成.s文件

汇编:汇编器将汇编语言文件翻译成机器语言,生成.o文件

链接:将目标文件和库链接到一起,生成可执行文件.exe

15.锁的底层原理是什么?
锁的底层是通过CAS,atomic 机制实现。

CAS机制:全称为Compare And Swap(比较相同再交换)可以将比较和交换操作转换为原子操作,CAS操作依赖于三个值:内存中的值V,旧的预估值X,要修改的新值B,如果旧的预估值X等于内存中的值V,就将新的值B保存在内存之中。(就是每一个线程从主内存复制一个变量副本后,进行操作,然后对其进行修改,修改完后,再刷新回主内存前。再取一次主内存的值,看拿到的主内存的新值与当初保存的快照值,是否一样,如果不一样,说明有其他线程修改,本次修改放弃,重试。)

atomic机制:如16问。

16.原子操作是什么?
原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何切换到另一个线程。

原理是:在X86的平台下,CPU提供了在指令执行期间对总线加锁的手段,CPU中有一根引线#HLOCK pin连接到北桥,如果汇编语言的程序在程序中的一条指令前面加上了前缀“LOCK”,经过汇编之后的机器码就使CPU在执行这条指令的时候把#HLOCKpin的电平拉低持续到这条指令结束的时候放开,从而把总线锁住,这样别的CPU就暂时不能够通过总线访问内存了,保证了多处理器环境中的原子性。

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

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

18.内存对齐是什么?为什么要进行内存对齐?内存对齐有什么好处?
内存对齐是处理器为了提高处理性能而对存取数据的起始地址所提出的一种要求。

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

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

19.进程之间的通信方式有哪些?
管道:管道分为匿名管道和命名管道,管道本质上是一个内核中的一个缓存,当进程创建管道后会返回两个文件描述符,一个写入端一个输出端。缺点:半双工通信,一个管道只能一个进程写,一个进程读。不适合进程间频繁的交换数据

消息队列:可以边发边收,但是每个消息体都有最大长度限制,队列所包含的消息体的总数量也有上限并且在通信过程中存在用户态和内核态之间的数据拷贝问题

共享内存:解决了消息队列存在的内核态和用户态之间的数据拷贝问题。

信号量:本质上是一个计数器,当使用共享内存的通信方式时,如果有多个进程同时往共享内存中写入数据,有可能先写的进程的内容被其他进程覆盖了,信号量就用于实现进程间的互斥和同步PV操作不限于信号量+-1,而且可以任意加减正整数

信号

套接字

20.线程之间的通信方式有哪些?
信号量

条件变量

互斥量

21.介绍一下socket中的多路复用,及其他们的优缺点,epoll的水平和边缘触发模式
select、poll、epoll都是IO多路复用的一种机制,可以监视多个文件描述符,一旦某个文件描述符进入读或写就绪状态,就能够通知系统进行相应的读写操作。

Select优点:可移植性好,因为在某些Unix系统中并不支持poll和epoll

                    对于超时时间提供了更好的精度:微妙,而poll和epoll都是毫秒级

Select缺点:支持监听的文件描述符fd的数量有限制,最大数量默认是1024个

Select需要维护一个用来存放文件描述符的数据结构,每次调用select都需要把fd集合从用户区拷贝到内核区,而select系统调用后有需要把fd集合从内核区拷贝到用户区,这个系统开销在fd数量很多的时候会很大。

Poll优点(相对于select而言):没有最大文件描述符数量的限制,poll基于链表存储主要解决了这个最大文件描述符数量的限制(当然,他还是有限制的,上限为操作系统能支持的能开启的最大文件描述符数量),优化了编程接口,减少了函数调用参数,并且,每次调用select函数时,都必须重置该函数的三个fd_set类型的参数值,而poll不需要重置。

Poll缺点:poll和select一样同样都需要维护一个用来存放文件描述符的数据结构,当注册的文件描述符无限多时,会使得用户态和内核区之间传递该数据结构的复制开销很大。每次poll系统调用时,需要把文件描述符fd从用户态拷贝到内核区,然后poll系统调用返回前,又需要把文件描述符fd集合从内核区拷贝到用户区,这个内存拷贝的系统开销在fd数量很多的时候会很大。

Epoll优点:和poll一样没有最大文件描述符数量的限制,epoll虽然也需要维护用来存放文件描述符的数据结构(epoll_event),但是它只需要将该数据结构拷贝一次,不需要重复拷贝,并且它只在调用epoll_ctl系统调用时拷贝一次要监听的文件描述符数据结构到内核区,在调用epoll_wait的时候不需要再把所有的要监听的文件描述符重复拷贝进内核区,这就解决了select和poll种内存复制开销的问题。

Epoll缺点:目前只有Linux操作系统支持epoll,不支持跨平台使用,而Unix操作系统上是使用kqueue

Epoll水平触发(LT):对于读操作,只要缓冲区内容不为空,LT模式返回读就绪。

                           对于写操作,只要缓冲区还不满,LT模式会返回写就绪。

Epoll边缘触发(ET):对于读操作,当缓冲区由不可读变为可读的时候,有新数据到达时,进程修改了EPOLL_CTL_MOD修改EPOLLIN事件时

在ET模式下,缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。通常配合将文件描述符设置为非阻塞状态一起使用,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

24.类的生命周期
类从被加载到内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中验证,准备,解析三个部分统称为连接

全局对象在main开始前被创建,main退出后被销毁。

静态对象在第一次进行作用域时被创建,在main退出后被销毁。

局部对象在进入作用域时被创建,在退出作用域时被销毁。

New创建的对象直到内存被释放的时候都存在。

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

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

26.多线程为什么会发生死锁,死锁是什么?死锁产生的条件,如何解决死锁?
因为在多进程中易发生多进程对资源进行竞争,如果一个进程集合里面的每一个进程都在等待这个集合中的其他一个进程才能继续往下执行,若无外力他们将无法推进,这种情况就是死锁。产生死锁的四个条件:互斥条件、请求和保持条件、不可剥夺条件、环路等待条件。解决死锁的方法就是破坏上述任意一种条件。

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

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

28.C++中左值和右值是什么?++i是左值还是右值,++i和i++哪个效率更高?
第一小问结合本文第七和第八问,++i是左值,因为++i返回的是一个左值没有发生拷贝,所以效率更高。

29.介绍一下vector、list的底层实现原理和优缺点
Vector优点:可使用下标随机访问,尾插尾删效率高。

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

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

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

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

底层是由双向链表实现的

30.静态变量在哪里初始化?在哪一个阶段初始化?(都存放在全局区域)
静态变量,全局变量,常量都在编译阶段完成初始化和内存分配。其他变量都是在编译阶段进行初始化,运行阶段内存分配.。

31.如何实现多进程?
在Linux中C++使用fork函数来创建进程

而windows中C++使用createprocess来创建进程

32.空对象指针为什么能调用函数?
在类的初始化的时候,编译器会将它的函数分配到类的外部,这也包括静态成员函数,这样做主要是为了节省内存,如果我们在调用类中的的成员函数时没有使用类中的任何成员变量,它不会使用到this指针所以可以正常调用这个函数。

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

34.push_back()左值和右值的区别是什么?
如果push_back()的参数是左值,则使用它拷贝构造新对象,如果是右值,则使用它移动构造新对象.。

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

优点:可以将左值变成右值而避免拷贝构造,将对象的状态所有权从一个对象转移到另一个对象,只是转移,没有内存搬迁或者内存拷贝。
————————————————
原文链接:https://blog.csdn.net/haokan123456789/article/details/139076485

顺便给大家分享一下,民族企业大厂前后端测试(https://jsj.top/f/o38ijj)捞人,待遇给的还不错,感兴趣的可以来试试!

温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2025-5-10 04:15 , Processed in 0.123500 second(s), 34 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表