【最全】C++面试题 STL (第五篇)

1.什么是STL?

STL(Standard Template Library,标准模板库)是C++的一个重要组成部分,它包含了一系列容器、迭代器、算法和函数对象,用于简化常见的编程任务。

2.STL主要包含哪些主要组件?

STL主要包含以下四类组件:

  • 容器(Containers):如vector、list、map、set等,用于存储数据。

  • 算法(Algorithms):如sort、find等,用于操作数据。

  • 迭代器(Iterators):提供访问容器内元素的方法。

  • 函数对象(Function Objects):也称为仿函数,支持自定义操作。

3.请解释vector容器和它的特点。

vector是STL中的一个动态数组,它的主要特点包括:

  • 动态大小:与传统数组不同,vector的大小可以在运行时动态改变。

  • 随机访问:支持通过索引直接访问任何元素,访问时间是常数时间复杂度(O(1))。

  • 内存管理:vector在内部管理其存储的内存。当元素被添加到vector中,并且当前分配的内存不足以容纳它们时,它会自动重新分配更多的内存。

  • 灵活性:在vector的末尾添加或删除元素非常高效。但在中间或开始位置插入或删除元素可能会比较慢,因为这可能需要移动现有的元素。

4.vector如何保证元素的连续存储?

vector通过使用动态分配的数组来存储其元素,从而保证元素在内存中的连续存储。当需要添加更多元素而当前内存空间不足时,vector会分配一个新的、更大的内存块,并将现有元素复制到新的内存块中,然后释放旧的内存块。这个过程保持了元素的连续存储。

5.vector扩容的机制是怎样的?

当vector的当前容量不足以容纳新元素时,它会进行扩容。扩容过程通常涉及以下几个步骤:

  1. 分配新内存:根据某种策略(通常是原容量的1.5倍或2倍),在堆上分配一块新的、更大的内存块。

  2. 复制元素:将原vector中的元素复制到新的内存块中。

  3. 释放旧内存:释放原vector所使用的内存块。

  4. 更新指针:将vector的指针指向新的内存块。

这个扩容过程的时间复杂度与元素的数量成正比(O(n)),但在大多数情况下,由于vector的连续存储特性,访问元素的时间复杂度仍然是常数(O(1))。

6.vector与list的区别是什么?

回答: vector和list都是STL中的容器,但它们之间有一些重要的区别:

  • 存储方式:vector使用连续的内存块存储元素,而list使用非连续的内存块(通常通过双向链表实现)。

  • 访问速度:由于vector的连续存储特性,通过索引访问元素的速度非常快(O(1))。而list需要遍历链表来访问元素,因此访问速度较慢。

  • 插入和删除操作:在vector的末尾添加或删除元素非常高效(O(1)),但在中间或开始位置插入或删除元素可能需要移动大量元素(O(n))。而list在任何位置的插入和删除操作都只需要常数时间(O(1)),因为它只需要更改相邻节点的指针。

  • 内存使用:由于list使用非连续的内存块,它可能会浪费更多的内存空间来存储节点之间的指针。而vector则更紧凑地存储元素,但可能需要更多的内存来应对扩容。

7.什么是函数对象

函数对象(也称为仿函数或函子)在C++中是一种特殊的对象,它们具有类似函数的行为,即可以被调用。函数对象实际上是具有operator()重载的类对象,这使得它们能够像普通函数那样被调用。

函数对象的主要优点之一是它们可以携带状态。与普通的函数指针或函数相比,函数对象可以包含自己的数据成员,并在其operator()的调用中使用这些数据。这使得函数对象在需要保持状态或具有更复杂的逻辑时非常有用。

函数对象通常用于算法中,例如STL(标准模板库)中的算法,这些算法通常接受函数对象作为参数,以便允许用户自定义算法的行为。

下面是一个简单的函数对象的例子:

#include <iostream>  
  
// 定义一个函数对象类  
class Add {  
public:  
    // 重载调用运算符  
    int operator()(int x, int y) {  
        return x + y;  
    }  
};  
  
