zoukankan      html  css  js  c++  java
  • STL-顺序容器及适配器

    序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素值,而是与元素加入容器时的位置相对应。

    9.1 概述

    所有顺序容器都提供了快速顺序访问元素的能力。但是,这些容器在以下方面都有不同的性能折中:

    -向容器添加或从容器删除元素的代价;

    -非顺序访问容器中元素的代价。

    顺序容器类型:

    vector  可变大数组。支持随机快速访问。在尾部之外的位置插入或删除元素可能很慢。

    deque  双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。

    list  双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。

    forawrd_list  单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。

    array  固定大小数组。支持快速随机访问。不能添加或删除元素。

    string  与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

    array固定大小;vector和string在中部插入/删除需要调整元素位置保持连续性;list和forward_list元素散布,依靠指针维持前后顺序,内存开销较大;deque支持快速随机访问,但支持头尾两端快速插入/删除,中部插入/删除也较慢(其结构较复杂)。

    forward_list和array是C++11新增加。array与内置数组相比,空间是在堆上分配。forward没有size操作,其设计用来达到与最好的手写单向链表性能接近,没有多余操作。

    对顺序容器使用时使需求和容器性能相适应,还可以使用多个容器配合使用,容器之间内容的复制很方便。

    9.2 容器库概览

    容器类型上的操作形成了一种层次:

    -某些操作是所有容器类型都提供的。

    -另外一些操作仅针对顺序容器、关联容器或无序容器。

    -还有一些操作只适用于一小部分容器。

    容器操作:(常用的)

    类型别名

    iterator  迭代器类型

    const_iterator  可以读取元素,但不能修改元素的迭代器类型

    size_type  无符号整数类型,足够保存此种容器类型最大可能容器的大小。

    value_type  元素类型

    reference  元素的左值类型;与value_type&含义相同

    const_reference  元素的const左值类型,const value_type&

    构造函数

    C c;  默认构造函数,构造空容器,

    C c1(c2);  构造c2的拷贝c1;

    C c(b,e);  构造c,将迭代器b和e指定的范围内的元素拷贝到c(array不支持);

    C c{a,b,c...};  列表初始化;

    赋值与swap

    c1=c2  将c1中的元素替换为c2中元素

    c1={a,b,c...}  将c1中的元素替换为列表中元素(不适用于array)

    a.swap(b)  交换a和b的元素

    swap(a,b)  交换a和b的元素

    大小

    c.size()  c中元素的数目(不支持forward_list)

    c.max_size()  c可保存的最大元素数目

    c.empty()  若c中存储了元素,返回false,否则返回true

    添加/删除元素(不适用于array)-在不同的容器中,这些操作的接口都不同

    c.insert(args)  将args中的元素拷贝进c

    c.emplace(inits)  使用inits构造c中的一个元素

    c.erase(args)  删除args指定的元素

    c.clear()  删除c中所有元素,返回void

    关系运算符

    ==,!=  所有容器都支持相等/不等运算符

    <, <=, > ,>=  关系运算符(无序容器不支持)

    获取迭代器

    c.begin(), c.end()  返回指向c的首部元素和尾部元素之后位置的迭代器

    c.cbegin(), c.cend()  返回const_iterator

    反向容器的额外成员(不支持forward_list)

    reverse_iterator  按逆序寻址元素的迭代器

    const_reverse_iterator  不能修改元素的逆序迭代器

    c.rbegin(), c.rend()  返回指向c的尾元素和首元素之前位置的迭代器

    c.crbegin(), c.crend() 返回const_reverse_iterator

    9.2.1迭代器

    标准迭代器的运算符:*it.smt ,it->smt,  ++, --, == ,!=

    forward_list不支持迭代器的--,单向链表(单向指针)

    一个迭代器范围由一堆迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。左闭右开区间。

    9.2.4 容器定义和初始化

    只有顺序容器的构造函数才能接受大小参数。

    C c;  C c1(c2);  C c1=c2;  C c{a,b,c,...};  C c={a,b,c...};  C c(b,e);迭代器区间内容复制;  

    顺序容器特有的构造函数: C seq(n);默认初始化;  C seq(n,t);n个元素,每个元素初始化为t。

    标准库array具有固定大小:当定义一个array时,必须指定其大小

    array<int,42>  //类型为:保存42个int的数组

    array<string,10>  //保存10个string的数组

    与其它容器不同,一个默认构造的array是非空的:它包含了于其大小一样多的元素。这些元素都被默认初始化,就像一个内置数组那样。

    如果我们对array进行列表初始化,初始值的数目必须等于或小于array的大小。如果初始值数目小于array的大小,则它们被用来初始化array中靠前的元素,所有剩余元素都会进行值初始化。在这两种情况下,如果元素类型是一个类类型,那么该类必须有一个默认构造函数,以使值初始化能够进行

    9.2.5 赋值和swap

    容器赋值运算:

    c1=c2;  将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型。

    c1={a,b,c...};  将c1中元素替换为列表中元素的拷贝(array不适用,但array构造时可用列表形式构造,列表元素数目小于等于array的大小)。

    swap(c1,c2);c1.swap(c2);  交换c1和c2。

    assign操作不适用于关联容器和array

    seq.assign(b,e);  将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素。

    seq.assign(il);  将seq中的元素替换为初始化列表il中的元素。

    seq.assign(n,t);  将seq中的元素替换为n个值为t的元素。

    赋值运算符要求左边和右边的运算对象具有相同类型。顺序容器(除array外)还定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

    swap交换两个相同类型容器的内容。除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。

    元素不会被移动意味着,除了string外,指向容器的迭代器、引用和指针在swap操作后都不会失效。而对一个string调用swap会导致迭代器、引用和指针失效。

    而swap两个array会真正交换它们的元素。因此,交换两个array的时间与元素数目成正比。

    在新标准中,容器即提供成员函数版本的swap,也提供非成员版本的swap。

    9.3 顺序容器操作

    9.3.1 向顺序容器添加元素

    这些操作会改变容器大小,所以array不支持这些操作。

    forward_list有自己专有版本的insert和emplace;forward_list不支持push_back和emplace_back。

    vector和string不支持push_front和emplace_front。

    c.push_back(t)

    c.emplace_back(inits);

    c.push_front(t);

    c.emplace_front(inits);

    c.insert(p,t); c.emplace(args);  在迭代器p指向的元素之前创建一个值为t或由args创建的元素。返回指向新添加的元素的迭代器。

    c.insert(p,n,t);  在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器。若n为0,则返回p。

    c.insert(p,b,e);  在迭代器p指向的元素之前插入由迭代器b和e指向的区间,返回指向新添加的第一个元素的迭代器。b和e不能是p所在容器的迭代器。

    c.insert(p,il);  il是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器;若列表为空,则返回p

    向一个vector、string和deque插入元素会使所有指向容器的迭代器、引用和指针失效。

    在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector或string添加元素可能引起整个对象存储空间的重新分配。

    emplace系列函数是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造函数。

    而push_back、push_front、insert会创建一个临时对象,并将对象压入要添加的容器中。

    9.3.2 访问元素

    如果容器中没有元素,访问操作的结果是未定义的。

    包括array在内的每个顺序容器都有一个front成员函数,而除了forward_list之外所有顺序容器都有一个back成员函数。这两个函数分别返回首元素和尾元素的引用。

    在顺序容器中访问元素的操作:

    at和下标操作只适用于string、vector、deque和array。

    back不适用于forward_list。

    c.back()

    c.front();

    c[n];  //n越界时函数行为未定义

    c.at(n);  //如果下标越界,则抛出一out_of_range异常。

    访问成员函数返回的是引用。

    9.3.3 删除元素

    非array容器有很多删除元素的方式。

    顺序容器的删除操作:

    这些操作会改变容器的大小,所以array不使用。

    forward_list有特殊版本的erase,forward_list不支持pop_back;vector和string不支持pop_front。

    c.pop_back();

    c.pop_front();

    c.erase(p);  删除p所指的元素,返回一个指向被删元素之后元素的迭代器。

    c.erase(b,e);  删除迭代器b和e所指范围内的元素。返回一个指向最后一个被删元素之后的元素的迭代器,若e本身就是尾后迭代器,则函数行为未定义。

    c.clear();

    删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效

    删除元素之前,必须由程序员来检查元素是否存在。

    9.3.4 特殊的forward_list操作

    forward_list中插入或删除元素的操作:

    lst.before_begin()  返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。cbefore_begin()返回一个const_iterator。

    lst.cbefore_begin()

    lst.insert_after(p,t) lst.insert_after(p,n,t) lst.insert_after(p,b,e) lst.insert_after(p,il)  在迭代器p之后的位置插入元素。t是一个对象,n是数量,b和e是表示一个范围的迭代器(b和e不能是在lst内),il是一个花括号列表。返回一个指向最后一个插入元素的迭代器。如果范围为空,则返回p。若p为尾后迭代器,则函数未定义。

    emplace_after(p,args)  使用args在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数未定义。

    lst.erase_after(p) lst.erase_after(b,e)  删除p指向的位置之后的元素,或删除从b之后直到(但不包含)e之间的元素。返回一个指向被删元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器。如果p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义。

    9.3.5 改变容器大小

    顺序容器大小操作:

    resize不适用于array

    c.resize(n)  调整c的大小为n个元素。若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化。

    c.resize(n,t)  调正c的大小为n个元素。任何新添加的元素都被初始化为值t。

    如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能会导致迭代器、指针和引用失效。

    9.3.6 容器操作可能使迭代器失效

    在向容器添加元素后:

    -如果容器是vector或string。且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后的迭代器、指针和引用将会失效。

    -对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。

    -对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。

    当我们删除一个元素后:

    -对于list和forward_list,指向容器其它位置的迭代器(包括尾后迭代器和直前迭代器)、引用和指针仍有效。

    -对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其它元素的迭代器、引用或指针也会失效。如果删除deque的尾元素,则尾后迭代器也会失效,但其它迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。

    -对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。

    注意:当我们删除元素时,尾后迭代器总是会失效。

    9.4 vector对象增长

    vector将元素连续地存储。分配的内存空间在某时是一定的,但容器大小可以改变。当容器插入元素而空间不足时,需要为vector或string重新分配空间且新空间比原空间大得多,多出来这部分作为备用(防止每次插入都要重新分配)。

    容器大小管理操作:

    shrink_to_fit只适用于vector、string和deque

    capacity和reverse只适用于vector和string。

     c.shrink_to_fit()  将capacity()减少为与size相同大小。

    c.capacity()  不重新分配内存空间的话,c可以保存多少元素。

    c.reserve(n)  分配至少能容纳n个元素的内存空间。

    只有当需要的内存空间超过当前容量时,reserve才会改变vector的容量。reserve需要小于当前容量时,什么也不做。

    调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间。

    vector内存变长分配时,先在堆中找到一块够大的空间(原空间的1.5或2倍),将原空间的内容移过去,并将在栈中的vector变量指向这个新分配的空间。

    9.5 额外的string操作

    9.5.1 构造string的其它方法

    string除了与其它顺序容器相同的构造函数外,还有另外3个构造函数:

    n、len2和pos2都是无符号值。

    string s(cp, n)  s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符。

    string s(s2, pos2)  s是string s2从下标开始的字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。

    string s(s2, pos2, len2)  s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符。

    substr操作

    substr操作返回一个string,它是原始string的一部分或全部的拷贝,可以传递给substr一个可选的开始位置和计数值:

    s.substr(pos, n)  返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0.n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。

    9.5.2 改变string的其它方法

    string类型支持顺序容器的赋值运算符以及assign、insert和erase操作。

    除了接收迭代器的insert和erase版本外,string还提供了接受下标的版本,下标指出开始删除的位置,或是insert到给定值之前的位置。

    标准库string类型还提供了接受C风格字符数组的insert和assign版本。const char *cp="cjjjjj";s.assign(cp,7);

    修改string的操作:

    s.insert(pos,args)  在pos之前插入args指定的字符。pos可以是一个下标或一个迭代器。接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。

    s.erase(pos,len)  删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始直至s末尾的所有字符。返回一个指向s的引用。

    s.assign(args)  将s中的字符替换为args指定的字符。返回一个指向s的引用。

    s.append(args)  将args追加到s。返回一个指向s的引用。

    s.replace(range, args)  删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用。

    例如:str.append("cjj"); str.replace(11,3,"5th");

    9.5.3 string的搜索操作

    搜索操作返回指定字符出现的下标,如果未找到则返回npos。

    s.find(args)  查找s中args第一次出现的位置。

    s.rfind(args)  查找s中args最后一次出现的位置。

    s.find_first_of(args)  在s中查找args中任何一个字符第一次出现的位置。

    s.find_last_of(args)  在s中查找args中任何一个字符最后一次出现的位置。

    s.find_first_not_of(args)  在s中查找第一个不在args中的字符。

    s.find_last_not_of(args)  在s中查找最后一个不在args中的字符。

    args必须是以下形式之一:

    c,pos  从s中位置pos开始查找字符c。pos默认为0

    s2,pos  从s中位置pos开始查找字符串s2。pos默认为0

    cp,pos  从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0

    cp,pos,n  从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值。

    9.5.4 compare函数

    标准库string类型提供了一组compare成员函数。

    compare有6个版本:3个与string比较,3个与C风格字符串比较

    s.compare(s2)  比较s和s2

    s.compare(pos1,n1,s2)  将s中从pos1开始的n1个字符与s2比较

    s.compare(pos1,n1,s2,pos2,n2)  将s中从pos1开始的n1个字符与s1中从pos2开始的n2个字符进行比较

    s.compare(cp)

    s.compare(pos1,n1,cp)

    s.compare(pos1,n1,cp,n2)

    9.5.5 数值转换

    string和数值之间的转换

    to_string (val)  一组重载函数,返回数值val的string表示。val可以是任何算术类型。对每个浮点类型和int或更大的整型,都有相应版本的to_string。小整型会被提升

    stoi(s,p,b) stol(s,p,b) stoul(s,p,b) stoll(s,p,b) stoull(s,p,b) 返回s的起始子串(表示整数内容)的数值,返回值类型分别是int、long、unsigned long、long long、unsigned long long。b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认值为0,即函数不保存下标。

    stof(s,p) stod(s,p) stold(s,p)  返回s的起始子串(表示浮点数内容)的数值,返回值类型分别是float、double或long double。参数p的作用与整数转换函数中一样。

    9.6 容器适配器

    除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。适配器是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另一种事物一样。

    容器适配器底层是顺序容器实现的,如stack可以是deque或vector封住了其他功能,留下部分(从尾部插入、尾部删除、empty、size等)

    所有容器适配器都支持的操作和类型:

    size_type  一种类型,足以保存当前类型的最大对象的大小。

    value_type  元素类型。

    container_type  实现适配器的底层类型

    A a;  创建一个名为a的空适配器。

    A a(c);  创建一个名为a的适配器,带有容器c的一个拷贝。

    关系运算符  每个适配器都支持所有关系运算符:==、!=、<、<=、>和>=这些运算符都返回底层容器的比较结果。

    a.empty()  若a包含任何元素,返回false,否则返回true。

    a.size()  返回a中的元素数目。

    swap(a, b) a.swap(b)  交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同。

    所有适配器都有添加和删除元素的功能:

    栈stack的元素操作:

    栈默认基于deque实现,也可以在list或vector之上实现。

    s.pop()  删除栈顶元素

    s.push(item) s.emplace(args)  创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或者由args构造

    s.top()  返回栈顶元素,但不将元素弹出栈。

    队列适配器:queue和priority_queue适配器定义在queue文件中。

    queue默认基于deque实现,priority_queue默认基于vector实现。

    queue也可用list或vector实现,priority_queue也可用deque实现。

    q.pop()  删除queue的首元素或priority_queue的最高优先级元素,但不反悔此元素。

    q.front()  返回首元素,只适用于queue

    q.back()  返回尾元素,只适用于queue

    q.top()  返回最高优先级元素,但不删除该元素,只适用于priority_queue

    q.push(item) q.emplace(args)  在queue末尾或priority_queue中恰当位置创建一个元素,其值为item,或者由args构造。

    标准库queue使用先进先出FIFO的存储和访问策略。

    priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。默认情况下,标准库在元素类型上使用<运算符来确定相对优先级

    priority_queue的push和pop实现都是采用heap算法,默认大顶堆。

    例如:我们定义一个以vector为顶层顺序容器,大顶堆排序的存放int型数据的优先级队列。

    默认下可以使用:priority_queue<int>pq;

    展开后:priority_queue<int,vector<int>,greater<int>>pq;  //第二个参数指定底层容器,第三个指定比较方式。第三个比较方式我们可以自己定义,在泛型编程中一般可使用仿函数(一个类只重载小括号运算符)。

  • 相关阅读:
    【uoj3】 NOI2014—魔法森林
    【bzoj2002】 Hnoi2010—Bounce 弹飞绵羊
    【hdu4010】 Query on The Trees
    【uoj129】 NOI2015—寿司晚宴
    【bzoj2877】 Noi2012—魔幻棋盘
    【bzoj2876】 Noi2012—骑行川藏
    【bzoj2875】 Noi2012—随机数生成器
    【codeforces 235E】 Number Challenge
    【bzoj2154】 Crash的数字表格
    【bzoj3529】 Sdoi2014—数表
  • 原文地址:https://www.cnblogs.com/cjj-ggboy/p/12402748.html
Copyright © 2011-2022 走看看