STL 复习要点

来源:互联网 发布:范冰冰演技 知乎 编辑:程序博客网 时间:2024/06/03 13:37

顺序容器:vector, list, deque, string, array 

关联容器:set, unordered_set, map, unordered_map

适配器:stack, queue

C++ STL总结

  • STL概述

STL (Standard Template Library, 标准模板库) 是惠普实验室开发的一系列软件的统称。主要核心分为三大部分:容器(container)、算法(algorithm)和迭代器(iterator),另外还有容器适配器(container adaptor)和函数对象(functor)等其它标准组件。

  • 容器:

顺序容器:

名称

特性

vector

模拟的数据结构式动态数组,在内存中是连续储存的,支持随机存取,支持在尾部快速插入和删除元素,搜索速度较慢

deque

称为双端队列,在内存中的储存方式是小片连续,每片之间用链表连接起来,支持随机存取,支持在头部和尾部快速插入和删除元素,搜索速度较慢

list

称为双向链表,在内存中的储存是不连续的,每个元素的内存之间用指针相连,不支持随机存取(因为要从首或尾遍历至指定位置),但是支持在任意位置快速插入和删除元素,搜索速度最慢,扩展内存时无需复制和拷贝原元素

array

称为静态数组,在内存中是连续储存的,支持随机存取,不支持插入或删除元素

forward_list

称为前向链表,在内存中的储存是不连续的,同list一样支持在任意位置快速插入和删除元素,不支持随机存取,搜索速度也较慢,与list最大的区别在于其只能从头部遍历至尾部,不能反向遍历,因此没有保存后向指针,比list更省内存,插入和删除元素比list稍慢。

注:红色加粗的容器为C++11标准中新增的

关联式容器:

名称

特性

set

以红黑树实现,内存中是不连续储存的,保存的是元素是唯一的键值且不可变,排列的方式根据指定的严格弱序排列,不支持随机存取,搜索速度较快

multiset

与set基本一致,差别就在于允许保存重复键值

map

同样以红黑树实现,保存的元素是一个pair类型{key, value},每个键值对应一个值,且键值唯一不可变,键值的排列方式根据指定的严格弱序排列,支持用key进行随机存取,搜索速度较快

multimap

与map基本一致,差别在于键值可以重复

 

名称

特性

unordered_set

以哈希表实现,内存中是不连续储存的,保存的是元素是唯一的键值且不可变,无序的排列方式,不支持随机存取,搜索速度比红黑树实现的set要快

unordered_multiset

与unordered_set基本一致,差别就在于允许保存重复键值

unordered_map

同样以哈希表实现,保存的元素是一个pair类型{key, value},每个键值对应一个值,且键值唯一不可变,key值无序排列,支持用key进行随机存取,搜索速度比红黑树实现的map要快

unordered_multimap

与unordered_map基本一致,差别在于键值可以重复

 

容器适配器:均可以用vector, list和deque来实现,没有提供迭代器

名称

特性

stack

默认用deque来实现数据结构的栈的功能

queue

默认用deque来实现数据结构的队列的功能

priority_queue

默认用vector来实现,其中保存的元素按照某种严格弱序进行排列,队首元素总是值最大的

 

空间适配器allocator:C++ Primer 5th 中文版P427

allocator模板类定义在头文件memory.h中,它帮助我们将内存分配和对象构造分开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。利用allocate方法分配一段内存,当利用allocator对象分配了内存以后,要再用construct方法来再这块内存中构造指定类型的对象。当使用完这块内存中的对象后,可以利用destroy方法来销毁这个对象,这块内存又变为原始的未构造的内存,可以再次在这块内存中构造指定类型的对象。当使用完这块内存后,要先销毁其中保存的对象,再利用deallocate方法销毁这块内存。

 

  • 算法:

STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成。要使用 STL中的算法函数必须包含头文件<algorithm>,对于数值算法须包含<numeric>,<functional>中则定义了一些模板类,用来声明函数对象。

STL中算法大致分为四类:

1)、非可变序列算法:指不直接修改其所操作的容器内容的算法。

2)、可变序列算法:指可以修改它们所操作的容器内容的算法。

3)、排序算法:包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作。

4)、数值算法:对容器内容进行数值计算。

以下对所有算法进行细致分类并标明功能:

<一>查找算法(13个):判断容器中是否包含某个值