int main() {  
    // 创建函数对象  
    Add addObj;  
  
    // 调用函数对象,就像调用普通函数一样  
    int sum = addObj(3, 4);  
    std::cout << "Sum: " << sum << std::endl; // 输出:Sum: 7  
  
    return 0;  
}

在这个例子中,Add类是一个函数对象类,它重载了operator()以使其能够像函数那样被调用。在main函数中,我们创建了一个Add类型的对象addObj,并通过addObj(3, 4)调用了它的operator()。这与调用一个接受两个整数并返回它们的和的函数是完全相同的。

当然,函数对象也可以有数据成员和更复杂的逻辑,这使得它们在某些情况下比普通函数更灵活和强大。

8.STL容器有哪些

STL(Standard Template Library,标准模板库)提供了许多容器,用于存储和管理数据。以下是一些主要的STL容器:

  1. 序列容器(Sequence Containers):

    • std::vector:动态数组,支持在尾部快速插入/删除元素,并支持随机访问元素。

    • std::deque:双端队列,支持在两端快速插入/删除元素,但随机访问不如vector快。

    • std::list:双向链表,支持在任何位置快速插入/删除元素,但不支持随机访问。

    • std::forward_list:单向链表,同样支持在任何位置快速插入/删除元素,但只能向前遍历。

    • std::array:固定大小的数组,提供了与C风格数组类似的接口,但更安全且更易于使用。

    • std::stack:栈,后进先出(LIFO)的数据结构,基于其他容器(如dequelist)实现。

    • std::queue:队列,先进先出(FIFO)的数据结构,基于其他容器(如dequelist)实现。

  2. 关联容器(Associative Containers):

    • std::set:集合,包含唯一元素的容器,元素默认按升序排序。

    • std::multiset:多重集合,允许包含重复元素的集合。

    • std::map:映射,关联数组,存储键值对(key-value pairs),键是唯一的,并默认按升序排序。

    • std::multimap:多重映射,允许包含具有相同键的多个元素。

    • 这些关联容器通常使用红黑树实现,以提供高效的查找、插入和删除操作。

  3. 无序关联容器(Unordered Associative Containers):

    • std::unordered_set:无序集合,与std::set类似,但元素不排序,插入和查找的平均时间复杂度为O(1)。

    • std::unordered_multiset:无序多重集合,与std::multiset类似,但元素不排序。

    • std::unordered_map:无序映射,与std::map类似,但元素不排序。

    • std::unordered_multimap:无序多重映射,与std::multimap类似,但元素不排序。

    • 这些无序关联容器通常使用哈希表实现。

  4. 容器适配器(Container Adapters):

    • std::stackstd::queuestd::priority_queue:这些适配器为栈、队列和优先队列提供了接口,但它们本身不存储元素,而是基于其他容器(如dequevector)实现。

请注意,这只是STL容器的一个简要概述,每个容器都有其特定的用途和性能特点。在选择容器时,应根据应用程序的需求和性能要求进行选择。

9.什么是容器适配器

容器适配器是C++标准库中的一种特殊类型的容器,它本身不直接存储数据,而是依赖于其他容器(如std::vectorstd::deque等)来存储数据,并提供一种特定的接口来访问和操作这些数据。容器适配器主要用于解决特定的问题或满足特定的需求,通过改变容器的接口、增加或限制容器的功能,使得数据操作更加方便和高效。

