面试宝典1


别人的面试总结文章:
C/C++常见面试知识点总结附面试真题
史上最全!C++后端开发面试题与知识点汇总
面试题目
c++经典面试题100例及答案
C++经典面试题(最全,面中率最高)
一份面试题+整理的答案
c/c++ 最常见50道面试题


1、操作系统的内存结构

在这里插入图片描述

BSS段:(bss segment)
       通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

数据段:(data segment)
       通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

代码段:(code segment/text segment)
       通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

堆(heap):
       堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
       这里区别 堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

栈(stack):
       栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
       这里区别 栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。

五大区:

1、栈区(stack)— 由编译器自动分配释放 ,存放函数地址,函数的参数,局部变量等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。此外,C++中有自由存储区(new)一说。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

2、堆和自由存储区的区别

       从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。

3、栈溢出、内存越界、内存泄漏

       首先要先贴出来这边文章写得特别好:c/c++ 栈溢出、越界、泄漏

       栈溢出就是不顾栈中数据块的大小,向该数据块写入过多的数据,导致数据越界,新的栈数据覆盖了老的栈数据,最后导致程序崩溃。
       因为操作系统给每个进程分配的栈空间一般是2M,程序调用一个函数是通过入栈和出栈这种先入后出的数据结构。当入栈时,如果栈的剩余空间小于要入栈的空间,老的栈数据就会被改写。在出栈时导致程序崩溃。
       会出现栈溢出的情况一般是函数调用的过深,或使用了递归函数该递归函数调用的过深,死循环等。可以使用尾递归来优化。

       内存越界就是存数据的时候,申请的内存空间小于要写入的数据大小,导致写到了申请内存空间之外,破坏了本不属于你的空间;或者就是访问某一对象内存数据的时候,访问位置超出了该对象范围。

       内存泄漏就是在堆空间上new的内存没有用delete去释放掉,导致该内存一直被占用。这样会造成系统的可分配内存越来越小,最后导致系统崩溃。可以用智能指针来优化。

4、指针和引用的区别

  1. 指针是一个变量,存的是一个地址,有自己的一块内存空间;引用是对象的别名
  2. 引用不能为空,被创建的时候必须初始化;指针可以是空值
  3. 指针的值可以改变,引用不可以
  4. 指针大小为4(32位),引用大小是对象的大小
  5. 指针可以有多级指针(**p),而引用只有一级

相同点:

  1. 都是地址的概念
  2. 引用内部是借助指针实现的
  3. 都是指向一块内存的;指针是指向内存地址,而引用则是内存的别名

5、static和const

static:

  1. 修饰局部变量:地址在全局区,在main函数之前已经初始化,生命周期在离开main函数之后结束,每次进入函数时不重新初始化,保留进入函数之前的数值。
  2. 修饰全局变量:普通全局变量可以用extern关键字跨文件引用,static全局变量不可以用extern关键字来跨文件调用。加了static修饰,不同源文件之间可以有同名的全局变量。加了static的全局变量的含义是本源文件专用
  3. 修饰成员变量:静态成员变量,地址在全局区,是全局性质的变量,只是名义上划归为某一类,但是该类的空间不包括静态成员变量;调用方法可以用对象或this指针,也可以直接指定类名加定义域去调用
  4. 修饰成员函数:静态成员函数,全局函数的性质,名义上划归为某一类,函数内没有this指针

const:

  1. const修饰指针:如果const在左边,表示被指物是常量(指针可以改变,值不能改变);如果在右边,表示指针自身是常量(指针不可以改变,值可以改变)
  2. const修饰变量:那这个变量只能在定义时初始化,后面不可以赋值修改;如果是函数参数,代表在这个函数中,该参数不能被修改
  3. const修饰成员函数:代表在该函数中,该类的成员变量不能被修改

6、静态全局变量、全局变量、静态局部变量、局部变量的区别

  • 静态全局变量、全局变量区别

