【6】C++岗位求职面试八股文系列文章(语言基础)

第一篇:语言基础

第二篇:设计模式

第三篇:数据库

第四篇:计算机网络

第五篇:操作系统

第六篇:LInux

第七篇:数据结构

第八篇:智力题

[101]自由存储区与堆有什么区别

“malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来释放。”

堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价

[102]说C与C++的内存分配方式

(1)从静态存储区域分配 内存在程序编译的时候就已经分配好,这块内存在程序的整个运⾏期间都存在,如全局变ᰁ, static变ᰁ。(2)在栈上创建 在执⾏函数时,函数内局部变ᰁ的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元 ⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容ᰁ有限。(3)从堆上分配(动态内存分配) 程序在运⾏的时候⽤malloc或new申请任意多少的内存,程序员负责在何时⽤free或delete释 放内存。动态内存的⽣存期⾃⼰决定,使⽤⾮常灵活。

[103]程序有哪些section,分别的作用?

[104]一个程序有哪些section

如上图,从低地址到高地址,一个程序由代码段、数据段、 BSS 段组成。数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。

可执行程序在运行时又会多出两个区域:堆区和栈区。堆区:动态申请内存用。堆从低地址向高地址增长。栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。最后还有一个文件映射区,位于堆和栈之间。

[105]程序启动的过程?

操作系统首先创建相应的进程并分配私有的进程空间,然后操作系统的加载器负责把可执行文件的数据段和代码段映射到进程的虚拟内存空间中。加载器读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序的所有依赖的动态链接库。加载器针对该程序的每一个动态链接库调用LoadLibrary(1)查找对应的动态库文件,加载器为该动态链接库确定一个合适的基地址。(2)加载器读取该动态链接库的导入符号表和导出符号表,比较应用程序要求的导入符号是否匹配该库的导出符号。(3)针对该库的导入符号表,查找对应的依赖的动态链接库,如有跳转,则跳到3(4)调用该动态链接库的初始化函数初始化应用程序的全局变量,对于全局对象自动调用构造函数。进入应用程序入口点函数开始执行。

[106]怎么判断数据分配在栈上还是堆上?

首先局部变量分配在栈上;而通过malloc和new申请的空间是在堆上

[107]初始化为0的全局变量在bss还是data

BSS段通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。

初始化为0指的是显示初始化为0,的话,在data没有初始化然后等加载时初始化为0的静态变量,在.bss

[108]请简述一下atomoic内存顺序。

有六个内存顺序选项可应用于对原子类型的操作:memory_order_relaxed:在原子类型上的操作以自由序列执行,没有任何同步关系,仅对此操作要求原子性。memory_order_consume:memory_order_consume只会对其标识的对象保证该对象存储先行于那些需要加载该对象的操作。memory_order_acquire:使用memory_order_acquire的原子操作,当前线程的读写操作都不能重排到此操作之前。memory_order_release:使用memory_order_release的原子操作,当前线程的读写操作都不能重排到此操作之后。memory_order_acq_rel:memory_order_acq_rel在此内存顺序的读-改-写操作既是获得加载又是释放操作。没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后。memory_order_seq_cst:memory_order_seq_cst比std::memory_order_acq_rel更为严格。memory_order_seq_cst不仅是一个"获取释放"内存顺序,它还会对所有拥有此标签的内存操作建立一个单独全序。除非你为特定的操作指定一个顺序选项,否则内存顺序选项对于所有原子类型默认都是memory_order_seq_cst。

[109]内存对齐规则

链接如果加入pragma pack(n) ,取n和变量自身大小较小的一个。在一个struct中包含另一个struct,内部struct应该以它的最大数据成员大小的整数倍开始存储

联合体:最后是成员最大一个满足所有元素的元长度的整数倍结构体:最后是成员总和和对齐模数比较

[110]简述C++中内存对齐的使用场景