adjacent_find: 在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator。否则返回last。重载版本使用输入的二元操作符代替相等的判断。

binary_search: 在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判断相等。

count: 利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数。

count_if: 利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数。

equal_range: 功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。

find: 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回指向该元素的Iterator。

find_end: 在指定范围内查找"由输入的另外一对iterator标志的第二个序列的最后一次出现。找到则返回最后一对的第一个ForwardIterator,否则返回输入的"另外一对"的第一个ForwardIterator。重载版本使用用户输入的操作符代替等于操作。

find_first_of: 在指定范围内查找"由输入的另外一对iterator标志的第二个序列"中任意一个元素的第一次出现。重载版本中使用了用户自定义操作符。

find_if: 使用输入的函数代替等于操作符执行find。

lower_bound: 返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用自定义比较操作。

upper_bound: 返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值。重载函数使用自定义比较操作。

search: 给出两个范围,返回一个ForwardIterator,查找成功指向第一个范围内第一次出现子序列(第二个范围)的位置,查找失败指向last1。重载版本使用自定义的比较操作。

search_n: 在指定范围内查找val出现n次的子序列。重载版本使用自定义的比较操作。

<二>排序和通用算法(14个):提供元素排序策略

inplace_merge: 合并两个有序序列,结果序列覆盖两端范围。重载版本使用输入的操作进行排序。

merge: 合并两个有序序列,存放到另一个序列。重载版本使用自定义的比较。

nth_element: 将范围内的序列重新排序,使所有小于第n个元素的元素都出现在它前面,而大于它的都出现在后面。重载版本使用自定义的比较操作。

partial_sort: 对序列做部分排序,被排序元素个数正好可以被放到范围内。重载版本使用自定义的比较操作。

partial_sort_copy: 与partial_sort类似,不过将经过排序的序列复制到另一个容器。

partition: 对指定范围内元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前。

random_shuffle: 对指定范围内的元素随机调整次序。重载版本输入一个随机数产生操作。

reverse: 将指定范围内元素重新反序排序。

reverse_copy: 与reverse类似,不过将结果写入另一个容器。

rotate: 将指定范围内元素移到容器末尾,由middle指向的元素成为容器第一个元素。

rotate_copy: 与rotate类似,不过将结果写入另一个容器。

sort: 以升序重新排列指定范围内的元素。重载版本使用自定义的比较操作。

stable_sort: 与sort类似,不过保留相等元素之间的顺序关系。

stable_partition: 与partition类似,不过不保证保留容器中的相对顺序。

<三>删除和替换算法(15个)

copy: 复制序列

copy_backward: 与copy相同,不过元素是以相反顺序被拷贝。

iter_swap: 交换两个ForwardIterator的值。

remove: 删除指定范围内所有等于指定元素的元素。注意,该函数不是真正删除函数。内置函数不适合使用remove和remove_if函数。

remove_copy: 将所有不匹配元素复制到一个制定容器,返回OutputIterator指向被拷贝的末元素的下一个位置。

remove_if: 删除指定范围内输入操作结果为true的所有元素。

remove_copy_if: 将所有不匹配元素拷贝到一个指定容器。

replace: 将指定范围内所有等于vold的元素都用vnew代替。

replace_copy: 与replace类似,不过将结果写入另一个容器。

replace_if: 将指定范围内所有操作结果为true的元素用新值代替。

replace_copy_if: 与replace_if,不过将结果写入另一个容器。

swap: 交换存储在两个对象中的值。

swap_range: 将指定范围内的元素与另一个序列元素值进行交换。

unique: 清除序列中重复元素,和remove类似,它也不能真正删除元素。重载版本使用自定义比较操作。

unique_copy: 与unique类似,不过把结果输出到另一个容器。

<四>排列组合算法(2个):提供计算给定集合按一定顺序的所有可能排列组合

next_permutation: 取出当前范围内的排列,并重新排序为下一个字典序排列。重载版本使用自定义的比较操作。

prev_permutation: 取出指定范围内的序列并将它重新排序为上一个字典序排列。如果不存在上一个序列则返回false。重载版本使用自定义的比较操作。

<五>算术算法(4个)

accumulate: iterator对标识的序列段元素之和,加到一个由val指定的初始值上。重载版本不再做加法,而是传进来的二元操作符被应用到元素上。

partial_sum: 创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。重载版本使用自定义操作代替加法。