(1)静态全局变量和全局变量都属于常量区
(2)静态全局区只在本文件中有效,别的文件想调用该变量,是调不了的,而全局变量在别的文件中可以调用
(3)如果别的文件中定义了一个该全局变量相同的变量名,是会出错的。

  • 静态局部变量、局部变量的区别

(1)静态局部变量是属于常量区的,而函数内部的局部变量属于栈区;
(2)静态局部变量在该函数调用结束时,不会销毁,而是随整个程序结束而结束,但是别的函数调用不了该变量,局部变量随该函数的结束而结束;
(3)如果定义这两个变量的时候没有初始值时,静态局部变量会自动定义为0,而局部变量就是一个随机值;
(4)静态局部变量在编译期间只赋值一次,以后每次函数调用时,不在赋值,调用上次的函数调用结束时的值。局部变量在调用期间,每调用一次,赋一次值。

6、new和malloc的区别

  1. new、delete是c++关键字,需要编译器支持。malloc、free是库函数,需要头文件支持
  2. 使用new操作符申请内存分配时无须指定内存大小,编译器会根据类型自行计算。而malloc则需要指定所需内存的大小
  3. new操作符内存分配成功时,返回的是对象类型的指针;而malloc内存分配成功则是返回void*,需要通过强制类型转换成需要的类型
  4. new分配失败会抛出bac_alloc异常;malloc分配失败返回null
  5. new和delete底层使用malloc和free实现,会调用对象的构造函数和析构函数。malloc和free是库函数,只能动态的申请和释放,无法强制要求其做自定义类型对象的构造和析构工作
  6. new和delete可以重载运算符,而malloc和free不允许重载
  7. new操作符从自由存储区为对象动态分配内存空间,而malloc从堆上动态分配内存。自由存储区是c++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中

有了malloc/free为什么还要new/delete
       malloc与free是c语言标准库函数,new/delete是c++的运算符。他们是可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此,c++语言用能完成动态内存分配和初始化工作的运算符new,以及能完成清理与释放内存工作的运算符delete。

7、c++三大特性

封装:
       通过关键字public、private和protected来实现隐藏类的属性和实现细节,仅仅对外提供接口。将一类事物归为一类封装起来作为一个对象处理,保护或者防止代码(数据)被我们无意中破坏。

继承:
       继承是面向对象程序设计中使代码可以复用的重要手段,它允许程序员在原有类特性的基础上进行扩展增加功能。派生类一般可以拥有基类的所有变量和函数(除了构造函数和析构函数的成员),并且可以在派生类中重写基类的方法和变量,当然这会受到继承权限的影响。并且派生类可以拥有基类没有的方法与变量。继承是多态的前提、继承增加了类的耦合性;

  • public继承:基类的public成员仍为派生类的public成员;基类的protected成员仍为派生类的protected成员;基类的private成员在派生类中不可见;基类的非私有成员在派生类的访问属性都不变。
  • protected继承:基类的public成员变为派生类的protected成员;基类的protected成员仍为派生类的protected成员;基类的private成员在派生类中不可见;基类的非私有成员都成为派生类的保护成员。
  • private继承:基类的public成员变为派生类的private成员;基类的protected成员变为派生类的private成员;基类的private成员在派生类中不可见;基类的非私有成员都成为派生类的私有成员。

多态:
       多态性是指对不同类的对象发出相同的消息将会有不同的实现,实现了接口重用。

  1. 在成员函数声明的前面加上virtual关键字,在派生类中重写该函数
  2. 在虚函数的后面写上 =0 ,则这个函数就变成纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象
  3. override关键字是用来检查函数是否重写,是在virtual void fun() override {}这里加上
  4. final是在class A final {};这里加上,目的是为了不让这个类被继承。
    或者,在一个函数后加,表示这个函数不能被重写。void fun() final {}

总结:
       封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。

8、c和c++中struct区别

       struct成员默认的是public, C++默认private。而C语言struct不是类,不可以有函数,也不能使用类的特征例如public等关键字 ,也不可以有static关键字。