内存对齐应用于三种数据类型中:struct/class/unionstruct/class/union内存对齐原则有四个:数据成员对齐规则:结构(struct)或联合(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始。

结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储。(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。

收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的"最宽基本类型成员"的整数倍。不足的要补齐。(基本类型不包括struct/class/uinon)。

sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。

[111]什么是内存对齐?

访问特定类型变量ᰁ的时候经常在特定的内存地址访问,这需要各种类型数据按照⼀定的规则在 空间上排序,⽽不是顺序的⼀个接⼀个的排放。那么什么是字节对齐?在C语言中,结构体是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构体、联合体等)的数据单元。在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即“对齐”跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。

比如在32位cpu下,假设一个整型变量的地址为0x00000004(为4的倍数),那它就是自然对齐的,而如果其地址为0x00000002(非4的倍数)则是非对齐的。现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。内存对齐是指首地址对齐

[112]为什么要字节对齐?

需要字节对齐的根本原因在于CPU访问数据的效率问题。主要原因可以归结为两点:

平台原因: 有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时,将分配的内存进行对齐,这就具有平台可以移植性了

性能原因: CPU每次寻址都是要消费时间的,并且CPU 访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问,内存对齐后可以提升性能。举个例子:、

未对齐后果:会导致访问变ᰁ时发⽣错误,以及读取效率上下降很多字节对齐除了内存起始地址要是数据类型的整数倍以外,还要满足一个条件,那就是占用的内存空间大小需要是结构体中占用最大内存空间的类型的整数倍,成员变量的顺序也会影响内存对齐的结果

32位:对齐模数464位:对齐模数8

联合体:20 4 8选取20 4 8-20对4满足整除,对8不满足,所以扩为24

20 4 8=3232 4整数倍32

4 8 4= 1616 4=16对齐模数默认为4

联合体:最后是成员最大一个满足所有元素的元长度的整数倍结构体:最后是成员总和和对齐模数比较

20字节 模数为424字节 模数为8

36字节(4,24,8) 模数4

40字节 模数8

[113]简述一下什么是面向对象

面向对象是一种编程思想,把这些对象拥有的属性变量和操作这些属性变量的函数打包成一个类来表示

面向过程和面向对象的区别面向过程:根据业务逻辑从上到下写代码面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程

[114]简述一下面向对象的三大特征

面向对象的三大特征是封装、继承、多态。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理:我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

protected与private基本相似,只有在继承时有较大的区别。继承的类可以访问protected成员,但是不能访问private成员。

多态:用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。实现多态,有二种方式,重写,重载。

[115]内存溢出和内存泄漏

内存溢出是指程序在申请内存时没有足够的内存空间供其使用。原因可能如下:(1)内存中加载的数据过于庞大;(2)代码中存在死循环;(3)递归调用太深,导致堆栈溢出等;(4)内存泄漏最终导致内存溢出;

内存泄漏是指使用new申请内存, 但是使用完后没有使用delete释放内存,导致占用了有效内存。

1.在类的构造函数和析构函数中没有匹配的调用new和delete函数2.没有正确地清除嵌套的对象指针3.在释放对象数组时在delete中没有使用方括号4.没有将基类的析构函数定义为虚函数5.缺少拷贝构造函数

[116]野指针

指向被释放的或者访问受限内存的指针。

造成野指针的原因:3个1.指针变量没有被初始化(如果值不定,可以初始化为NULL)

2.指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.

3指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。

[117]简述一下 C++ 的重载和重写,以及它们的区别

重写是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类对象调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

重载函数重载是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。函数重载原理:编译器采用了名称修饰技术重写是指在派生类当中,重新对基类中的虚函数度重新实现。重载使用相同的函数名字,函数的参数、个数或者是类型都不同,与函数返回类型无关。重写:重写是垂直关系,是子类和父类之间的关系重载:重载是水平关系,是同一个类中方法之间的关系

在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。重写用虚函数来实现,结合动态绑定。纯虚函数是虚函数再加上 = 0。抽象类是指包括至少一个纯虚函数的类。纯虚函数:virtual void fun()=0。即抽象类必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

不是函数重载,会报错:(顶层const不是重载)

是函数重载(带引用的都是底层const)

基类指针只能调用基类的被隐藏函数,无法识别派生类中的隐藏函数(隐藏函数指的是基类和子类中的函数重载)

(父类指针指向子类)指针只能调用基类的重载函数,调用子类的重载函数也显示的是基类的重载函数(原因是没有重写,不具有多态·)

但是子类对象都可以

[118]C 语言如何实现 C++ 语言中的重载

C语言本身没有函数重载功能c语言中不允许有同名函数,因为编译时函数命名是一样的,不像c++会添加参数类型和返回类型作为函数编译后的名称,进而实现重载。如果要用c语言显现函数重载,可通过以下方式来实现:使用函数指针来实现,重载的函数不能使用同名称,只是类似的实现了函数重载功能重载函数使用可变参数,方式如打开文件open函数gcc有内置函数,程序使用编译函数可以实现函数重载

使用函数指针实现例子:

extern “C”

extern “C” void func(){…………}

被extern “C”所修饰的代码会按照c语言方式编译,在申明和定义都要加用途:做c和c++混合开发

只有c++里有这个宏,c里没有#ifdef __cplusplus

#endif

[119]指针和引用的区别

(1)指针是一个变量,保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接使用别名就能访问。(2)指针可以改变所指向的对象,引用必须永远绑定在一个对象上。(3)引用在定义的时候必须初始化,不可以为NULL;而指针则不需要,可以为NULL。(4)指针有指向常量的指针和指针常量,而引用没有引用常量,因为引用不是对象。(5)引用比指针安全。(6)指针可以有多级,引⽤只有⼀级(7)sizeof指针得到的是本指针的⼤⼩,sizeof引⽤得到的是引⽤所指向变量的⼤⼩

[120]数组与指针

(1)把数组当参数传递给函数时,数组退化为对应其元素类型的指针。(2)sizeof数组得到的是整个数组的大小,而将指针用作其参数时,得到的是指针大小。Sizeof()是一个操作符

[续]C++岗位求职面试八股文第七篇

更多关于算法题解、软件开发面经、机器学习算法面经、各企业面试问题记录,关注Fintech砖,持续更新中。https://www.nowcoder.com/users/873777317

企业面试记录专栏https://www.nowcoder.com/creation/manager/columnDetail/0YBWnm

机器学习面经专栏https://www.nowcoder.com/creation/manager/columnDetail/j8nNy0

软件开发面经专栏https://www.nowcoder.com/creation/manager/columnDetail/0aXKaM

更多校园招聘常见面试问题(开发、算法、编程题目)参见CSDN博客:http://t.csdn.cn/V4qbH

欢迎关注、收藏、点赞后进行问题咨询及秋招建议!

#晒一晒我的offer##牛客在线求职答疑中心##牛客解忧铺##如何判断面试是否凉了##我的实习求职记录#
软件开发八股面经 文章被收录于专栏

包含C++、操作系统、数据库、计算机组成、计算机网络、设计模式、操作系统、牛客网服务器项目、综合智力题等

全部评论
感谢分享,加油牛友!😁
点赞 回复 分享
发布于 2024-04-23 19:14 山东

相关推荐

评论
2
10
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务