C++面试
2100
2021.02.27
2021.03.02
发布于 未知归属地

1. 指针和引用的区别:

  1. 指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。(是否可变)
  2. 指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间(是否占内存)
  3. 指针可以为空,但是引用必须绑定对象(是否可为空)
  4. 指针可以有多级,但是引用只能一级(是否能为多级)

2. 栈和堆的区别:

  1. 申请方式:栈是系统自动分配,堆是程序员主动申请。
  2. 申请后系统响应:只要栈的剩余空间大于申请空间,则分配成功,否则栈溢出。操作系统有一个记录空闲地址空间的链表,在链表上寻找第一个大于所申请的空间的结点,然后把该结点从链表中删除,并将该结点的空间分配给程序。大多数系统中该块空间的首地址存放的是本次分配空间的大小,便于后续释放,将该块空间上的剩余空间再次连接在链表上。
  3. 大小限制:栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。
  4. 申请效率:栈是由系统自动分配,申请效率高,但程序员无法控制;堆是由程序员主动申请,效率低,使用起来方便,但是容易产生碎片。
  5. 存放内容:栈中存放的是局部变量,函数的参数;堆中存放的内容由程序员控制。

3. const和static的用法:
const用法:

  • const定义常量,相较于宏常量,优点是可以进行类型检查,节省内存,效率高。
  • const修饰函数参数,使得传递过来的参数不可改变。
  • const修饰成员函数,使得该成员函数不能修改成员变量,也不能调用非const成员函数,因为非const成员函数可能会修改成员变量。

static定义静态变量,静态函数:

  • static作用于局部变量,改变了局部变量的生存周期,使得该变量存在到程序运行结束。
  • static作用于全局变量和函数,改变了全局变量的作用域,使得该全局变量只能在定义它的文件里使用。
  • static作用于类的成员变量和成员函数,使得类成员变量和成员函数与类有关,也就是说即使不定义类的对象,也可以通过类访问这些静态成员变量或函数。类的静态成员函数只能访问静态成员变量或静态成员函数,静态成员函数不能定义成虚函数。

const 和 static 在类中使用的注意事项(定义、初始化和使用)
static 静态成员变量:

  • 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和 private/public/protected 访问规则。
  • 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
  • 静态成员变量可以作为成员函数的可选参数,而普通成员变量不可以.
  • 静态成员变量的类型可以是所属类的类型,而普通成员变量不可以,普通的成员变量只能声明成类的指针或引用。

static 静态成员函数:

  • 静态成员函数不能访问非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。静态成员函数做为类作用域的全局函数。
  • 静态成员函数不能声明成虚函数(virtual)、const、volatile

const 成员变量:

  • const 成员变量只能在类内声明、定义,在构造函数初始化列表中初始化
  • const 成员变量只在某个对象的生存周期内是常量,对于整个类而言却是可变的,因为类可以创建多个对象,不同对象的 const 成员变量的值是不同的,所以不能在类的声明中初始化 const 成员变量,因为类的对象还没有创建,编译器不知道它的值。

const 成员函数:

  • 不能修改成员变量的值,除非有 mutable 修饰;只能访问成员变量
  • 不能调用非const成员函数,以防修改成员变量的值

4. define 和 const 的区别(编译阶段、安全性、内存占用等)

  • 编译阶段:define 是在编译预处理阶段起作用,const 是在编译阶段和程序运行阶段起作用
  • 安全性:define 定义的宏常量没有数据类型,只进行简单的替换,不会进行类型安全的检查;const 定义的只读变量是有类型的,是要进行安全检查的
  • 内存占用:define 定义的宏常量,在程序中使用多少次就会进行多少次替换,内存中有多个备份;const 定义的只读变量在程序运行中只有一份
  • 调试:define 定义的不能调试,因为在编译预处理阶段就已经进行了替换;const 定义的可以进行调试