以下是关于容器适配器的几个关键点:

  1. 类型:

    • STL定义了三个主要的序列容器适配器:std::stackstd::queuestd::priority_queue

  2. 功能:

    • stack(栈):后进先出(LIFO)的数据结构,只允许在末尾进行插入和删除操作,即只能在栈顶进行入栈和出栈操作。

    • queue(队列):先进先出(FIFO)的数据结构,允许在队列的尾部插入元素,在队列的头部删除元素。

    • priority_queue(优先队列):每次取出的元素都是当前队列中优先级最高的元素,其底层实现通常基于堆数据结构。

  3. 特点:

    • 容器适配器提供了一种简单的方式来重新组织和访问数据,同时隐藏了底层容器的实现细节。

    • 容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。

  4. 构造:

    • 每个适配器都定义了两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数则拷贝该容器来初始化适配器。

  5. 操作:

    • 对于stack,提供了push(入栈)、pop(出栈)、top(返回栈顶元素)等操作。

    • 对于queue,提供了push(在尾部插入元素)、pop(在头部删除元素)、front(返回队头元素)等操作。

    • 对于priority_queue,同样提供了push、pop和top操作,但保证每次取出的元素都是最大或最小的(根据底层实现和元素比较方式)。

  6. 底层实现:

    • 容器适配器通常基于其他类型的容器实现,如std::dequestd::vector。例如,std::stackstd::queue在C++标准库中通常基于std::deque实现。

  7. 作用:

    • 容器适配器的主要作用是改变容器的接口、增加容器的功能或限制容器的功能,以满足特定的需求。

总结来说,容器适配器是一种高级的数据结构,它基于其他容器实现,并提供了特定的接口和操作,使得数据操作更加方便和高效。在C++编程中,合理使用容器适配器可以简化代码,提高程序的效率和可维护性。

10.map 和unordermap区别

std::mapstd::unordered_map 是 C++ 标准库中的两种关联容器,它们用于存储键值对(key-value pairs)并允许基于键进行快速查找。然而,它们在内部实现、性能特性以及迭代顺序等方面存在一些区别。

std::map

  • 内部实现std::map 通常使用红黑树(Red-Black Tree)作为其内部数据结构。红黑树是一种自平衡的二叉搜索树,它能够在插入、删除和查找操作中保持对数时间复杂度(O(log n))。

  • 排序std::map 中的元素总是按键的升序排序。这是因为红黑树是一种有序的树形结构。

  • 迭代顺序:当你遍历 std::map 时,你会按照键的升序访问元素。

  • 性能:对于插入、删除和查找操作,std::map 的平均时间复杂度为 O(log n)。

std::unordered_map

  • 内部实现std::unordered_map 使用哈希表(Hash Table)作为其内部数据结构。哈希表通过将键映射到桶(buckets)中的位置来存储元素,这允许在平均情况下实现接近常数时间复杂度的插入、删除和查找操作(O(1))。

  • 排序std::unordered_map 中的元素不保证任何特定的顺序。元素的顺序取决于键的哈希值和哈希表的内部状态。

  • 迭代顺序:当你遍历 std::unordered_map 时,你会按照元素在哈希表中的存储顺序访问它们,这通常不是有序的。

  • 性能:对于插入、删除和查找操作,std::unordered_map 的平均时间复杂度为 O(1),但在最坏情况下可能会达到 O(n)。这取决于哈希函数的质量以及哈希表的负载因子(即桶的数量与元素数量的比率)。

10.哈希表和红黑树之间的区别是什么

