C++
STL
C++的标准模板库(STL)包括多种容器、迭代器、算法和函数适配器。
- 向量(vector)
- 列表(list)
- 双端队列(deque)
- 集合(set)
- 映射(map)
- 无序集合(unordered_set)
- 无序映射(unordered_map)
- 栈(stack)
- 队列(queue)
- 优先队列(priority_queue)
array:
array
是固定大小的数组,在内存中是连续的,支持高效随机访问,当内存容量不足的时候 ,需要手动重新分配一个更大想空间,然后复制内容到新数组,释放旧数组。
vector:
vector
是一个动态数组,提供快速的随机访问(O(1)),但在中间插入和删除元素相对较慢(O(n))。
list:
list
是一个双向链表,提供快速的插入和删除(O(1)),但随机访问较慢(O(n))。
map与unordered_map的区别:
map
通常实现为红黑树,红黑树是平衡二叉树,提供顺序迭代,查找、插入和删除操作的时间复杂度为O(log n)。每个节点储存着键值对。unordered_map
通常实现为哈希表,提供更快的平均时间复杂度查找(O(1)),但在最坏情况下可能退化为O(n);它不提供顺序迭代。
C++引用是什么,和指针有什么区别
- C++ 引用:
- 引用相当于给变量起了另一个名字,它是一个变量的别名。
- 引用在定义时必须初始化,并且一旦绑定到一个变量,就不能再绑定到其他变量。
- 引用不能为
null
,总是引用一个有效的存储位置。 - 使用引用时,语法上像直接操作普通变量一样,不需要解引用操作。
- 引用的内存占用通常与指针相同,但是引用的使用更安全,因为它保证了引用的有效性。
- C++ 指针:
- 指针是一个变量,存储的是另一个变量的地址。
- 指针可以在声明后被初始化为任何值,包括
nullptr
(C++11中引入的空指针类型),也可以在声明后再被赋值。 - 指针可以被重新赋值以指向另一个变量。
- 使用指针时需要通过解引用操作(
*
)来访问目标变量的值。 - 指针可以执行算术操作,如增加或减少指针的值,指向连续内存块中的下一个或前一个元素。
虚函数
虚函数表是C++中实现多态性的关键机制,它是一种用于支持动态绑定的技术,允许在运行时确定调用哪个版本的虚函数,而不是在编译时确定。
在包含虚函数的类中,编译器会为每个包含虚函数的类生成一个虚函数表。虚函数表是一个由指向虚函数地址的指针组成的数组,其中每个元素指向类中的一个虚函数。当类的对象被创建时,会包含一个指向相应虚函数表的指针(通常称为虚指针),这样当调用虚函数时,实际上是通过虚指针找到对应的虚函数表,然后在虚函数表中查找相应的函数地址,并执行对应的函数。
虚函数怎么存的?
虚函数通常存储在一个特殊的表中,称为虚函数表(vtable)。每个拥有虚函数的对象都有一个指针,指向相关的虚函数表。
虚函数在内存表中的顺序怎么确定的?
虚函数在虚函数表中的顺序由它们在类中声明的顺序决定。如果派生类覆盖了基类的虚函数,派生类的虚函数表中会用派生类的函数地址替换基类函数的地址。如果派生类添加了新的虚函数,这些新函数会被添加到表的末尾。
虚函数和纯虚函数的区别:
- 虚函数:在基类中声明为虚的函数,可以在派生类中重写。基类中的虚函数通常有自己的实现,但它们可以被派生类中的同名函数覆盖。当通过基类指针或引用调用函数时,会根据对象的实际类型调用相应的函数,这是多态的一种体现。
- 纯虚函数:在基类中声明为纯虚的函数没有具体的实现(使用
= 0
语法定义)。如果一个类包含纯虚函数,则该类称为抽象类,不能实例化。纯虚函数强制要求派生类提供具体的实现,这样才能实例化派生类的对象。
多态的实现:
- 多态主要通过虚函数实现。当函数在基类中被声明为虚函数后,它可以在派生类中被重写,从而改变其行为。
- 在运行时,当通过基类的指针或引用调用一个虚函数时,C++的运行时系统会根据对象的实际类型来确定应该调用哪个类的函数版本,这是动态绑定。
- 为实现动态绑定,C++在每个含有虚函数的对象中维护一个虚表(v-table),其中存储了指向虚函数实现的指针。这允许在运行时根据对象的实际类型调用适当的方法。
多线程
智能指针是线程安全吗?
智能指针在某些操作上是线程安全的,但这取决于具体实现。例如,在C++中,std::shared_ptr
的引用计数增加和减少是原子操作,因此是线程安全的。但是,两个线程不能同时对同一个智能指针对象进行读写操作,因为对象的状态可能会不一致。
多线程如何安全的操作队列
在多线程环境中安全地操作队列通常需要使用互斥锁(mutex)来保护数据结构,防止竞态条件。条件变量(cond)用于同步,允许线程睡眠等待某个条件成立。
当线程对条件变量调用wait()
时,它必须已经持有互斥锁。调用wait()
后,线程将释放互斥锁并进入睡眠状态,等待条件变量被唤醒。当cond
被signal()
或broadcast()
唤醒时,等待的线程将被唤醒,但在它可以继续执行之前,它必须重新获得先前释放的互斥锁。只有当线程重新获得了互斥锁,它才能继续执行。