const相较于define的优点:
可调式,节省内存,效率高,有数据类型,可以进行安全性检查。

5. C 和 C++ 的区别

  • C 是面向过程的编程,特点是函数;C++ 是面向对象的编程,特点是类。(特性)
  • C 主要用在嵌入式开发、驱动开发和硬件直接打交道的领域;C++ 可以用于应用层的开发、用户界面开发等和操作系统直接打交道的领域。(应用领域)
  • C++ 继承了C的底层操作特性,增加了面向对象的机制,增加了泛型编程、异常处理、运算符重载,命名空间等。(相较于 C 的升级)

6. Struct 和 class 的区别
struct 和 class 都可以自定义数据类型,也支持继承操作

  • struct 中默认的访问级别是 public,默认的继承级别也是 public
  • class 中默认的访问级别是 private,默认的继承级别也是 private
  • 当 class 继承 struct 或者 struct 继承 class 时,默认的继承级别取决于class或struct本身
  • class 可以使用模板,struct 不能

7. C++、Java 的联系与区别,包括语言特性、垃圾回收、应用场景等(java 的垃圾回收机制)
语言特性上的区别:

  • 指针:C++ 可以直接操作指针,容易产生内存泄漏以及非法指针引用的问题;JAVA 并不是没有指针,虚拟机(JVM)内部还是使用了指针,只是程序员不能直接使用指针,不能直接通过指针来访问内存,并且 JAVA 增加了内存管理机制
  • 多重继承:C++ 支持多重继承,一个类继承自多个父类,功能强大,但是如果使用的不当会造成很多问题,例如:菱形继承;JAVA 不支持多重继承,但允许一个类可以继承多个接口,可以实现 C++ 多重继承的功能,同时避免了多重继承带来的问题
  • 数据类型和类:C++ 可以将变量或函数定义成全局,但JAVA是完全面向对象的语言,除了基本的数据类型之外,其他的都作为类的对象,包括数组

垃圾回收:

  • JAVA 具有垃圾回收机制,程序员无需考虑内存管理问题,可以有效的防止内存泄漏
  • JAVA 所有的对象都是用 new 操作符建立在内存堆栈上,类似于 C++ 中的 new 操作符,但是当要释放该内存空间时,JAVA 自动进行内存回收操作,C++ 需要程序员主动释放,并且 JAVA 中的内存回收是以线程的方式在后台运行的,消耗空闲时间。

应用场景:

  • java 运行在虚拟机上,跨平台性强,C++ 需要编译成可执行文件,是否跨平台在于用到的编译器是否支持多平台
  • C++ 可以直接编译成可执行文件,运行效率比 JAVA 高
  • JAVA 主要用来开发 web 应用,C++ 在嵌入式开发、网络编程、并发编程方面用的比较多

8. new 和 delete 是如何实现的,new 与 malloc 的异同处
使用的时候 new,delete 搭配使用,malloc 和 free 搭配使用

  • 属性:malloc/free 是库函数,需要头文件的支持;new/delete 是关键字,需要编译器的支持
  • 参数:new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算;malloc 在申请空间时,需要确定所申请空间的大小
  • 返回值:new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,符合类型安全的操作符;malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针
  • 分配失败:new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针
  • 重载:new/delete 支持重载,malloc/free 不能进行重载
  • 自定义类型实现:new 首先调用 operator new 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete 释放空间(底层通过 free 实现)。malloc/free 无法进行自定义类型的对象的构造和析构
  • 内存区域:new 从自由存储区上动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)

9. STL 中 unordered_map 和 map 的区别

  • 底层实现不同:
    unordered_map 底层实现是一个哈希表,元素无序,map 底层实现是红黑树,其内部的元素是有序的,因此对 map 的所有操作,其实是对红黑树的操作。
  • 优缺点:
    unordered_map:查找和插入效率高,时间复杂度是O(1);但是建立哈希表比较耗费时间
    map:内部元素有序,查找和删除操作都是 logn 的时间复杂度;但是维护红黑树的存储结构需要占用一定的内存空间。
  • 适用情况:
    对于要求内部元素有序的使用 map,不要求元素有序且要求高效查找的用 unordered_map

