前言
之前介绍了string和vector的功能和实现,接下来就到list。就像C语言中的一样,string和vector的类型是顺序表,而C++标准库中的list则是双向带头循环列表。这么说的话,基础结构大致也就清楚了,如果不知道的可以去找我之前写过的博客,词条也在下方给出了,需要自取。
用到顺序表的通讯录-CSDN博客文章浏览阅读589次,点赞25次,收藏19次。顺序表式数据结构中基础部分,也是我接触C语言学到数据结构中的线性结构第一次接触到的结构。从整体上来看,顺序表就像是一个大数组。能够扩容,能够装自定义变量的数组。和普通在栈内的数组不同,我们需要在堆区开辟 空间。这就用到了之前学到的“malloc”、“realloc”。https://blog.csdn.net/2302_81342533/article/details/137490575单链表与双链表_单链表和双链表-CSDN博客文章浏览阅读866次,点赞19次,收藏27次。本文详细介绍了单链表和双链表在实现通讯录应用中的结构、主要函数,包括初始化、销毁、添加联系人、删除联系人、查找和修改等功能,并提供了示例代码和测试过程。
https://blog.csdn.net/2302_81342533/article/details/137715956 string和vector是开辟的连续空间,所以迭代器能够直接使用指针进行。但是list中的空间不是连续的,那么不能用指针的“++”“--”来找到下一块资源的位置,所以我们需要一个新的类来封装迭代器实现它的效果。
虽然string、vector和list函数的功能相同,但是本文还是会稍微讲解list函数的作用以及举例。
一、list简介
list是序列容器,允许在序列中的任何位置进行恒定时间的插入和擦除操作,以及双向迭代。
list容器实现为双链表;双链表可以将它们包含的每个元素存储在不同且不相关的存储位置。排序在内部由与每个元素的关联来保持,链接到它前面的元素,链接到后面的元素。
它们与forward_list非常相似:主要区别在于forward_liist对象是单链表,因此它们只能向前迭代,以换取更小、更高效。
与其他基本标准序列容器(数组、向量和双端队列)相比,列表在插入、提取和移动容器内已获得迭代器的任何位置的元素方面通常表现更好,因此在密集使用这些元素的算法中也表现更好,如排序算法。
与这些其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开始或结束)迭代到该位置,这在它们之间的距离上需要线性时间。它们还消耗了一些额外的内存来保存与每个元素相关的链接信息(这可能是大型小元素列表的一个重要因素)。
二、list的结构
1、基础结构
首先,list作为双向带头循环链表,会有一个指针指向哨兵位的头结点,然后会有一个计数来记录链表中储存的元素个数。链表的节点也需要用一个类来表示,其中包含资源以及指向前后节点的指针:
// List的节点类template<class T>struct ListNode{ListNode<T>* _pPre;ListNode<T>* _pNext;T _val;};// 整个listtemplate<class T>class list{typedef ListNode<T> Node;typedef Node* PNode;private:PNode _pHead;size_t _size;};
2、构造函数
2.1、list构造函数介绍
C++98一共有4种构造函数:
(1) 空容器构造函数(默认构造函数)
构造一个没有元素的空容器。
(2) 填充构造函数
构造一个包含n个元素的容器。每个元素都是val的副本。
(3) 范围构造器
构造一个包含与范围[first,last)一样多的元素的容器,每个元素都按照相同的顺序从该范围内的相应元素构造而成。
(4) 复制构造函数
构造一个容器,其中包含x中每个元素的副本,顺序相同。
容器保存alloc的内部副本,用于在其整个生命周期内分配存储。
复制构造函数(4)创建了一个容器,该容器保存并使用x分配器的副本。
元素的存储是使用此内部分配器分配的。
2.2、构造函数的使用举例
#include <iostream>
#include <list>// 构造函数void test_list1()
{// 构造函数的使用顺序与上述相同:std::list<int> first; // 默认构造std::list<int> second (4,100); // 重复构造std::list<int> third (second.begin(),second.end()); // 迭代器构造std::list<int> fourth (third); // 拷贝构造// 其他容器的迭代器构造:int myints[] = {16,2,77,29};std::list<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );// 使用迭代器打印链表中的数据std::cout << "The contents of fifth are: ";for (std::list<int>::iterator it = fifth.begin(); it != fifth.end(); it++)std::cout << *it << ' ';std::cout << '\n';
}int main ()
{test_list1();return 0;
}
3、析构函数
和string、vector的析构函数相同。会在list结束生命周期的同时,释放名下的资源。该函数会自动调用,故不做演示。
4、赋值拷贝
4.1、赋值拷贝介绍
为容器分配新内容,替换其当前内容,并相应地修改其大小。
在调用之前,容器中保存的任何元素都会被分配或销毁。
标准库中的赋值拷贝都为深拷贝。
4.2、赋值拷贝的使用举例
// =赋值拷贝
void test_list2()
{std::list<int> first (3); // list中存放3个0std::list<int> second (5); // list中存放5个0second = first;first = std::list<int>();std::cout << "Size of first: " << int (first.size()) << '\n';std::cout << "Size of second: " << int (second.size()) << '\n';
}
5、迭代器相关函数
如图所示,迭代器函数的相关名称和vector、string中的函数名称相同,作用类似。“begin()”和“end()”分别返回开始的迭代器和末尾的迭代器。“rbegin()”和“rend()”分别返回开始的反向迭代器和末尾的反向迭代器。区间上都是左闭右开的。而加上“c”修饰的函数返回的是const迭代器,const迭代器的不是迭代器无法修改,而是迭代器指向的资源无法修改。相当于“const int*”这样的概念,而不是“int *const”。
相关模拟之前有构造函数中使用过,故不在举例。
需要注意的点如上图所示:list的迭代器不像string和vector的迭代器那样是随机迭代器,而是双向迭代器。随机迭代器除了支持“++”“--”的运算之外,还支持“+n”“-n”的操作,而双向迭代器只支持“++”“--”运算。论其原因是因为list开辟的空间不连续。
还有一种迭代器是单向迭代器,这中迭代器只能“++”操作,是单向链表中的迭代器。
list中有关迭代器的实现,会在list的模拟中展现出来。
6、容量相关函数
这里就和vector有很大的不同,没有了reverse()、resize(),只剩下empty()、size()。作用也和vector中的同名函数相同,访问链表是否为空和链表中元素个数。
7、修改函数
7.1、所有修改成员函数
7.2、assign()
为列表容器分配新内容,替换其当前内容,并相应地修改其大小。
这个函数很少见,相当于清除链表中的内容后重新构造它。
// assign()模拟
void test_list3()
{std::list<int> first;std::list<int> second;first.assign(7,100); // 从新载入7个100second.assign(first.begin(), first.end()); // 将链表1的拷贝过来int myints[] = {1776, 7, 4};first.assign (myints, myints + 3); // 再次重新构造链表1// 打印链表中有多少元素std::cout << "Size of first: " << int (first.size()) << '\n';std::cout << "Size of second: " << int (second.size()) << '\n';
}
7.3、emplace_front()、push_front()和pop_front()
这三种函数算作一类,都和链表的首个元素节点有关,其中emplace_front()和push_front()都是插入新的元素到链表开头。不同的是emplace_front()可以传构造元素的内容,而push_front()需要传完整的元素。
关于pop_front()则是删除首元素。
// 头删和头插函数举例
void test_list4()
{std::list<int> mylist;mylist.push_back (100);mylist.push_back (200);mylist.push_back (300);std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';std::cout << "Popping out the elements in mylist:";while (!mylist.empty()){std::cout << ' ' << mylist.front();mylist.pop_front();}std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';
}
7.4、emplace_back()、push_back()和pop_back()
和7.3的函数形成对比,这些函数都变成和末尾相关得到即可,故不继续赘述了
// 尾删和尾插函数举例
void test_list4()
{std::list<int> mylist;mylist.push_back (100);mylist.push_back (200);mylist.push_back (300);std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';std::cout << "Popping out the elements in mylist:";while (!mylist.empty()){std::cout << ' ' << mylist.back();mylist.pop_back();}std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';
}
7.5、emplace()、insert()和erase()
这些也分别对应了构造插入、插入和删除。不过和7.3、7.4中的函数相比,需要多传入一个迭代器来确定具体是要操作哪个节点。
#include <vector>
// erase()/insert()举例
void test_list5()
{std::list<int> mylist;std::list<int>::iterator it;// 插入一些元素:for (int i=1; i<=5; ++i) mylist.push_back(i); // 1 2 3 4 5it = mylist.begin();++it; // 使指针移动到第二位 ^mylist.insert (it, 10); // 1 10 2 3 4 5// 指针仍然指向2 ^mylist.insert (it, 2, 20); // 1 10 20 20 2 3 4 5--it; // 现在指针指向第二个20 ^std::vector<int> myvector (2, 30);mylist.insert (it,myvector.begin(), myvector.end());// 1 10 20 30 30 20 2 3 4 5// ^std::cout << "mylist contains:";for (it=mylist.begin(); it!=mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
// erase()/insert()举例
void test_list5()
{std::list<int> mylist;std::list<int>::iterator it1,it2;// 插入一些元素:for (int i = 1; i < 10; ++i) mylist.push_back(i*10);// 10 20 30 40 50 60 70 80 90it1 = it2 = mylist.begin(); // ^^advance (it2, 6); // ^ ^++it1; // ^ ^it1 = mylist.erase (it1); // 10 30 40 50 60 70 80 90// ^ ^it2 = mylist.erase (it2); // 10 30 40 50 60 80 90// ^ ^++it1; // ^ ^--it2; // ^ ^mylist.erase (it1,it2); // 10 30 60 80 90// ^std::cout << "mylist contains:";for (it1 = mylist.begin(); it1 != mylist.end(); ++it1)std::cout << ' ' << *it1;std::cout << '\n';
}
7.6、swap()
用x的内容交换容器的内容,x是另一个相同类型的列表。尺寸可能不同。
调用此成员函数后,此容器中的元素是调用前x中的元素,x的元素是此中的元素。所有迭代器、引用和指针对于交换的对象仍然有效。
请注意,存在一个同名的非成员函数swap,它使用与此成员函数类似的优化来重载该算法。
没有定义容器分配器是否也被交换,除非在适当的分配器特性明确指示它们应该传播的情况下。
// swap()
void test_list6()
{std::list<int> first (3, 100); // 存入3个100std::list<int> second (5,200); // 存入5个200first.swap(second);std::cout << "first contains:";for (std::list<int>::iterator it=first.begin(); it!=first.end(); it++)std::cout << ' ' << *it;std::cout << '\n';std::cout << "second contains:";for (std::list<int>::iterator it=second.begin(); it!=second.end(); it++)std::cout << ' ' << *it;std::cout << '\n';
}
作用和vector、string同名函数相同。
7.7、resize()
调整容器大小,使其包含n个元素。
如果n小于当前容器大小,则内容将减少到其前n个元素,删除(并销毁)超出的元素。
如果n大于当前容器大小,则通过在末尾插入尽可能多的元素来扩展内容,以达到n的大小。如果指定了val,则新元素将初始化为val的副本,否则将进行值初始化。
请注意,此函数通过插入或删除容器中的元素来更改容器的实际内容。
这里和vector中的resize()作用相同故不做演示。
7.8、clear()
清理掉链表中所有元素,效果和vector中同名函数相同。
8、list中的操作函数
8.1、splice()
将元素从x转移到容器中,并将其插入到指定位置。
这有效地将这些元素插入到容器中,并将其从x中删除,从而改变了两个容器的大小。该操作不涉及任何元件的建造或破坏。无论x是左值还是右值,或者value_type是否支持move构造,它们都会被转移。
第一个版本(1)将x的所有元素转移到容器中。
第二个版本(2)只将i指向的元素从x转移到容器中。
第三个版本(3)将范围[first,last)从x转移到容器中。
// splice()
void test_list7()
{std::list<int> mylist1, mylist2;std::list<int>::iterator it;// 给链表插入一些元素:for (int i = 1; i <= 4; ++i)mylist1.push_back(i); // mylist1: 1 2 3 4for (int i = 1; i <= 3; ++i)mylist2.push_back(i * 10); // mylist2: 10 20 30it = mylist1.begin();++it; // 指针指向 2mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4// mylist2 (empty)// "it" 将依然指向元素2 (也就是第5个元素)mylist2.splice (mylist2.begin(), mylist1, it);// mylist1: 1 10 20 30 3 4// mylist2: 2// "it" 失效.it = mylist1.begin();std::advance(it, 3); // "it" 指向30mylist1.splice (mylist1.begin(), mylist1, it, mylist1.end());// mylist1: 30 3 4 1 10 20std::cout << "mylist1 contains:";for (it=mylist1.begin(); it != mylist1.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';std::cout << "mylist2 contains:";for (it=mylist2.begin(); it != mylist2.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
8.2、remove()
从容器中删除所有与val相等的元素。这将调用这些对象的析构函数,并根据删除的元素数量减小容器大小。
与成员函数list::erase不同,它根据元素的位置(使用迭代器)擦除元素,此函数(list::remove)根据元素的值删除元素。
存在一个类似的函数list::remove_if,它允许使用相等比较以外的条件来确定元素是否被删除。
这个函数使用的较少。
// remove()
void test_list8()
{int myints[]= {17,89,7,14};std::list<int> mylist (myints, myints + 4);mylist.remove(89);std::cout << "mylist contains:";for (std::list<int>::iterator it = mylist.begin(); it != mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
8.3、remove_if()
和remove()直接比较val的值不相同,remove_if()传入的是一种函数,通过函数运算来判断是否删除val,如果结果为真,则删除该节点。
从容器中删除Predicate pred返回true的所有元素。这将调用这些对象的析构函数,并通过删除的元素数量来减小容器大小。
该函数为每个元素调用pred(*i)(其中i是该元素的迭代器)。列表中返回true的任何元素都将从容器中删除。
// 函数:小于10为真
bool single_digit (const int& value) { return (value < 10); }// 仿函数:奇数为真
struct is_odd {bool operator() (const int& value) { return (value % 2) == 1; }
};// remove_if()
void test_list9()
{int myints[]= {15,36,7,17,20,39,4,1};std::list<int> mylist (myints, myints + 8); // 15 36 7 17 20 39 4 1mylist.remove_if (single_digit); // 15 36 17 20 39mylist.remove_if (is_odd()); // 36 20std::cout << "mylist contains:";for (std::list<int>::iterator it = mylist.begin(); it != mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
8.4、unique()
没有参数(1)的版本从容器中每个连续的相等元素组中删除除第一个元素外的所有元素。
请注意,只有当一个元素与前一个元素的值相等时,它才会从列表容器中删除。因此,此函数对于排序列表特别有用。
第二个版本(2)采用一个特定的比较函数作为参数,该函数确定元素的“唯一性”。事实上,任何行为都可以实现(不仅仅是等式比较),但请注意,该函数将对所有元素对调用binary_pred(*i,*(i-1))(其中i是元素的迭代器,从第二个开始),如果谓词返回true,则从列表中删除i。
被移除的元素被销毁。
#include <cmath>
// 函数: 整数部分相同
bool same_integral_part (double first, double second)
{ return ( int(first) == int(second) ); }// 仿函数: 差距小于5
struct is_near {bool operator() (double first, double second){ return (fabs(first - second) < 5.0); }
};// unique()
void test_list10()
{double mydoubles[]={ 12.15, 2.72, 73.0, 12.77, 3.14,12.77, 73.35, 72.25, 15.3, 72.25 };std::list<double> mylist (mydoubles, mydoubles + 10);mylist.sort(); // 2.72, 3.14, 12.15, 12.77, 12.77,// 15.3, 72.25, 72.25, 73.0, 73.35mylist.unique(); // 2.72, 3.14, 12.15, 12.77// 15.3, 72.25, 73.0, 73.35mylist.unique (same_integral_part); // 2.72, 3.14, 12.15// 15.3, 72.25, 73.0mylist.unique (is_near()); // 2.72, 12.15, 72.25std::cout << "mylist contains:";for (std::list<double>::iterator it=mylist.begin(); it!=mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
8.5、merge()
通过将x在其各自有序位置的所有元素转移到容器中(两个容器都应已排序),将x合并到列表中。
这有效地删除了x中的所有元素(变为空),并将它们插入到容器中的有序位置(容器的大小会随着传输的元素数量而扩展)。该操作在不构造或销毁任何元素的情况下执行:无论x是左值还是右值,或者value_type是否支持move构造,它们都会被传输。
具有两个参数(2)的模板版本具有相同的行为,但采用特定的谓词(comp)来执行元素之间的比较操作。这种比较将产生元素的严格弱序(即,不考虑其反身性的一致传递性比较)。
此函数要求列表容器的元素在调用之前已经按值(或按comp)排序。有关无序列表的替代方法,请参阅list::splice。
假设这样的排序,x的每个元素都根据运算符<或comp定义的严格弱排序插入到与其值对应的位置。等效元素的顺序是稳定的(即,等效元素保持调用前的相对顺序,现有元素位于从x插入的等效元素之前)。
如果(&x==this),则函数不执行任何操作。
// merge()
void test_list11()
{std::list<double> first, second;first.push_back (3.1);first.push_back (2.2);first.push_back (2.9);second.push_back (3.7);second.push_back (7.1);second.push_back (1.4);first.sort();second.sort();first.merge(second);// second 已经空了second.push_back (2.1);first.merge(second, mycomparison);std::cout << "first contains:";for (std::list<double>::iterator it = first.begin(); it != first.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';
}
8.6、sort()
链表因为区间不连续所以不能使用algorithm中的sort,而是使用自己写的sort。从底层逻辑上来说,链表的排序要复杂于标准库中的排序。
对列表中的元素进行排序,改变它们在容器中的位置。
排序是通过应用一种算法来执行的,该算法使用运算符<(在版本(1)中)或comp(在版(2)中)来比较元素。这种比较将产生元素的严格弱序(即,不考虑其反身性的一致传递性比较)。
等价元素的顺序是稳定的:即等价元素保持调用前的相对顺序。
整个操作不涉及任何元素对象的构建、销毁或复制。元素在容器内移动。
这里举例可以参考merge()函数中的举例,其他的比较也是相似的。
8.7、reverse()
这里不要看错了是reverse不是reserve,list容器是没有reserve的。
反转列表容器中元素的顺序。
比较简单,就不做举例了。
三、list模拟
list模拟除了上面介绍的函数之外,还有迭代器部分。它的迭代器是由一个包含节点指针的类封装的,其中还会有构造函数、拷贝构造、赋值重载等函数。为了方便普通迭代器和const迭代器的分别封装,迭代器类采用的是模版,以此来控制返回值是否能够修改,具体使用方法可以参考举例所给出的迭代器代码。和vector相同,模拟实现的时候由于insert功能是可以涵盖push类的,所以push的底层是由insert实现的,而erase和pop函数的关系和前者相似。
1、模拟代码
#include <iostream>
#include <assert.h>
#include <string>
#include <vector>
using namespace std;namespace lcs
{// List的节点类template<class T>struct ListNode{ListNode(const T& val = T()):_val(val),_pPre(nullptr),_pNext(nullptr){}ListNode<T>* _pPre;ListNode<T>* _pNext;T _val;};//List的迭代器类template<class T, class Ref, class Ptr>struct ListIterator{typedef ListNode<T>* PNode;typedef ListIterator<T, Ref, Ptr> Self;public:ListIterator(PNode pNode = nullptr):_pNode(pNode){}ListIterator(const Self& l):_pNode(l._pNode){}Ref operator*(){return _pNode->_val;}Ptr operator->(){return &(_pNode->_val);}Self& operator++(){_pNode = _pNode -> _pNext;return *this;}Self operator++(int){_pNode = _pNode -> _pNext;return _pNode -> _pPre;}Self& operator--(){_pNode = _pNode -> _pPre;return *this;}Self operator--(int){_pNode = _pNode -> _pPre;return _pNode -> _pNext;}bool operator!=(const Self& l){return _pNode != l._pNode;}bool operator==(const Self& l){return !(*this != l);}PNode _pNode;};//list类template<class T>class list{typedef ListNode<T> Node;typedef Node* PNode;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;public:///// List的构造list(){CreateHead();}list(int n, const T& value = T()){CreateHead();while(n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();while(first != last){push_back(*first);++first;}}list(const list<T>& l){CreateHead();for(const auto& e : l){push_back(e);}}list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete _pHead;_pHead = nullptr;}///// List Iteratoriterator begin() {//使用首元素节点的指针构造迭代器return _pHead -> _pNext;}iterator end(){//使用自身节点指针构造迭代器return _pHead;}const_iterator begin() const{//使用首元素节点的指针构const造迭代器return _pHead -> _pNext;}const_iterator end() const{//使用自身节点指针构造const迭代器return _pHead;}///// List Capacitysize_t size()const{return _size;}bool empty()const{return !size();}// List AccessT& front(){// 检查链表中是否有元素assert(!empty());return _pHead->_pNext->_val;}const T& front()const{// 检查链表中是否有元素assert(!empty());return _pHead->_pNext->_val;}T& back(){// 检查链表中是否有元素assert(!empty());return _pHead->_pPre->_val;}const T& back()const{// 检查链表中是否有元素assert(!empty());return _pHead->_pPre->_val;}// List Modifyvoid push_back(const T& val) { insert(end(), val); } // 尾差void pop_back() { erase(--end()); } // 尾删void push_front(const T& val) { insert(begin(), val); } // 头插void pop_front() { erase(begin()); } // 头删// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){// 记录插入节点的前后节点位置PNode cur = pos._pNode;PNode prev = cur->_pPre;// 申请新节点PNode newnode = new Node(val);// 接入新节点newnode->_pNext = cur;newnode->_pPre = prev;prev->_pNext = newnode;cur->_pPre = newnode;// 数据+1++_size;return newnode;}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());// 记录删除节点前后位置PNode next = pos._pNode->_pNext;PNode prev = pos._pNode->_pPre;// 链表前后重新连接prev->_pNext = next;next->_pPre = prev;// 释放删除节点的空间delete pos._pNode;// 数据个数-1_size--;// 返回原来相对位置return prev;}void clear(){iterator it = begin();while(it != end()){it = erase(it);}_size = 0;}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:void CreateHead(){_pHead = new Node;_pHead->_pNext = _pHead->_pPre = _pHead;_size = 0;}PNode _pHead;size_t _size;};
}
2、测试代码
#include "list.hpp"void test_list1()
{lcs::list<int> Lt;Lt.push_back(1);Lt.push_back(2);Lt.push_back(3);Lt.push_back(4);lcs::list<int>::iterator it = Lt.begin();while(it != Lt.end()){cout << *it << " ";it++;}cout << endl;
}void point_mylist(const lcs::list<string> Lt)
{for(const auto& e : Lt){cout << e;}cout << endl;
}void test_list2()
{lcs::list<string> Lt1;Lt1.push_back("hello ");Lt1.push_back("world ");Lt1.push_back("I love");lcs::list<string> Lt2(3, "haha");point_mylist(Lt2);Lt2 = Lt1;Lt2.push_back(" you");point_mylist(Lt2);
}int main()
{test_list1();test_list2();return 0;
}
根据以上代码,list模拟中的所有代码均以简单的使用到了,不排除复杂模拟出现bug的可能性。
结语
对于list来说,难点有二,一是新增了一些不常用的函数、另一个是迭代器的模拟用类进行封装。之后学习map的时候,模拟起来对于迭代器部分还会更加的困难、晦涩。如果掌握了也很简单,建议是多手操几次,一次理解不了很正常。
如果想更好的掌握容器的使用,也可以到牛客网或者leetcode中找一些简单的题目熟系一下容器的使用。
下一篇论文会讲讲适配器的使用,性能的比较等等,内容比较杂。但是有很多是旧东西,所以如果前面的知识都掌握好了的话,新东西不多,主要是deque和仿函数。