(独自升级Lv.2)C++基础面试题

C++基础篇1和基础篇2总结的到这里就差不多结束了,后续有新内容会继续更新这两篇,刷到这篇文章的同学可以看前一章武功秘籍,这两篇是收集了差不多涵盖大部分C++常考的面试题,有刁钻的也有基础的,适合复习、查缺补漏总结到自己的笔记来使用。


1.C++ 编译过程:

  • 预处理:展开宏定义、处理条件编译指令、删除注释,获得一个纯C++源码文件。
  • 编译:将源码文件翻译成汇编语言,生成汇编代码,编译器进行词法语法语义分析,检查代码正确性,生成中间代码。
  • 汇编:将汇编代码转换成机器指令,生成与目标文件
  • 链接:将目标文件和必要的库文件链接在一起,生成可执行程序。链接器解析目标文件的符号引用,将其与其他目标文件或库文件中符号定义进行匹配,最终生成完整的可执行文件。链接阶段还会进行地址重定位、符号解析、符号表生成等操作,确保程序正确执行。

2. 静态链接和动态链接有什么区别:

  • 链接方式:静态链接库会在编译链接时会完整的被复制到可执行文件中。动态链接库在编译链接时只会在可执行文件中包含对库的引用,库文件在运行时由操作系统动态加载。
  • 文件大小:静态链接会使可执行文件大小增加;动态链接库不会增加可执行文件的大小
  • 内存占用:静态链接在可执行文件中有一定的内存占用,动态链接库在运行时被加载,可以在多进程间共享,减少内存占用。
  • 拓展性:动态链接库的拓展性更好,不修改可执行文件的情况下替换和添加新的库文件,静态库需要重新编译链接。

3.C++面向对象的三大特性:

  • **面向对象:**对象指一切的事物,事物的抽象就是类,类中包含属性(成员变量)和行为(成员函数)。
  • **封装:**将一个事物的属性与行为封装成了一个类,类产生的实例叫做对象,对外提供了必要的成员函数作为接口访问,提高了数据安全性。
  • **继承:**继承于基类的派生类,可以拥有父类公有的属性与行为,继承可以分为多继承和菱形继承和虚继承,虚继承是解决菱形继承二义性的问题。
  • **多态:**通过统一接口,处理不同类对象的行为。

静态多态:

  • 函数重载、模板重载
    动态多态:
  • 在继承关系中,父类中定义虚函数,若干子类继承后重写虚函数,主函数中父类的指针或引用指向子类对象,通过程序运行期间的动态绑定,去调用子类中的重写虚函数,从而实现多态。

4. 多态的原理?

  • 动态绑定,通过虚表和虚表指针来实现的,虚表就是函数指针数组,虚表指针是指向虚表的指针。动态绑定的过程:定义虚函数的类,在编译期间,编译器会初始化虚表指针和创建虚表存储在常量区.data段。程序运行期间,在构造函数中给虚表指针赋值来指向对应虚表,那么父类指针或引用指向子类对象时,通过子类对象的虚表指针来找到对应虚函数表中的虚函数来调用。

5. 函数重载、函数重写、函数隐藏的区别:

  • 函数重载:同一作用域下,不同的参数列表的同名函数,与返回值无关,主函数根据参数列表来决定调用哪个函数。
  • 函数重写:继承关系中,父类定义虚函数,子类中重写这个同名虚函数(除了函数体不同,其余严格一致),就叫做函数重写。
  • 函数隐藏:继承关系中,子类隐藏了与其同名的父类成员函数,只要同名,不管参数列表和返回值相不相同都隐藏。也可以简单记忆成不是函数重写也不是函数重载的就叫函数隐藏。如果需要访问父类函数,可以通过作用域限定符或 using 声明引入。

6.C++ 中的函数对象?

  • 函数对象是重载了operator()的类的实例化,函数对象可以像普通函数一样被调用。
  • 普通函数直接被调用,函数对象需要创建实例然后通过实例调用,还可以重载多个操作符实现更多功能,C++11中的lambda表达式就是个匿名的函数对象。

7.C++ 中提供默认的函数有哪些(缺省函数)?

  • 构造函数、析构函数、拷贝构造函数、拷贝赋值、移动构造、移动赋值

8. 虚函数与纯虚函数的区别?

  • 虚函数和纯虚函数的定义形式不同。
  • 虚函数有默认实现,子类重写不重写都可以,不重写继承父类实现。纯虚函数没有默认实现,=0,强制子类重写,否则子类无法实例化。
  • 拥有虚函数的类是普通类可以实例化,拥有纯虚函数的类是抽象类,无法实例化。
  • 用途:子类灵活重写,实现多态;定义接口规范,强制重写

