一.指针和引用的区别
1.指针和引用的定义是什么?
指针是一个变量,其值是另一个变量的地址。声明指针时用*符号。
引用是一个别名,它是在已存在的变量上创建的。引用在声明的时候用&符号
2.指针和引用在汇编层面上的表现是什么 ?
在汇编层面上,指针和引用本质上都是通过存储目标对象的地址实现间接访问。
引用通常被编译器认为是指向对象的指针,两者的底层表现几乎是一致的。
无论是指针还是引用,访问其数据的时候,都会取出存储的地址,再访问对应的内存内容
我通过gdb调试工具,调试指针和引用的时候,发现无论是指针还是引用,gdb显示的地址和内容都是基本一致的,进一步说明了它们底层是没有本质区别的。
二.const关键字
1. const关键字的作用是什么?
const
用于限定变量的值不可被修改,可以修饰变量、指针、成员函数、函数参数和返回值等,还可以避免多次内存分配。
2. const int *p
、int *const p
和 const int *const p
有什么区别?
const int *p
:指向常量的指针,不能通过p
修改指向的值,但可以改变p
的指向。
int *const p
:常量指针,p
的指向不能变,但可以通过p
修改指向的值。
const int *const p
:指向常量的常量指针,既不能修改指向的值,也不能改变指向。
3. 为什么要用 const
修饰函数参数和返回值?
防止函数内部修改参数值,提高代码安全性和可读性。
对返回值加
const
可以防止调用者修改返回对象(如返回引用或指针时)。
4. const
成员函数的作用是什么?
const
成员函数保证不会修改类的成员变量(除mutable
修饰的成员外),只能调用其他const
成员函数。
5.const
和 #define
有什么区别?
const
有类型检查,作用于编译阶段;
#define
是预处理器宏替换,没有类型检查,仅做简单文本替换。
三.static关键字
1. static
关键字的作用有哪些?
修饰局部变量:使变量在函数调用结束后依然保留其值(静态存储期)。
修饰全局变量/函数:限制变量或函数的作用域只在当前文件(内部链接)。
修饰类成员:使成员变量或成员函数属于类本身,而不是某个对象。
2. static
修饰的局部变量和普通局部变量有什么区别?
普通局部变量每次进入作用域都会重新创建,离开作用域销毁。
static
局部变量只初始化一次,生命周期贯穿程序始终,离开作用域不销毁。
3. static
修饰的全局变量和函数有什么作用?
使全局变量或函数只在当前源文件内可见,不能被其他文件访问,实现"文件私有"。
4. 类的 static
成员变量和成员函数有什么特点?
属于类本身,不属于某个对象。
所有对象共享同一份静态成员变量。
静态成员函数不能访问非静态成员变量和成员函数。
5. 静态成员变量如何初始化?
类内声明,类外初始化。
四.new malloc delete free 关键字
1. new/delete
和 malloc/free
有什么区别?
new/delete
是 C++ 的操作符,malloc/free
是 C 的库函数。
new
自动计算所需内存并返回对象类型指针,malloc
需手动计算字节数并返回void*
,需强制类型转换。
new
分配失败会抛出异常,malloc
失败返回NULL
。
new
在自由存储区(free store)分配内存,malloc
在堆(heap)分配内存。delete
释放内存时需要对象类型指针,free
需要void*
指针。
new/delete
会调用构造和析构函数,malloc/free
不会。
2. 什么时候应该用 new/delete
,什么时候用 malloc/free
?
在 C++ 中建议使用
new/delete
,因为它们支持对象的构造和析构。在 C 代码或需要与 C 库兼容时使用
malloc/free
。
3. 释放内存后为什么要将指针置为 nullptr
或 NULL
?
防止出现野指针(悬空指针)问题,避免后续误用已释放的内存
4. delete
和 free
释放内存后,内存会立即释放吗?指针会自动变为 nullptr
吗?
内存释放由操作系统管理,不一定立即回收。
指针不会自动变为
nullptr
,需要手动赋值。
5. 用 new[]
分配的数组可以用 delete
释放吗?
不可以。用
new[]
分配的数组必须用delete[]
释放,否则会导致未定义行为。
五.extern 关键字
1. extern 关键字的作用是什么?
用于声明变量或函数在其他文件或作用域中定义,不是定义,只做声明。
让编译器知道该符号存在,链接时再去找具体实现。
2. C++ 调用 C 代码时 extern "C" 的作用?
extern "C"
用于告诉编译器用 C 的方式进行符号修饰(防止C++的 name mangling),实现 C/C++ 混合编程。
六.strcpy sprintf memcpy 关键字
1.请简述 strcpy、sprintf 和 memcpy 的主要区别及各自适用的场景。
strcpy
用于字符串拷贝,遇到\0
结束,适合拷贝 C 风格字符串。
sprintf
用于格式化字符串,可以将多个变量格式化为字符串。
memcpy
用于内存块拷贝,按指定字节数复制,适合拷贝任意类型的数据(包括二进制数据),不会遇到\0
停止。
2.如果要拷贝一个包含二进制数据(可能包含 \0
)的缓冲区,应该使用 strcpy、sprintf 还是 memcpy?为什么?
应该使用
memcpy
,因为strcpy
和sprintf
都会在遇到\0
时停止拷贝,无法正确处理包含\0
的二进制数据。而memcpy
可以按照指定的字节数完整拷贝所有内容,包括中间的\0
字节。
六.C和C++中的类型转换
1.C中的类型转换是怎么转换?
(T)exp 或 exp(T) ------ 语法简单,但不安全,容易出错。
2.C++中的类型转换是什么样的?
static_cast<T>(exp) ------ 最常用,类型安全,编译器检查。 (就用这个就对了)
const_cast<T>(exp) ------ 改变const/volatile属性。 (不建议用)
dynamic_cast<T>(exp) ------ 用于多态类型的安全转换。 (不建议用)
reinterpret_cast<T>(exp) ------ 底层指针或类型强制转换,风险大。(别用)
七.默认拷贝构造函数
1.如果类成员变量是另一个类对象,什么时候必须生成默认拷贝构造函数?
当成员变量是类对象且该成员类有默认拷贝构造函数时,为了让成员类的拷贝构造函数能被正确调用,必须为当前类生成默认拷贝构造函数。
2.类继承自一个基类,基类有默认拷贝构造函数,子类拷贝时会发生什么?
子类执行拷贝构造时,会先调用父类的拷贝构造函数。如果没有为子类生成默认拷贝构造函数,父类的拷贝构造函数将无法被调用。
3.什么时候不能只依赖编译器生成的默认拷贝构造函数?
当类成员变量或基类有自定义拷贝构造函数,或者类中有指针、资源管理等特殊需求时,不能只依赖默认拷贝构造函数,需要自定义以保证拷贝行为符合预期。
八.深拷贝,浅拷贝
1.什么是浅拷贝和深拷贝?它们的区别是什么?
浅拷贝只复制指针的值(地址),拷贝后多个对象的指针成员指向同一块内存;深拷贝会分配新内存,并复制实际数据,拷贝后对象互不影响。
2.在什么情况下会发生浅拷贝?会带来什么问题?
默认拷贝构造函数和赋值操作符执行的是浅拷贝。如果类中有指针成员,浅拷贝会导致多个对象指向同一内存,可能出现重复释放、数据混乱等问题。
3.请举例说明函数参数为类对象(值传递)时,浅拷贝和深拷贝的影响。
值传递会调用拷贝构造函数。如果是浅拷贝,函数内外的对象指针成员指向同一内存,函数结束时析构会导致外部对象的指针悬挂。深拷贝则不会有此问题。
4.如何实现深拷贝?
需要自定义拷贝构造函数和赋值操作符,在其中为指针成员分配新内存,并复制原对象的数据内容。
5.返回类对象时(如 return obj;
),浅拷贝和深拷贝有何影响?
返回类对象会调用拷贝构造函数。浅拷贝会导致多个对象共享同一内存,析构时可能重复释放。深拷贝则每个对象有独立内存,安全可靠。
九.struct 和 class 关键字
1.C++ 中 struct 和 class 的主要区别是什么?
C++ 中 struct 和 class 的唯一区别是默认访问权限不同。struct 的成员默认是 public,class 的成员默认是 private。除此之外,struct 和 class 在继承、成员函数、构造析构等方面完全等价。
2.struct 能否有成员函数、构造函数、继承和访问控制?
可以。C++ 中 struct 和 class 一样,可以有成员函数、构造函数、析构函数、继承、访问控制(public/protected/private)等特性。
3.实际开发中 struct 和 class 应该如何选择?
一般用于只包含数据成员、没有复杂行为的数据结构时用 struct;有封装、行为和访问控制需求时用 class。但两者功能上没有本质区别,主要是风格和可读性。
**十.C++**内存管理
1.什么是内存泄漏?如何避免?
程序分配了内存但未释放,导致内存无法再被使用。避免方法:及时释放内存、使用智能指针(如
unique_ptr
、shared_ptr
)、养成良好编码习惯。
2.什么是野指针?如何产生?如何避免?
指向已释放或未初始化内存的指针。产生原因:指针未初始化、释放后未置空、返回局部变量地址。避免方法:指针初始化为 nullptr,释放后置空,避免返回局部变量地址。
3.C++11 引入了哪些内存管理相关的新特性?
智能指针(
unique_ptr
、shared_ptr
、weak_ptr
)、移动语义(std::move
)、内存对齐(alignas
、alignof
)等。
4.什么是内存对齐?为什么需要内存对齐?
内存对齐是指数据在内存中的存储地址要按一定规则排列,提高访问效率,避免硬件异常。C++ 可用
alignas
、alignof
控制对齐方式。
5.nullptr和NULL的区别,为什么C++更推荐用nullptr?
nullptr 是 C++11 引入的专用空指针类型,类型安全,能够明确区分指针和整数,避免与整型混淆。
使用 nullptr 可以让编译器准确识别空指针,防止因 NULL 被当作 0 导致的重载解析歧义和类型不安全等问题,提升代码的可读性和健壮性,因此推荐在现代 C++ 中用 nullptr 替代传统的 NULL。
十一.vector容器
1.vector 的底层数据结构是什么?
vector 本质上是一个可以动态扩容的连续数组。它通过三个指针(_M_start, _M_finish, _M_end_of_storage)管理内存的起始、结束和容量边界,实现高效的随机访问和动态扩容。
2. vector 插入和删除元素时,底层会发生什么?
插入元素时,vector 会检查空间是否足够,不够则分配更大的内存(通常翻倍),并将原有元素拷贝到新内存。删除元素时,只是移动指针或元素,内存不会立即释放,只有通过 swap 空容器或 shrink_to_fit 才能释放未用内存。
3. vector 的内存增长机制是怎样的?
vector 的内存通常按倍数增长(如 在Linux下gcc/g++ 下按2倍增长,在Windows下是按1.5倍增长),每次扩容会分配更大的内存,将原有数据拷贝过去,然后释放旧内存。内存空间只会增加不会自动减少。
4. vector 的 reserve 和 resize 有什么区别?
reserve 只增加 capacity,不改变 size,不会初始化新元素。
resize 会改变 size,增加时新元素有初始值,减少时多余元素被析构。
reserve 适合预分配空间,避免多次扩容;resize 适合需要保证元素数量时使用。
5. vector 为什么不能存放引用类型?
引用不是对象,不能分配内存,也不能改变绑定,vector 需要管理元素的内存和生命周期,因此不能存放引用类型。
6. vector 的元素是指针时,析构时会自动释放指针指向的内存吗?
不会。vector 只会析构指针本身,不会释放指针指向的堆内存,需要手动释放。
7. vector 的 at 和操作符[] 有什么区别?
操作符[] 不检查越界,at 会检查越界并抛出异常。两者都返回元素的引用,可以直接修改元素。
8. 如何释放 vector 未使用的内存?
可以通过 swap 一个空容器或调用 shrink_to_fit 来释放未使用的内存空间。
9. vector 支持随机访问吗?为什么?
支持。因为 vector 底层是连续内存,可以通过下标直接访问任意元素,时间复杂度为 O(1)。
10. vector 扩容时为什么要拷贝元素?
因为新分配的内存地址不同,必须将原有元素拷贝到新内存,保证数据连续性和正确性。
11.请简述空间配置器(Allocator)在 C++ STL vector 中的作用
空间配置器(Allocator)是 STL 容器用于管理底层内存分配和释放的机制。vector 通过 allocator 动态分配、释放、构造和析构元素对象,保证元素在一块连续内存中存储。
12.请简述 STL 容器(如 vector)空间配置器(Allocator)常用的方法及其作用。
allocate(size_t n)
分配 n 个元素所需的原始内存(但不构造对象)。
deallocate(pointer p, size_t n)
释放通过 allocate 获得的内存。
construct(pointer p, Args&&... args)
在指定内存地址 p 上使用 placement new 构造对象。
destroy(pointer p)
显式调用对象的析构函数,销毁对象但不释放内存。
address(reference x)
获取对象 x 的实际地址。
max_size()
返回分配器能分配的最大元素个数。
十二.list容器
1.list 的底层数据结构是什么?
list 底层实现是一个双向循环链表,每个节点有指向前后节点的指针。
2.list 的节点结构包含哪些主要成员?
节点结构包含存储数据的
_M_storage
,指向下一个节点的_M_next
,指向上一个节点的_M_prev
。
3.list 为什么需要一个空节点/头节点?它的作用是什么?
空节点用于表示双向循环链表的头尾,方便链表的插入、删除等操作,避免特殊情况的判断。
4.list 的迭代器如何实现++和--操作?
++ 操作让迭代器指向下一个节点,-- 操作让迭代器指向上一个节点。
5.如何通过迭代器获取 list 的第一个和最后一个元素?
第一个元素是空节点的下一个节点,最后一个元素是空节点的上一个节点。
6.list 的插入和删除操作的时间复杂度是多少?
在任意位置插入和删除元素的时间复杂度都是 O(1)。
7.list 支持随机访问吗?为什么?
不支持。因为链表节点不连续,无法通过下标直接访问,只能顺序遍历。
8.list 的 splice 操作有什么特点?
splice 可以在常数时间内把一个 list 的部分或全部节点移动到另一个 list,无需拷贝或移动元素。
9.list 的 remove 和 remove_if 有什么区别?
remove 删除所有等于指定值的元素,remove_if 根据谓词删除满足条件的元素。
10.list 的 clear 操作会释放节点内存吗?
会,clear 会销毁所有节点并释放其占用的内存。
十三.deque容器
1.deque 的底层数据结构是什么?
deque 底层是由一组指针数组(map)管理的多个定长连续内存块组成,形成双向开口的连续线性空间,实现双端数组。
2.deque 的 _M_map、_M_start、_M_finish 分别是什么作用?
_M_map 是指针数组,存储各个连续空间的指针;_M_start 记录首个连续空间的信息;_M_finish 记录最后一个连续空间的信息。
3.deque 的迭代器结构包含哪些主要成员?
含 _M_cur(当前元素)、_M_first(当前块首地址)、_M_last(当前块末尾地址)、_M_node(指向 map 中当前块指针)。
4.deque 在 push_back 或 pop_back 时,底层会发生什么?
push_back 时会检查当前连续空间和 map 是否足够,不够则分配新空间或扩展 map;pop_back 时如果当前连续空间无数据则释放该空间。
十四.vector && list && deque @compare
1.在什么场景下应该优先选择 vector?为什么?
当需要高效的随机访问(如通过下标访问元素),且对插入和删除效率要求不高时,应选择 vector,因为其底层是连续内存,支持 O(1) 随机访问。
2.什么时候应该使用 list?
当需要频繁在任意位置插入和删除元素,且不关心随机访问效率时,应使用 list,因为其底层是双向链表,插入和删除效率高。
3.deque 适合什么场景?
当既需要高效的随机访问,又需要在两端高效插入和删除元素时,应使用 deque,因为其底层支持双端操作和 O(1) 随机访问。
4.vector、list、deque 的插入和删除操作的时间复杂度分别是多少?
vector 在尾部插入/删除为 O(1),中间为 O(n);list 任意位置插入/删除为 O(1)(已知位置);deque 在两端插入/删除为 O(1)。
5.vector、list、deque 是否支持随机访问?
vector 和 deque 支持 O(1) 随机访问,list 不支持随机访问,只能顺序遍历。
6.如果需要频繁在容器头部插入元素,vector、list、deque 哪个效率最高?
list 和 deque 头部插入效率高,vector 头部插入效率低(需整体移动元素)。
十五.priority_queue
1.priority_queue 的底层数据结构是如何实现的?
底层用堆(完全二叉树)实现,通常用数组(如 vector)存储,保证根节点是最大(最大堆)或最小(最小堆)元素。
2.priority_queue 的 push 和 pop 操作底层是如何实现的?
push 时先把新元素加到数组末尾,再上浮重构堆;pop 时将首元素与末元素交换,删除末元素,再下沉重构堆。
3.priority_queue 默认使用什么容器?可以换成什么?
默认使用 vector,也可以用 deque,但不能用 list,因为 list 不支持随机访问。
4.priority_queue 如何实现最大堆和最小堆?
通过 less<T>(最大堆)或 greater<T>(最小堆)作为比较器,决定父子节点大小关系。
5.priority_queue 的堆结构中,父子节点的索引关系是什么?
对于索引为 n 的节点,左子节点索引为 2n+1,右子节点索引为 2n+2。
6.priority_queue 能否遍历所有元素?为什么?
不能直接遍历,因为 priority_queue 只保证堆顶元素有序,其他元素无序。
7.priority_queue 的底层堆是如何构建的?
通过 STL 的 make_heap、push_heap、pop_heap 算法,根据比较器 comp 构建和维护堆结构。
8.priority_queue 适合哪些应用场景?
适合需要频繁获取最大(或最小)元素的场景,如任务调度、优先级管理、堆排序、图的最短路径算法等。
9.priority_queue 和普通 queue 有什么区别?
queue 是先进先出,priority_queue 每次弹出的是优先级最高的元素(堆顶),不是按插入顺序。
十六.set && multiset && map && multimap 容器
1.set 和 multiset 的底层数据结构是什么?为什么选择这种结构?
底层是红黑树(平衡二叉搜索树),因为它能保证元素有序且插入、删除、查找的时间复杂度为 O(log n)。
2.set 和 multiset 有什么区别?
set 不允许重复元素,multiset 允许重复元素,二者都自动有序。
3.map 和 multimap 有什么区别?
map 的 key 唯一,multimap 的 key 可以重复,二者都自动有序,底层都是红黑树。
4.set、multiset、map、multimap 的查找、插入、删除的时间复杂度是多少?
都是 O(log n)。
5.set/multiset 的元素能否修改?为什么?
不能直接修改,因为修改后可能破坏红黑树的有序性。
我们应该 先用迭代器找到要修改的元素;删除该元素;插入修改后的新元素。
6.map 和 multimap 的 value_type 是什么?
都是 pair<const Key, T>。
7.set/multiset 的 insert 返回值是什么?
set 的 insert 返回 pair<iterator, bool>,multiset 的 insert 返回 iterator。
8.map/multimap 的 insert 返回值是什么?
map 的 insert 返回 pair<iterator, bool>,multimap 的 insert 返回 iterator。
9.set/multiset 支持 emplace 吗?和 insert 有什么区别?
支持。emplace 直接在容器内部原地构造元素,避免不必要的拷贝或移动。
10.set/multiset 的 key 可以是自定义类型吗?需要注意什么?
可以,但必须提供
<
运算符或自定义比较器,保证元素可排序。
十七.unordered_set && unordered_multiset && unordered_map && unordered_multimap 容器
1.unordered_set 的底层实现是什么?
哈希表
2.unordered_map 的底层实现是什么?
哈希表(通常是开链地址法)。
3.unordered_map 如何处理哈希冲突?
unordered_map 通过开链法处理哈希冲突,每个桶存储一个链表,冲突元素插入链表,查找和删除时在链表中顺序查找。负载因子过高时自动 rehash,保证性能。
4.unordered_map 的 rehash 机制是什么?
负载因子超阈值时自动扩容并重新分配桶
5.unordered_map/unordered_set 的查找、插入、删除的时间复杂度是多少?
平均 O(1),最坏 O(n)。
十八.iterator 迭代器
1.迭代器的主要作用是什么?
提供一种访问容器内元素而不暴露容器内部实现的方式,实现解引用和成员访问,统一不同容器的访问方式。
2.STL 迭代器底层实现的核心思想是什么?
不是面向对象思想,而是范式思想,通过泛型编程实现通用算法与容器解耦。
3.STL 迭代器有哪些类型?请简要说明各自的特点。
输入迭代器(只读,单向遍历)、输出迭代器(只写,单向遍历)、前向迭代器(可读写,单向遍历)、双向迭代器(可读写,双向遍历)、随机访问迭代器(支持下标、加减法,功能最强)。
4.为什么需要不同类型的迭代器?
不同容器和算法对遍历和操作元素的能力需求不同,迭代器类型决定了可用的操作和算法适用性。
5.迭代器与指针有什么关系?
代器本质上是一种广义的指针,很多容器的迭代器实现就是指针,但也可以是自定义对象,统一了容器访问接口。
6.STL 算法如 find、sort、reverse 等为何能适用于不同容器?
因为这些算法基于迭代器编写,只要容器提供相应类型的迭代器即可适配。
7.迭代器类型之间的关系是什么?
随机访问迭代器 > 双向迭代器 > 前向迭代器 > 输入/输出迭代器,功能逐级增强,兼容性逐级递减。
十九.STL容器线程安全
1.STL 容器默认是线程安全的吗?为什么?
不是线程安全的,STL 容器内部没有加锁,多个线程同时操作同一个容器会有数据竞争。
2.vector、deque 等容器在多线程下存在哪些安全隐患?
扩缩容时会移动或重分配内存,插入和删除会导致节点关系变化,可能引发数据竞争和未定义行为。
3.红黑树四兄弟(set、map、multiset、multimap)在多线程下插入/删除会发生什么?
插入和删除会引起 rebalance(重平衡),导致结构变化,线程不安全。
4.unordered 系列容器在多线程下如何保证线程安全?
可以提前拿到所有 key,避免并发修改同一个 key 时加锁,或使用原子操作。
5.读多写少的场景下,如何提升 STL 容器的并发性能?
可以使用读写锁,读操作共享锁,写操作独占锁。
6.list 容器在多线程下如何优化并发插入/删除?
可以采用生产者-消费者模型,每个线程操作自己的部分,减少锁竞争。
7.为什么 deque 及基于 deque 的容器不适合生产者-消费者模型?
因为 deque 的扩缩容和节点关系复杂,容易引发线程安全问题。
8.除了加锁,还有哪些避免加锁的并发优化方法?
可以提前分配好节点,将数据分成多份,每个线程只操作自己的数据,缺点是可能导致线程饿死。
9. 在多线程下,如何安全地对同一个 key 进行并发修改?
需要对同一个 key 加锁,或使用原子操作。
10.vector 的 resize 和 reserve 能否解决多线程下的所有安全问题?
不能,只能解决扩缩容问题,不能解决节点关系变化导致的线程安全问题。
二十.面向对象思想
1.什么是封装?C++ 如何实现封装?
封装是隐藏实现细节、实现模块化的机制。C++ 通过类的访问权限(public、protected、private)实现封装,友元类可以打破 private 限定。
2.C++ 中 public、protected、private 有什么区别?
public 对所有对象开放,protected 只对子类开放,private 只对本类开放。
3.什么是继承?继承的主要目的是什么?
继承是子类在无需修改原有类的情况下扩展功能的机制,主要目的是代码复用和功能扩展。
4.C++ 支持哪几种继承方式?
支持 public、protected、private 继承,影响基类成员在子类中的访问权限。
5.基类成员在子类中的最高权限是什么?如何通过 using 修改?
基类成员在子类中的最高权限由继承方式决定,可以通过 using 语句修改基类成员在子类中的权限。
6.C++ 支持多继承吗?多继承可能带来什么问题?
支持多继承,可能带来二义性和菱形继承问题。
7.什么是接口继承?C++ 如何实现接口继承?
接口继承是只继承接口(纯虚函数),C++ 通过纯虚函数实现接口继承。
8.什么是多态?多态的目的是什么?
多态是一个接口多种实现形态,目的是通过接口重用和增强扩展性。
9.C++ 中的静态多态和动态多态分别如何实现?
静态多态通过函数重载实现,动态多态通过虚函数重写实现。
10.什么是虚函数?如何实现运行时多态?
函数是用 virtual 修饰的成员函数,基类指针/引用调用虚函数时会根据实际对象类型调用对应实现,实现运行时多态。
二十一.多态(补充)
1.什么是早绑定和晚绑定?
早绑定是编译时确定函数地址,晚绑定是运行时通过虚函数表确定函数地址。
2.C++ 中虚函数表(vtable)和虚表指针(vptr)是什么?
vtable 是存放虚函数地址的一维数组,每个含虚函数的类有一个,vptr 是对象中的指针,指向该类的 vtable,构造函数中初始化。
3.为什么虚函数表指针要在构造函数中初始化?
保证对象创建时能正确指向对应类的虚函数表,实现多态调用。
二十二.菱形继承
1.什么是菱形继承?请举例说明。
菱形继承是指一个子类继承多个父类,而这些父类又继承自同一个基类,导致继承关系呈菱形结构。
2.菱形继承会带来哪些问题?
会造成数据成员重复(浪费存储空间)和成员访问二义性(不知道访问哪一份基类成员)。
3.C++ 如何解决菱形继承问题?
通过虚继承(virtual 继承),让子类只继承一份共同基类的数据和成员。
4.虚继承的语法是什么?
在继承基类时加上 virtual 关键字,例如:
class B : virtual public A {}
。
5.虚继承的底层实现原理是什么?
通过虚表偏移,父类的虚表指针(vptr)中保存到共同基类的偏移量,保证多重继承时只指向同一个基类实例。
6.什么是虚基类?它的作用是什么?
用 virtual 关键字修饰的基类称为虚基类,主要用于解决多重继承中的菱形继承问题,避免数据成员重复和二义性。