inner_product: 对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。重载版本使用用户定义的操作。

adjacent_difference: 创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。重载版本用指定二元操作计算相邻元素的差。

<六>生成和异变算法(6个)

fill: 将输入值赋给标志范围内的所有元素。

fill_n: 将输入值赋给first到first+n范围内的所有元素。

for_each: 用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素。

generate: 连续调用输入的函数来填充指定的范围。

generate_n: 与generate函数类似,填充从指定iterator开始的n个元素。

transform: 将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定容器。

<七>关系算法(8个)

equal: 如果两个序列在标志范围内元素都相等,返回true。重载版本使用输入的操作符代替默认的等于操作符。

includes: 判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。重载版本使用用户输入的函数。

lexicographical_compare: 比较两个序列。重载版本使用用户自定义比较操作。

max: 返回两个元素中较大一个。重载版本使用自定义比较操作。

max_element: 返回一个ForwardIterator,指出序列中最大的元素。重载版本使用自定义比较操作。

min: 返回两个元素中较小一个。重载版本使用自定义比较操作。

min_element: 返回一个ForwardIterator,指出序列中最小的元素。重载版本使用自定义比较操作。

mismatch: 并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。重载版本使用自定义的比较操作。

<八>集合算法(4个)

set_union: 构造一个有序序列,包含两个序列中所有的不重复元素。重载版本使用自定义的比较操作。

set_intersection: 构造一个有序序列,其中元素在两个序列中都存在。重载版本使用自定义的比较操作。

set_difference: 构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。重载版本使用自定义的比较操作。

set_symmetric_difference: 构造一个有序序列,该序列取两个序列的对称差集(并集-交集)。

<九>堆算法(4个)

make_heap: 把指定范围内的元素生成一个堆。重载版本使用自定义比较操作。

pop_heap: 并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。重载版本使用自定义的比较操作。

push_heap: 假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较操作。

sort_heap: 对指定范围内的序列重新排序,它假设该序列是个有序堆。重载版本使用自定义比较操作。

 

  • 迭代器(iterator):

1. STL容器中只有顺序容器关联容器支持迭代器遍历,行为类似于指针,用于指向容器内的元素,并通过解引用*符号来获取元素。

2. 所有容器的迭代器都支持==和!=运算符;一般都支持++和--运算符(forward_list不支持--运算符);除了无序关联容器以外,其它容器的迭代器均支持<, <=, >, >=运算符。

3. 一个迭代器范围由一对迭代器表示,第一个迭代器指向范围内第一个元素,第二个迭代器指向范围内最后一个元素的下一个位置,是一个左闭右开区间[begin, end)。如果begin和end相等,则范围为空;如果begin和end不等,则范围至少包含一个元素,且begin指向第一个元素;begin递增若干次可以==end。

4. 每种容器都有指向首元素的begin()和指向尾后位置的end()迭代器,类型类似于vector<int>::iterator;另外还有对应的const版本的迭代器cbegin()和cend(),类型类似于vector<int>::const_iterator。

5. 反向迭代器,仅能反向迭代的容器支持。rbegin()指向末尾的第一个元素,rend()指向首元素的前一个位置,crbegin()和crend()分别为对应的const版本。类型为reverse_iterator以及其const版本const_reverse_iterator。

6. 所有容器支持的迭代器类型:

容器名称

支持的迭代器类型

vector

随机访问迭代器

deque

随机访问迭代器

list

双向迭代器

array

随机访问迭代器

forword_list

前向迭代器

stack

不支持

queue

不支持

priority_queue

不支持

set

双向迭代器

multiset

双向迭代器

map

双向迭代器

multimap

双向迭代器

1.说说std::vector的底层(存储)机制。

vector就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。


2.std::vector的自增长机制。

当已经分配的空间不够装下数据时,分配双倍于当前容量的存储区,把当前的值拷贝到新分配的内存中,并释放原来的内存。

3.说说std::list的底层(存储)机制。

以结点为单位存放数据,结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间

4.什么情况下用vector,什么情况下用list。

vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。
list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁。


5.list自带排序函数的排序原理。