9、c和c++的区别

       c是结构化编程语言,面向过程开发(但c也可以编写面向对象的程序)。c++是面向对象编程语言。
       c++是c的超集,在c的基础上增加了很多的东西。
       c适用于代码体积小的,效率高的,如嵌入式;c++适合更上层的,复杂的编程。c比c++效率要高。

10、静态全局变量的作用

       静态全局变量限制了其作用域,只能在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。

11、c++函数传参有哪几种方式,区别是什么

值传递:
       当进行值传递时,就是将实参的值复制到形参中,而形参和实参不是同一个存储单元,所以函数调用结束后,实参的值不会发生改变。

指针传递:
       当进行指针传递时,形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元,这种方式还是"值传递",只不过实参的值是变量的地址而已,而在函数中改变的不是实参的值,而是实参变量地址所指向的变量的值;

引用传递:
       实参地址传递到形参,使形参的地址取实参的地址,从而使形参与实参共享同一单元的方式;

12、c++中virtual和inline关键字含义分别是什么

       在基类成员函数的声明前加上virtual关键字,代表将该成员函数声明为虚函数。虚函数的特点:使用多态,在派生类中重新定义该函数的实现方法。
       inline与函数的定义体放一起,使该函数称为内联函数。inline是一种用于实现的关键字,而不是用于声明的关键字。内联函数的特点:
提高函数的运行效率。内联函数代码不能过长,不能包含循环语句,因为执行循环语句要比调用函数的开销大。

13、c++中explicit关键字含义是什么

       explicit关键字修饰类的构造函数,防止隐式转换。

14、析构函数的作用

       析构函数时特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载,只有在类对象的声明周期结束的时候,由系统自动调用。有释放内存空间的作用。

15、模板类的优点

  1. 可用来创建动态增长和减小的数据结构
  2. 它是类型无关的,因此具有很高的可复用性
  3. 它在编译时而不是运行时检查数据类型,保证了类型安全
  4. 它是平台无关的,可移植性
  5. 可用于基本数据类型

16、深拷贝和浅拷贝的区别

       深拷贝拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源。这样使得两个指针指向同一份资源,造成对同一份资源释放两次,最后导致程序崩溃。

17、说一说c++中四种cast转换

static_cast

  • 用于内置数据类型之间的相互转换,也可用于自定义类型,但只能在有相互联系(继承)的类型间转换,且不需要一定包含虚函数。不安全。

const_cast

  • 常量指针 被强转为 非常量指针,且仍然指向原来的对象。
  • 常量引用 被强转为 非常量引用,且仍然指向原来的对象。
  • 常量对象 被强转为 非常量对象。

dynamic_cast

  • 其它三种都是编译时完成的,dynamic_cast是运行时处理的,运行时进行类型检查,比较安全。
  • 不能用于内置基本数据类型间的强制转换。
  • 基类中一定要有虚函数。
  • 若转换成功,返回指针或引用,否则返回NULL

reinterpret_cast

  • 有着与c风格的强制转换能力
  • 它可以转换任何内置数据类型为其它任何的数据类型,也可以转换任何指针类型为其它类型
  • 不到万不得已绝对不用。

18、请你说一下c++的四个智能指针

auto_ptr

  • c++11已经弃用。当auto_ptr对象赋值给另一个auto_ptr对象时,原对象就变成了空指针,再使用原对象时就会出错。

unique_ptr

  • unique_ptr算是对auto_ptr的替换,它禁止了拷贝语义,提供了移动语义,即使用std::move进行控制权限的转移。使得unique_ptr比auto_ptr更安全。

shared_ptr

  • 内部存在引用计数的智能指针,多个智能指针可以指向相同对象。当一个对象被多个shared_ptr引用时,引用计数会增加,释放对象时,只有该对象的引用计数为零时才释放该内存。