10. STL 中 vector 的实现
vector 是一个动态数组,底层是一段连续的线性内存空间。
扩容的本质:当 vector 实际所占用的内存空间和容量相等时,如果再往其中添加元素需要进行扩容。其步骤如下:

  • 首先,申请一块更大的内存空间,一般是增加当前容量的 50% 或者 100%,和编译器有关
  • 然后,把旧内存空间的内容,按照原来的顺序拷贝到新的空间中
  • 最后,释放旧内存空间

从 vector 扩容的原理也可以看出:vector 容器释放后,与其相关的指针、引用以及迭代器会失效
注意事项:
插入元素需要考虑元素的移动问题和是否需要扩容的问题,频繁的调用 push_back() 使得扩容对性能造成影响

11. C++ 中的重载和重写(覆盖)的区别

  • 对于类中函数的重载或者重写而言,重载发生在同一个类的内部,重写发生在不同的类之间,子类和父类之间
  • 重载的函数需要与原函数有相同的函数名、不同的参数列表,不关注函数的返回值类型;重写的函数的函数名、参数列表和返回值类型都需要和原函数相同
  • virtual 关键字:基类被重写的函数必须有 virtual 关键字的修饰,重载的函数可以有 virtual 关键字的修饰也可以没有

一般而言,函数重写就是虚函数重写,为的是实现多态调用

12. C++ 内存管理
C++ 内存分区:栈、堆、自由存储区、全局/静态存储区、常量区

  • 栈:存放函数的局部变量,由编译器自动分配和释放
  • 动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收
  • 自由存储区:和堆十分相似,存放由 new 分配的内存块,由 delete 释放内存
  • 全局区/静态区:存放全局变量和静态变量
  • 常量存储区:存放的是常量,不允许修改

堆和自由存储区的区别:

  • 自由存储是 C++ 中通过 new 与 delete 动态分配和释放对象的抽象概念,通过new申请的内存可称为自由存储区;而堆是 C 语言和操作系统的术语,是操作系统维护的一块特殊的,具备动态分配功能的内存。
  • 很多 C++ 编译器的 new/delete 都是以 malloc/free 为基础来实现的,可以说这些编译器默认使用堆来实现自由存储
  • 堆和自由存储区有区别,并非等价。使用 new 来分配内存,程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。

13. 指针和数组的对比

  • 字符数组的内容可以改变,字符指针的内容不可以改变
char a[] = "hello";
a[0] = 'l';
cout << a << endl;
char *p = "world";//p指向常量字符串
p[0] = 'h'; //运行出错
  • 字符数组之间不能赋值,不能直接用比较运算符比较,若进行赋值调用 strcpy() 函数,若进行比较调用 strcmp() 函数;
char a[] = "hello";
char b[10];
strcpy(b, a);//不能用b = a;
if(strcmp(a, b) == 0)//不能用a==b
	cout << "endl";
  • 运算符 sizeof 可以计算出字符数组的容量,但是计算字符指针时,得到的是指针变量所占用的空间,而不是指针所指向空间的大小。
  • 数组作为参数传递时,数组会自动退化为指针
  1. 出现野指针的情形
  • 指针定义的时候未初始化
  • 指针指向的内存空间在释放(delete 或 free)后,指针没有置为 NULL,让人误以为是合法指针
  • 指针操作超过了变量的作用范围。例如:在函数中将一个局部变量的地址作为函数的返回值,这里编译器会给出警告,因为离开该函数后,局部变量的空间就会释放掉,返回的地址(指针)相当于是野指针。

内存泄漏: 内存泄漏常指的是堆内存泄漏,当然还包括系统资源的泄漏

评论 (1)