将前两个元素合并,再将后两个元素合并,然后合并这两个子序列成4个元素的子序列,重复这一过程,得到8个,16个,...,子序列,最后得到的就是排序后的序列。
时间复杂度:O(nlgn)
[cpp] view plain copy print?
void List::sort() { List carry; List counter[64]; //数组元素为链表 int fill = 0; while (head->next != tail) { carry.transfer(carry.getHead()->next, head->next, head->next->next); //head是哨兵,不存放有效值//head->next元素被移走,所以while循环不需要head=head->next; int i = 0; while (i < fill && counter[i].getHead()->next != counter[i].getHead())//counter[i]不是空{ counter[i].merge(carry); carry.swap(counter[i++]); } carry.swap(counter[i]); if (i == fill) ++fill; } for (int i = 1; i < fill; i++) counter[i].merge(counter[i - 1]); //通过这个实现排序(将有序的链表合成一个新的有序链表) swap(counter[fill - 1]); } 



6.说说std::deque的底层机制。

deque动态地以分段连续空间组合而成,随时可以增加一段新的连续空间并链接起来。不提供空间保留功能。
注意:除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。对deque排序,为了提高效率,可先将deque复制到一个vector上排序,然后再复制回deque。
deque采用一块map(不是STL的map容器)作为主控,其为一小块连续空间,其中每个元素都是指针,指向另一段较大的连续空间(缓冲区)。
deque的迭代器包含4个内容:
1)cur:迭代器当前所指元素
2)first:此迭代器所指的缓冲区的头。
3)last:缓冲区尾。
4)node:指向管控中心。


7.说说std::map底层机制。

map以RB-TREE为底层机制。RB-TREE是一种平衡二叉搜索树,自动排序效果不错。map的内部结构是R-B-tree来实现的,所以保证了一个稳定的动态操作时间,查询、插入、删除都是O(logN),最坏和平均都是。
通过map的迭代器不能修改其键值,只能修改其实值。所以map的迭代器既不是const也不是mutable。


8.vector插入删除和list有什么区别?

vector插入和删除数据,需要对现有数据进行复制移动,如果vector存储的对象很大或者构造函数很复杂,则开销较大,如果是简单的小数据,效率优于list。
list插入和删除数据,需要对现有数据进行遍历,但在首部插入数据,效率很高。

9.hashtable如何避免地址冲突?

1)线性探测:先用hash function计算某个元素的插入位置,如果该位置的空间已被占用,则继续往下寻找,知道找到一个可用空间为止。
其删除采用惰性删除:只标记删除记号,实际删除操作等到表格重新整理时再进行。
2)二次探测:如果计算出的位置为H且被占用,则依次尝试H+1^2,H+2^2等(解决线性探测中主集团问题)。
3)开链:每一个表格元素中维护一个list,hash function为我们分配一个list,然后在那个list执行插入、删除等操作。


10.hashtable,hash_set,hash_map的区别。

hash_set以hashtable为底层,不具有排序功能,能快速查找。其键值就是实值。(set以RB-TREE为底层,具有排序功能。)
hash_map以以hashtable为底层,没有自动排序功能,能快速查找,每一个元素同时拥有一个实值和键值。(map以RB-TREE为底层,具有排序功能。)


11.hash_map与map的区别?什么时候用hash_map,什么时候用map?

构造函数:hash_map需要hash function和等于函数,而map需要比较函数(大于或小于)。
存储结构:hash_map以hashtable为底层,而map以RB-TREE为底层。
总的说来,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logn级别。但不一定常数就比log小,而且hash_map还有hash function耗时。
如果考虑效率,特别当元素达到一定数量级时,用hash_map。
考虑内存,或者元素数量较少时,用map。


12.红黑树有什么性质?

1)每个结点是红色或者黑色。
2)根结点为黑色。
3)叶结点为黑色的NULL结点。
4)如果结点为红,其子节点必须为黑。
5)任一结点到NULL的任何路径,所含黑结点数必须相同。


13.map和set的3个问题。

1)为何map和set的插入删除效率比其他序列容器高。
因为不需要内存拷贝和内存移动
2)为何map和set每次Insert之后,以前保存的iterator不会失效?
因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。
2)当数据元素增多时(从10000到20000),map的set的查找速度会怎样变化?
RB-TREE用二分查找法,时间复杂度为logn,所以从10000增到20000时,查找次数从log10000=14次到log20000=15次,多了1次而已。


14.vector中begin和end函数返回的是什么?

begin返回的是第一个元素的迭代器,end返回的是最后一个元素后面位置的迭代器。