weak_ptr

  • 它解决了shared_ptr循环引用的问题。
  • weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用。可以将shared_ptr的对象赋值给weak_ptr,并且这样不会改变引用计数的值。
  • 因为weak_ptr本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象。

19、数组和指针的区别

  • 数组: 数组是用于储存多个相同类型数据的集合。
  • 指针: 指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。

20、野指针是什么

       野指针指向一段实际的内存,只是它指向 哪里我们并不知情,或者是它所指向的内存空间已经被释放。

怎么会产生野指针:

  1. 指针变量的值未初始化
  2. 指针所指地址已被释放,却没有赋值为NULL
  3. 指针操作超越了作用域

21、为什么析构函数必须是虚函数,为什么c++默认的析构函数不是虚函数

       将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
       c++默认的析构函数不是虚函数,是因为虚函数需要额外的虚函数表虚表指针,占用额外的内存。对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此c++默认的析构函数不是虚函数,而是只有当需要被继承时,才被设置为虚函数。

22、什么是函数指针

       如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

23、说说c++函数重载和覆盖

函数重载
       是指一个类中,参数的类型、个数、顺序不同的同名函数,根据参数列表确定调用哪个函数,重载与函数的返回类型无关。

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

24、++i和i++的区别

       ++i比i++效率要高。
       在内部实现中i++会创建一个int对象并返回,而++i没有。

25、c++怎么定义常量的,常量存放在内存的哪个位置

  1. 使用#define预处理器
  2. 使用const关键字

存放于 常量区

26、说一说c++的隐式类型转换

  • 所谓隐式类型转换,是指不需要用户干预,编译器默认进行的类型转换行为。
  • 隐式类型转换一般分为两种:内置数据类型和自定义数据类型。
  • 自定义数据类型的隐式转换与构造函数有关,当构造函数的参数为1时,会进行隐式类型转换。可以使用explicit关键字防止隐式转换。

27、说说c++函数栈空间的最大值

       在windows下,栈地址是向低地址扩展的数据结构,是一块连续的内存区域。栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的大小时2MB,而申请堆空间的大小一般小于2GB。 栈的速度快,但空间小,不灵活。堆是向高地址扩展的数据结构,是不连续的内存区域。
       这是因为系统是用链表来存储空间内存的,自然是不连续的,而链表的遍历方向是由低地址向高地址的,而堆的大小受限于计算机系统中的有效的虚拟内存,所以堆获得的空间比较灵活,也比较大,但是速度相对慢些。

28、说一说 extern “C”

       extern "C"的主要作用就是为了能够正确实现c++代码调用其它c语言代码。加上extern "C"后,会指示编译器这部分代码按c语言进行编译。由于c++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中;而c语言不支持函数重载,因此编译c语言代码的函数时不会带上函数的参数类型。

29、虚函数是如何实现运行时多态的

       子类如果重写父类虚函数,在虚函数表中,该函数的地址会被替换掉。对于存在虚函数的类的对象,在vs中,对象的对象模型头部存放着指向虚函数表的指针,通过该机制,实现运行时多态。

30、函数调用的过程

int main(void)
{
  ...
  d = fun(a, b, c);
  cout<<d<<endl;
  ...
  return 0;
}

调用fun()的过程大致如下:
main()========
1).参数拷贝(压栈),注意顺序是从右到左,即c-b-a;
2).保存d = fun(a, b, c)的下一条指令,即cout<<d<<endl(实际上是这条语句对应的汇编指令的起始位置);
3).跳转到fun()函数,注意,到目前为止,这些都是在main()中进行的;
fun()=====
4).移动ebp、esp形成新的栈帧结构;
5).压栈(push)形成临时变量并执行相关操作;
6).return一个值;
7).出栈(pop);
8).恢复main函数的栈帧结构;
9).返回main函数;
main()========
。。。

31、说说如何处理函数的返回值

  1. 不要返回局部对象的引用或返回值
  2. 引用返回左值,其它返回类型 得到右值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值