哈希表(Hash Table)和红黑树(Red-Black Tree)是两种常用的数据结构,它们各自具有不同的特性和适用场景。以下是它们之间的主要区别:

  1. 基本结构和实现:

    • 哈希表:通过散列函数(Hash Function)将关键码值(Key)映射到表中的位置,以加快查找速度。这种映射关系通过散列函数确定,不需要比较即可直接取得所查记录。哈希表通常是由数组和链表组成,数组用于存储散列地址,链表用于处理哈希冲突。

    • 红黑树:一种特殊的二叉搜索树,它通过一系列的颜色和属性规则来保持树的平衡。这些规则确保了树的高度相对较低,从而实现了高效的查找、插入和删除操作。

  2. 查找速度:

    • 哈希表:查找速度非常快,通常接近常数时间复杂度O(1)。这是因为哈希表可以直接通过散列函数定位到元素在表中的位置,无需遍历整个数据结构。

    • 红黑树:查找速度相对较慢,为对数时间复杂度O(log n)。尽管比线性查找要快得多,但仍然不如哈希表直接定位元素的速度。

  3. 有序性:

    • 哈希表:无序。哈希表中的数据是通过散列函数随机映射到表中的位置,因此没有固定的顺序。

    • 红黑树:有序。红黑树作为二叉搜索树的一种,其数据是按照键的升序(或降序)排列的。

  4. 内存使用:

    • 哈希表:哈希表需要预先分配足够的内存来存储散列表,即使有些槽可能弃用。当哈希表被填满时,性能会严重下降,需要进行扩容操作,这通常是一个相对昂贵的操作。

    • 红黑树:红黑树只需为其存在的节点分配内存,因此内存使用更加紧凑。树的结构允许动态地插入和删除节点,无需预先分配大量内存。

  5. 扩展性:

    • 哈希表:当哈希表中的数据量增大时,可能需要重新计算散列函数或调整哈希表的大小,这可能会影响性能。但是,哈希表通常可以很好地扩展到非常大的数据集。

    • 红黑树:红黑树可以自然地处理数据量的变化,无需进行类似哈希表的重新散列或扩容操作。因此,红黑树在动态环境中表现更加稳定。

  6. 适用场景:

    • 哈希表:适用于需要快速查找、插入和删除操作的场景,如缓存系统、数据校验等。由于哈希表是无序的,因此不适用于需要保持数据顺序的场景。

    • 红黑树:适用于需要保持数据顺序的场景,如排序算法、关联数组等。同时,红黑树也适用于需要动态调整数据结构的场景,如实时数据库索引等。

综上所述,哈希表和红黑树各有优缺点,应根据具体的应用场景和需求进行选择。如果需要快速查找和插入操作,并且不关心数据的顺序,那么哈希表可能是更好的选择。如果需要保持数据的顺序,并且需要动态地插入和删除节点,那么红黑树可能更合适。

11.介绍一下map和set

当然可以。在C++的STL(Standard Template Library)中,mapset 是两种非常重要的关联容器,它们分别用于存储键值对(key-value pairs)和唯一的元素。以下是它们的基本介绍:

map

map 是一个关联容器,它包含可以重复的键(key)类型的元素,但每个键只能映射到一个值(value)。map 内部通常使用一个红黑树(Red-Black Tree)进行排序和存储,因此,在 map 中插入、删除和查找元素的时间复杂度通常为对数时间(O(log n))。

特点:
  1. 键值对存储:每个元素都是一个键值对。

  2. 自动排序:元素总是按键的升序存储。

  3. 唯一键:在 map 中,每个键都是唯一的。

  4. 快速查找:由于内部使用了红黑树,因此查找、插入和删除操作都相对较快。

使用示例:
#include <iostream>  
#include <map>  
  
int main() {  
    std::map<int, std::string> my_map;  
    my_map[1] = "one";  
    my_map[2] = "two";  
    my_map[3] = "three";  
  
    for (const auto& pair : my_map) {  
        std::cout << pair.first << ": " << pair.second << std::endl;  
    }  
  
    return 0;  
}

set

set 是一个关联容器,它包含唯一的元素。set 的内部实现也通常是一个红黑树,因此,在 set 中插入、删除和查找元素的时间复杂度也是对数时间(O(log n))。

特点:
  1. 唯一元素:在 set 中,每个元素都是唯一的。

  2. 自动排序:元素总是按升序存储。

  3. 快速查找:由于内部使用了红黑树,因此查找、插入和删除操作都相对较快。

使用示例:
#include <iostream>  
#include <set>  
  
int main() {  
    std::set<int> my_set;  
    my_set.insert(1);  
    my_set.insert(3);  
    my_set.insert(2);  
  
    for (const auto& elem : my_set) {  
        std::cout << elem << " ";  
    }  
    std::cout << std::endl;  
  
    return 0;  
}

需要注意的是,mapset 中的元素都是有序的,这是因为它们使用了红黑树作为内部数据结构。同时,由于 mapset 中的元素都是唯一的(对于 set 来说是元素本身,对于 map 来说是键),因此向它们中插入重复的元素是无效的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的小猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值