15.为什么vector的插入操作可能会导致迭代器失效?
vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一片较大的新空间,然后将内容拷贝过来,并释放原来的空间。由于操作改变了空间,所以迭代器失效。


16.vector、list、map、deque用erase(it)后,迭代器的变化。

vector和deque是序列式容器,其内存分别是连续空间和分段连续空间,删除迭代器it后,其后面的迭代器都失效了,此时it及其后面的迭代器会自动加1,使it指向被删除元素的下一个元素。
list删除迭代器it时,其后面的迭代器都不会失效,将前面和后面连接起来即可。
map也是只能使当前删除的迭代器失效,其后面的迭代器依然有效。

17.hashtable和hashmap的区别

hashmap以hashtable为底层。主要有以下几点不同:
1)hashtable是Dictionary的子类,而hashmap是Map接口的一个实现类。
2)hashtable中的方法是同步的,而hashmap的方法不同步。

18. 请用c++ 实现stl中的string类,实现构造,拷贝构造,析构,赋值,比较,字符串相加,获取长度及子串等功能。

数据成员有char* data 和 int size。
注意:

  • 重载 operator = (const string s) 时候要注意判断是否是s 是否是自己。
  • 重载 =, +, - 和复制构造函数时,大小要注意,别忘了结尾0,申请的内存大小要加一,但是 size 成员不加。

#include <iostream>#include <cstring>using namespace std;class String {public:    // 默认构造函数    String(const char* str = NULL);    // 复制构造函数    String(const String &str);    // 析构函数    ~String();    // 字符串连接    String operator+(const String & str);    // 字符串赋值    String & operator=(const String &str);    // 字符串赋值    String & operator=(const char* str);    // 判断是否字符串相等    bool operator==(const String &str);    // 获取字符串长度    int length();    // 求子字符串[start,start+n-1]    String substr(int start, int n);    // 重载输出    friend ostream & operator<<(ostream &o, const String &str);private:    char* data;    int size;};// 构造函数String::String(const char *str) {    if (str == NULL) {        data = new char[1];        data[0] = '\0';        size = 0;    }//if    else {        size = strlen(str);        data = new char[size + 1];        strcpy(data, str);    }//else}// 复制构造函数String::String(const String &str) {    size = str.size;    data = new char[size + 1];    strcpy(data, str.data);}// 析构函数String::~String() {    delete[] data;}// 字符串连接String String::operator+(const String &str) {    String newStr;    //释放原有空间    delete[] newStr.data;    newStr.size = size + str.size;    newStr.data = new char[newStr.size + 1];    strcpy(newStr.data, data);    strcpy(newStr.data + size, str.data);    return newStr;}// 字符串赋值String & String::operator=(const String &str) {    if (data == str.data) { // 注意要先判断是否是自己给自己赋值        return *this;    }//if    delete[] data;    size = str.size;    data = new char[size + 1];    strcpy(data, str.data);    return *this;}// 字符串赋值String& String::operator=(const char* str) {    if (data == str) {        return *this;    }//if    delete[] data;    size = strlen(str);    data = new char[size + 1];    strcpy(data, str);    return *this;}// 判断是否字符串相等bool String::operator==(const String &str) {    return strcmp(data, str.data) == 0;}// 获取字符串长度int String::length() {    return size;}// 求子字符串[start,start+n-1]String String::substr(int start, int n) {    String newStr;    // 释放原有内存    delete[] newStr.data;    // 重新申请内存    newStr.data = new char[n + 1];    for (int i = 0; i < n; ++i) {        newStr.data[i] = data[start + i];    }//for    newStr.data[n] = '\0';    newStr.size = n;    return newStr;}// 重载输出ostream & operator<<(ostream &o, const String &str) {    o << str.data;    return o;}int main() {    String str1("hello ");    String str2 = "world";    String str3 = str1 + str2;    cout << "str1->" << str1 << " size->" << str1.length() << endl;    cout << "str2->" << str2 << " size->" << str2.length() << endl;    cout << "str3->" << str3 << " size->" << str3.length() << endl;    String str4("helloworld");    if (str3 == str4) {        cout << str3 << " 和 " << str4 << " 是一样的" << endl;    }//if    else {        cout << str3 << " 和 " << str4 << " 是不一样的" << endl;    }    cout << str3.substr(6, 5) << " size->" << str3.substr(6, 5).length() << endl;    return 0;}