9. 单继承和多继承的虚表结构?

  • 单继承中,一个对象只包含一个vptr,指向该类的虚表,子类的虚函数表通过覆盖和拓展而来。
  • 多继承中,如果子类继承的父类都包含虚函数,那么一个子类的对象(C类)会包含多个基类子对象(A类和B类),每个基类子对象都有各自自己的vptr,指向对应的虚表,这样保证通过不同基类指针或引用指向子类对象时,正确的动态绑定从而调用对应的虚表中的虚函数。

10. 抽象类?

  • 至少拥有一个纯虚函数的类就是抽象类(接口类),特点就是不能实例化,继承的子类必须强制实现纯虚函数,否则子类也是抽象类,也不能实例化。
  • **例如:**定义Shape抽象类时,不同图形的面积计算逻辑完全不同,无法在Shape类中给出统一实现,因此将计算面积的函数声明为纯虚函数,强制子类必须实现自己的计算逻辑,既保证了所有图形类都具备核心功能,又能通过多态实现 "统一调用、不同行为"。
  • 用途:定义接口规范,强制子类实现核心逻辑;封装通用逻辑,减少代码重复(框架/库设计、插件化开发

11. 多继承?菱形继承?

  • **多继承:**一个C类既可以继承A类也可以继承B类,用来功能合并、代码复用的。
  • 菱形继承: D继承B/C,B/C又继承父类A。菱形继承会有数据冗余和访问二义性问题。
    • 数据冗余:D中会包含两份A的成员(来自B和C),浪费内存。
    • 访问二义性:假设基类A有个成员变量a,当创建D对象d时使用d.a时会编译出错,会无法区分是B类的A成员还是C类的A成员,加上作用域可以区分。
  • 实际开发:优先使用单继承+组合代替多继承(比如:C类中包含A类和B类的对象,而非继承A类和B类)

12. 虚继承?

  • 解决二义性、数据冗余,让B/C类虚继承A,保证D中只保留一份A的成员。
  • 核心是「虚基类指针+ 虚基类表」实现共享基类:B/C 不再存储 A 的成员,而是通过指针 + 偏移量指向 D 中共享的 A 实例;共享的 A 实例由最终派生类(D) 负责构造和析构,而非 B/C------ 即使 B/C 有构造函数,也不会直接初始化 A,而是由 D 统一初始化 A,避免多次构造。
  • 最终派生类 D 中只保留一份 A 成员,解决了菱形继承的 "数据冗余 + 二义性";

13.C++ 空类大小:

  • 大小为1。C++规定,任何对象都必须有唯一的内存地址,如果空类大小为0,创建此类多个对象时,它们共享同一个地址,违反每个对象地址唯一的规则(大小为1,只是占位,不是为了存数据)。

14. 只含虚函数的类大小:

  • 有虚函数类的对象中有虚表指针,大小为4字节(32位),8字节(64位)。

15.new malloc 的区别:

  • new的返回值是具体类型的指针(类型安全);malloc的返回值是void*,需要强转
  • new是运算符(关键字)可以重载,malloc是C语言标准库函数,不可重载
  • new申请内存无需传入具体字节数,自动计算类型大小;malloc需要传入具体的字节数
  • new申请内存失败抛出bad_alloc异常;malloc申请失败返回NULL指针,需要手动检查

16.new 一个对象有几个步骤?

  • 先用operator new给对象本身分配内存,也就是通过operator new底层调用malloc申请堆区内存给对象,此时对象内部的成员都未初始化。
  • 然后调用构造函数初始化成员,给指针类型成员变量分配堆区内存。
  • 注意:对象本身占用的堆内存和成员指向的堆内存是两块独立的堆内存

17.delete 如何知道释放多大的内存?

  • 通过malloc申请内存时,不会按照传入的字节大小去申请,而是相比来说大一些,用来存传入字节大小的数值,这样free时,通过偏移量读取到内存的大小后进行释放。

18.malloc 的原理?

  • malloc的底层源码中定义了一个阈值:
    • 如果用户分配的内存小于128kb,使用brk()系统调用。
    • 如果用户分配的内存大于128kb,使用mmap()系统调用。
  • brk():堆顶指针向高地址移动,获得内存。
  • mmap():从文件映射区域分配内存。

19.malloc 分配的是物理内存吗?

  • mallo分配的是虚拟内存,分配后的虚拟内存没有被访问时,不会映射到物理内存上,不占用物理内存,这种机制叫做按需分页。
  • 当程序第一次访问虚拟内存时,如果页表中没有对应的物理页,就会触发缺页中断,操作系统会为该页分配物理内存并建立映射。

20.free 释放的内存会归还给操作系统吗

  • brk()申请的不会归还,缓存在malloc的内存池中,待下次使用。
  • mmap()申请的内存会归还给操作系统,得到了真正的释放。

21. 4G 的物理内存上申请 8G 内存会怎样?

  • 能申请的到,并且是虚拟内存,如果这块内存被访问了,页表中没有对应的物理页,触发缺页中断,操作系统为其分配物理内存建立映射。如果是32位机器,物理内存不足,系统会尝试回收内存或使用swap,如果仍然不足,可能会触发OOM killer杀死进程。

22. 模板:

  • 模板分为函数模板和类模板,关键字template后跟模板参数列表,模板类型参数前用关键字typename
  • **函数模板:**避免为每一种类型定义一个新函数,编译器可以根据函数实参推断模板实参,确定绑定哪个函数模板,函数模板被调用实例化后就是模板函数。
  • **类模板:**和函数模板类似,但编译器不能为类模板推断模板参数的类型,使用该类模板时,需要在模板名后的尖括号中指明类型。

23. 函数模板和类模板的区别?

  • 实例化方式不同:函数模板实例化是在调用的时候;类模板实例化在程序中显式指定。
  • 默认参数:类模板的模板参数列表中可以有默认参数。
  • 调用方式:函数模板可以隐式和显式调用;类模板只能显式调用。

24. 四种类型转换方式?

  • const_cast:去掉(指针或者引用类型)常量属性的一个类型转换。
  • static_cast:编译器认为安全的类型转换,相关类型的转换(数值类型、指针类型之间),不进行运行期间类型检查,也就是不支持多态类型的向下类型转换。
  • reinterpret_cast:类似于C风格的强制类型转换,不相关的类型转换(数值和指针之间),不进行任何类型检查。
  • dynamic_cast:用于多态类型的向下类型转换,运行期间类型检查(支持RTTI),通过类型转换将父类指针转换子类的指针,从而调用子类对象的接口,转换失败返回空指针或抛出std::bad_cast。

25. C++函数返回值的传递过程?

  • 基本数据类型和指针类型
    • 通常通过寄存器来传递,传递速度更快。
  • 大的结构体或类:
    • 超过了寄存器的范围,可以选择是否开启RVO优化。
      • 未开启RVO优化:有两次拷贝过程,返回值先拷贝一份作为临时变量,再拷贝一份给接收的变量
      • 开启RVO优化:省去两次拷贝带来的开销
  • 此外,C++11引入的移动语义,直接将资源转移,避免拷贝开销

26 . 初始化参数列表?

  • 初始化参数列表是给创建对象后的成员变量进行初始化的,语法格式就是构造函数后跟着冒号和成员变量(初始化参数),按照成员变量声明的顺序。
  • 作用:
      • 引用类型和常量类型的成员变量必须在初始化参数列表中初始化。
      • 基类中没有默认的构造函数时,派生类使用初始化参数列表显式调用有参构造。
      • 一个类里有另一个类的对象作为成员变量时,另一个类中没有默认构造函数,本类需要初始化参数列表显式调用有参构造。
  • 继承关系中,如果其他类的对象作为成员时 -》 创建子类对象的时候 -》 初始化参数列表先调用基类的构造函数,然后按照对象成员声明的顺序构造,最后在调用自己类的构造函数

27. 为什么用初始化参数列表?(与构造函数的区别)

  • 构造函数内赋值会:先进行成员变量的默认初始化,然后在给成员变量赋值,初始化参数列表会避免默认初始化节省了开销。
相关推荐
艾莉丝努力练剑2 小时前
【MYSQL】MYSQL学习的一大重点:数据库基础
linux·运维·服务器·数据库·c++·学习·mysql
齐齐大魔王2 小时前
虚拟机网络无法连接
linux·网络·c++·python·ubuntu
2501_945425152 小时前
C++编译期字符串处理
开发语言·c++·算法
m0_733612212 小时前
模板编译期哈希计算
开发语言·c++·算法
Jordannnnnnnn2 小时前
复试day27
数据结构·c++·算法
仰泳的熊猫2 小时前
题目2311:蓝桥杯2019年第十届省赛真题-Fibonacci 数列与黄金分割
数据结构·c++·算法·蓝桥杯
似水明俊德2 小时前
06-C#
开发语言·c++·算法·c#
ysa0510302 小时前
模拟【打牌游戏】
数据结构·c++·笔记·算法
ht巷子3 小时前
boost.asio网络学习:Http Server
网络·c++·http