说明
本文主要记录一些 C++ 知识点,记录的知识点没有任何规律和归纳,好记性不如烂笔头,供自己查看。
知识点笔记
1. C++ list 和 vector 区别
Vector 是数组,List 是链表
具体来说,区别如下 Chatgpt 答案
数据结构:
vector:基于数组的容器,提供快速的随机访问能力,即可以在常数时间内访问任何元素。然而,向vector中间插入或删除元素可能较慢,因为这可能导致内存中大量元素的移动。
list:基于双向链表的容器,不支持直接的随机访问。这种结构使得在任何位置(包括中间)插入和删除元素都非常快速,因为这仅仅涉及到修改指针。
内存分配:
vector:通常以块的形式分配内存,每当其空间不足以容纳更多元素时,就会进行重新分配和复制整个数组到新的内存块。这可以导致较大的内存移动和复制开销。(所以最好使用 reserve 方法提前预留空间,以免每次添加元素都需要重新分配内存)
list:通常为每个新元素单独分配内存。因此,它的内存分配更为频繁,但删除或添加元素时无需移动其他元素。
性能考量:
vector:由于数据连续存储,对缓存友好,这可以提升遍历性能。适合用于经常需要访问元素的场景。
list:由于元素不连续存储,每次访问元素都可能导致缓存未命中,遍历性能较低。但在需要频繁插入和删除的情况下表现更好。
功能特性:
vector 支持直接通过索引访问元素(例如 v[3]),而 list 不支持这种操作,必须通过迭代器遍历访问。
list 允许进行快速的双向遍历,而 vector 则优化了向后遍历。
适用场景:
vector:适合元素数量变化不大,或主要在末尾添加或删除元素的情况。
list:适合元素数量频繁变动,或在列表中间频繁插入和删除元素的场景。
2. C++ 类的拷贝创建,如果成员中有指针,怎么做
该问题本质是说如何在拷贝创建是否需要进行深拷贝
所以需要将这个问题分开讨论
不需要对指针进行深拷贝,即创建出的实例和之前的实例共用这个地址里的数据,这样减少拷贝。但是如果该地址成员是一个 raw ptr, 会很难处理。如果在析构中 free,会出现 double free,如果不 free,会内存泄漏。所以最好是使用 shared_ptr 解决该类问题
需要进行深拷贝,即你需要创建出的实例拷贝地址数据,创建完全新的。例如 stl 中 vector 就是如此。这时需要定义拷贝构造函数和拷贝赋值运算符,以实现深拷贝。
class MyClass {
public:
int* data;
// 构造函数
MyClass(int val) : data(new int(val)) {}
// 拷贝构造函数 auto b = MyClass(a)
MyClass(const MyClass& other) : data(new int(*other.data)) {}
// 拷贝赋值运算符 auto b = a
MyClass& operator=(const MyClass& other) {
if (this != &other) {
int* newData = new int(*other.data);
delete data;
data = newData;
}
return *this;
}
// 析构函数
~MyClass() {
delete data;
}
};
3. 如何提高 C++ 代码效率,有哪些编程基础准则
这个问题非常宽泛,我们可以提炼一些准则,但是并不完全
- 尽量避免数据拷贝,函参一定为指针,大量数据拷贝前一定要反复思考能否不拷贝
- 对于预先可以计算的结果,可以预先计算存在表里,运行时使用查表,比如 sin cos 计算
- 异步编程尽量避免使用锁,使用锁的时候需要特别小心
- 模块间尽量解耦,每个模块自身处理逻辑越简单越好
- 了解 STL 中数据结构,根据特点使用,比如上述的 vector 和 list 的使用
4. C++ 中多态和重载是如何实现的
多态即虚函数,派生类可以重新定义父类中虚函数,底层实现机制设计虚函数表 (vtable) 和虚函数指针 (vptr) 实现
虚函数表(vtable):
- 每个含有虚函数的类都有一个虚函数表。这个表是一个存储类的虚函数地址的数组。如果一个类继承自另一个类,并且重写了虚函数,那么其虚函数表中对应的函数指针会被更新为派生类中的新函数地址。
虚函数指针(vptr):
- 每个对象实例都会包含一个虚函数指针(vptr),这个指针指向该对象所属类的虚函数表。当通过基类的指针或引用调用虚函数时,实际调用的函数是通过vptr找到的虚函数表中的对应函数。
调用过程:
- 当通过基类的指针或引用调用一个虚函数时,程序会首先查看指针所指对象的vptr。
- 通过vptr找到对象的虚函数表。
- 从虚函数表中获取到对应函数的地址。
- 执行函数代码。
重载即同名但函参不同的函数,实现机制为在编译时使用根据函参生成的不同的后缀名区分不同符号