19. 如何选择使用vector或dequeue

一般情况下使用vector,在需要从首尾两端进行插入或删除操作的时候需要选择dequeue.

20. vector中erase方法与algorithn中的remove方法区别

vector中erase方法真正删除了元素,迭代器不能访问了
remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

21. vector和list的区别

vector和数组类似,拥有连续的内存空间,支持随机的存取,在中间进行元素的插入和删除的操作时间复杂度是O(n)

list是由双向链表实现的,只能通过数组指针来进行数据访问,遍历中间的元素,时间的复杂度是O(n).

22. 用过哪些容器?讲一讲?

最常用的容器就是:vector, list, map, hash_map等等。

23. vector,list,deque的实现。

vector是一块连续内存,当空间不足了会再分配。
list是双向链表。
deque是双端队列可在头和尾部插入、删除元素。

23. 一元、二元仿函数

24. 智能指针auto_ptr的概念及用法

auto_ptr可以代替指针进行类似指针的操作,并不用关心内存的释放,auto_ptr的析构函数自动释放它绑定的动态分配对象。
auto_ptr的实现

和shared_ptr不同的是,auto_ptr没有考虑引用计数。因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。

1. 利用特点“栈上对象在离开作用范围时会自动析构”。
2. 对于动态分配的内存,其作用范围是程序员手动控制的,这给程序员带来了方便但也不可避免疏忽造成的内存泄漏,毕竟只有编译器是最可靠的。
3. auto_ptr通过在栈上构建一个对象a,对象a中wrap了动态分配内存的指针p,所有对指针p的操作都转为对对象a的操作。而在a的析构函数中会自动释放p的空间,而该析构函数是编译器自动调用的,无需程序员操心。

使用auto_ptr的限制
auto_ptr所所指向的对象要求只能拥有一个拥有者
如下用法是错误的

classA *pA = new classA;
auto_ptr<classA> ptr1(pA);
auto_ptr<classB> ptr2(pA);

以及
auto_ptr<classA> ptr1(new classA());
auto_ptr<classA> ptr2; 
ptr2 = ptr1; // ptr2拥有对象
ptr1->foo();// 错误!ptr2已经拥有了对象的拥有权,ptr1丧失了拥有权! 
  • auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。
  • auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
  • auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。
  • auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。

25. 迭代器删除元素的会发生什么?

  • 对于关联容器(如map, set, multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。
  • 对于序列式容器(如vector,deque),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vetor,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。还好erase方法可以返回下一个有效的iterator
  • 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种方法都可以使用。

26. stl有哪些容器,对比vector和set? STL容器分为顺序容器和关联容器。

  • 顺序容器主要有vector、list、deque、string、array等。其中vector表示一段连续的内存,基于数组实现,list表示非连续的内存,基于链表实现,deque与vector类似,但是对首元素提供插入和删除的双向支持。
  • 关联容器主要有map、multimap、unordered_map、set、multiset、unorderd_set等。map是key-value形式,set是单值。map和set只能存放唯一的key,multimap和multiset可以存放多个相同的key。
    1. 首先,vector是顺序容器,而set是关联式容器;set包含0个或多个不重复不排序的元素。也就是说set能够保证它里面所有的元素都是不重复的;另外,对set容器进行插入时可以指定插入位置或者不指定插入位置。如insert(v.begin(),1),也可以直接用insert(1)。还有一点是set对一些操作符没有进行重载,如<。
    2. vector和deque的区别主要在于他们底层的实现不同,特别是在插入和删除操作的实现机制不同。
    3. 对于vector来说,不管其大小是多少,在头部插入的效率总是比在尾部插入的效率低。在尾部插入将耗费固定的时间。在头部进行插入时,耗费的时间与vector的大小成正比,vector越大,耗费的时间越多。例如,在一个大小为1000的vector头部插入一个元素,与在一个大小为10的vector头部插入一个元素相比,将耗费100倍的时间。删除操作的情形也与插入类似。因此,vector适合于插入和删除操作都在尾部进行的情况。
    4. deque和vector不同,不管进行的插入还是删除操作,也不管这些操作时在头部还是尾部进行,算法的效率是固定的。例如:不管deque的大小是10,100,还是1000.deque在头部和尾部插入删除的时间是一样的。因此要在对于两端进行插入或者删除操作时。deque要优于vector。

原创粉丝点击