1. 单继承和多继承的虚函数表结构
- 单继承:在单继承中,每个类如果包含虚函数(或从父类继承虚函数),编译器会为这个类创建一个虚函数表(vtable)。类的每个对象都包含一个指向这个虚函数表的指针,称为虚表指针(vptr)。vtable中包含了类的所有虚函数的地址。
- 多继承:在多继承的情况下,由于一个类可以从多个基类继承,可能存在多个vtable。如果基类中都有虚函数,子类的对象会包含多个vptr,分别指向不同基类的vtable。为了正确调用虚函数,编译器需要处理不同基类的vptr指向及偏移。
2. C++ 程序编译过程
C++程序编译过程通常分为以下几个步骤:
- 预处理(Preprocessing):处理宏定义、头文件包含、条件编译等指令。
- 编译(Compilation):将预处理后的代码翻译成汇编代码。
- 汇编(Assembly):将汇编代码转换为机器代码,生成目标文件(object file)。
- 链接(Linking):将目标文件与库文件链接,生成可执行文件。
3. C++ 内存管理
C++提供了多种内存管理方式,包括静态内存分配、栈内存分配和堆内存分配。
- 静态内存:全局变量、静态变量,生命周期贯穿程序整个运行过程。
- 栈内存:局部变量、函数调用栈,生命周期由作用域决定,自动管理。
- 堆内存:动态分配的内存,通过
new
和delete
来管理,需手动释放。
4. 栈和堆的区别
- 栈:由编译器自动管理,速度快,存储局部变量,作用域结束时自动释放。
- 堆:由程序员手动管理(通过
new
和delete
),灵活性高,适合动态内存需求,但易引发内存泄漏。
5. 变量的区别
- 局部变量:定义在函数或代码块内,作用域仅在函数或块内,栈上分配。
- 全局变量:定义在所有函数外部,程序运行期间一直存在。
- 静态变量:局部静态变量在函数内定义但跨函数调用保持值,全局静态变量作用域限制在声明文件。
6. 全局变量定义在头文件中有什么问题?
将全局变量定义在头文件中可能导致重复定义问题。每个包含该头文件的源文件都会有该变量的定义,导致链接错误。解决方案是使用extern
声明全局变量,并在一个源文件中定义它。
7. 内存对齐
内存对齐是指将数据放在内存中特定的边界地址上,以提高内存访问效率。编译器通过填充字节使得数据对齐到适合的边界。常见对齐边界有1、2、4、8字节等。
8. 什么是内存泄露
内存泄露是指程序在堆上分配内存后未能正确释放,导致内存无法被再次使用,最终可能耗尽系统内存。
9. 怎么防止内存泄漏?内存泄漏检测工具的原理?
- 防止内存泄漏:使用智能指针(如
std::unique_ptr
、std::shared_ptr
),避免手动管理内存。养成良好编码习惯,确保每次new
都有对应的delete
。 - 内存泄漏检测工具:工具如Valgrind、AddressSanitizer通过监控内存分配和释放操作,记录未释放的内存块,帮助定位内存泄漏点。
10. 智能指针有哪几种?智能指针的实现原理?
- 智能指针种类:
std::unique_ptr
:独占所有权,不能复制,只能转移所有权。std::shared_ptr
:共享所有权,通过引用计数管理。std::weak_ptr
:不控制所有权,配合shared_ptr
使用,防止循环引用。
- 实现原理:智能指针类封装了指针操作,提供构造、析构、拷贝、赋值等操作。在析构时自动释放资源,以防止内存泄漏。
11. 智能指针应用举例
例如,在管理动态创建的对象生命周期时,使用std::shared_ptr
来确保对象在不再需要时自动删除:
std::shared_ptr<MyClass> p1(new MyClass());
std::shared_ptr<MyClass> p2 = p1; // p1 和 p2 共享所有权
12. 一个 unique_ptr
怎么赋值给另一个 unique_ptr
对象?
unique_ptr
不能直接赋值,因为它禁止拷贝。可以使用std::move
将所有权从一个unique_ptr
转移到另一个:
std::unique_ptr<MyClass> p1(new MyClass());
std::unique_ptr<MyClass> p2 = std::move(p1);
13. 使用智能指针会出现什么问题?怎么解决?
- 循环引用问题:
std::shared_ptr
可能会导致循环引用,从而无法释放内存。解决方法是使用std::weak_ptr
打破循环。 - 性能开销:智能指针带有额外的开销,如引用计数管理。可以在性能敏感区域谨慎使用。
14. VS检测内存泄漏,定位泄漏代码位置方法
在Visual Studio中,可以通过启用内存泄漏检测功能和使用_CrtDumpMemoryLeaks()
函数来定位内存泄漏。通过在程序结束时调用该函数,可以输出所有未释放的内存块信息。
15. 深拷贝与浅拷贝
- 浅拷贝:只复制对象的指针,不复制实际内容。两个对象共享同一内存块。
- 深拷贝:复制对象的内容,每个对象都有自己的内存块,互不影响。
16. 虚拟内存
虚拟内存是操作系统提供的一种技术,将程序的虚拟地址空间映射到物理内存或磁盘上。它允许程序使用比实际物理内存更多的地址空间。
17. 语言对比
C++与其他语言对比,可能包括对比C、Python、Java等。需要根据具体情况讨论语言的特点、应用场景和优缺点。
18. C++ 11 新特性
C++11引入了许多新特性,如:
auto
关键字用于类型推导。nullptr
表示空指针。lambda
表达式,简化了函数对象。std::thread
,标准库中的多线程支持。std::unique_ptr
和std::shared_ptr
智能指针。
19. C 和 C++ 的区别
- 面向对象:C++支持类和对象,C不支持。
- 泛型编程:C++支持模板,C不支持。
- 异常处理:C++支持异常处理,C通常使用返回值进行错误处理。
- 内存管理:C++提供了智能指针等自动管理内存工具,C需要手动管理。
20. Python 和 C++ 的区别
- 语法:Python语法简洁,适合快速开发;C++语法复杂,更接近底层。
- 性能:C++编译后的程序性能高,适用于性能敏感的领域;Python是解释型语言,运行速度较慢。
- 内存管理:C++需要手动管理内存,Python通过垃圾回收自动管理内存。
21. 面向对象
面向对象编程(OOP)是一种编程范式,它使用对象和类的概念来设计程序。OOP的核心是封装、继承和多态。
22. 什么是面向对象?面向对象的三大特性
面向对象是一种编程范式,强调将数据和操作数据的函数封装在对象内。三大特性包括:
- 封装:将数据和方法绑定在一起,对外隐藏实现细节。
- 继承:允许新类继承现有类的特性和行为。
- 多态:允许不同类的对象通过相同的接口调用不同的行为。
23. 重
载、重写、隐藏的区别
- 重载:同一作用域中函数名相同但参数列表不同。
- 重写:派生类重新定义基类中的虚函数,函数签名相同。
- 隐藏:派生类中的函数屏蔽了基类中同名函数,不要求签名相同。
24. 如何理解 C++ 是面向对象编程
C++通过类和对象实现面向对象编程的三大特性,允许程序员通过封装、继承和多态创建模块化、可复用的代码。
25. 什么是多态?多态如何实现?
多态是指不同类的对象可以通过相同的接口调用不同的实现。C++中多态通过虚函数实现,基类中声明虚函数,派生类可以重写该函数。
26. 静态多态与动态多态
- 静态多态:在编译时确定函数调用,如函数重载和模板。
- 动态多态:在运行时确定函数调用,如虚函数。
27. 类相关
涉及类的设计和使用,包括构造函数、析构函数、拷贝构造函数、赋值运算符等。
28. 什么是虚函数?什么是纯虚函数?
- 虚函数:基类中声明的可以在派生类中重写的函数,使用
virtual
关键字声明。 - 纯虚函数:没有实现的虚函数,必须在派生类中重写,基类不能实例化,使用
= 0
声明。
29. 虚函数和纯虚函数的区别?
- 虚函数:基类中有默认实现,可以在派生类中重写。
- 纯虚函数:基类中没有实现,必须在派生类中实现,通常用于定义接口。
30. 虚函数的实现机制
虚函数通过虚函数表(vtable)实现。每个含有虚函数的类都有一个vtable,存储类中虚函数的地址。对象的vptr指向这个表,通过vptr访问vtable,实现动态绑定。