《c++primer》 第9章 顺序容器

9.1概览

1.forword_list和array是c++11增加的类型。array大小固定不能增加删除元素以及改变容器的大小,但是支持拷贝和对象赋值操作。


2.多使用标准容器库。除非有更好的选择,否则用vector。


3.迭代器的范围一般是左闭右开。


4.容器可以进行列表初始化。(c++11)

#include <iterator>    //迭代器,包含c++11的begin()和end()函数
#include <array>       //c++11数组类型,长度固定,提供了更好更安全的接口,执行效率和内置数组相同,可以有效替代内置数组
#include <valarray>    //c++11值类型的数组类型,针对值类型的数组有更多的操作,比如求和,最大最小数等
#include <list>        //双向链表,插入删除操作很快,不支持随机访问
#include <forward_list>//c++11单向链表,单向访问,插入删除速度快,不支持随机访问,没有size操作
#include <deqeue>      //双端队列,支持快速随机访问
#include <string>      //string插入删除耗时
#include <vector>      //可变大小数组,支持快速随机访问。在尾部之外的位置插入或删除可能很慢

//c++11 vector<vector<int>> 最后两个>>可以不增加空格

5.迭代器不能比较大小。


6.begin和end:c++11新增加了auto和begin,end的结合用法。增加了cbegin和crbegin。

    list<string>il = {"hello", "world", "wang", "wei", "hao" };  
    auto it1 = il.begin();         //list<string>::iterator  
    auto it2 = il.cbegin();        //list<string>::const_iterator  
    auto it3 = il.rbegin();        //list<string>::reverse_iterator  
    auto it4 = il.crbegin();       //list<string>::const_reverse_iteratror  
    cout << *it1 << endl;  
    //*it2 = "ww"; error:const类型不能修改  
    cout << *it2 << endl;  
    cout << *it3 << endl;  
    cout << *it4 << endl;  
    //*it4 = "ww"; error:const类型不能修改 

7.容器的定义和初始化

<1.初始化

    vector<int>ivec1;                            //默认初始化  
    vector<int>ivec2 = {1,2,3,4,5,6,7,8,9,0};    //列表初始化  c++11
    //vector<int>ivec2{1,2,3,4,5,6,7,8,9,0};   
    vector<int>ivec3(ivec2);                     //拷贝初始化  
    //vector<int>ivec3 = ivec2;  
    vector<int>ivec4(ivec1.begin(), ivec1.end());//迭代器初始化  
    vector<int>ivec5(10);                        //n个元素初始化  
    vector<int>ivec6(10, 9);                     //n个元素加初始值初始化 

<2.为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配不过当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的。而且新容器和原容器中的元素类型也可以不同,只要能将拷贝的元素转换即可。

<3.顺序容器(array除外)还提供另一个构造函数,它接受一个容器大小和一个(可选地)元素初始值。

如果元素类型是内置类型或者具有默认构造函数,可以只提供一个容器大小的参数,如果没有,必须还要显示指定元素初始值。

只有顺序容器的构造函数才接受大小参数,关联容器并不支持。

<4.标准库array具有固定大小

array类型是c++11数组类型,长度固定(必须初始的时候指定),提供了更好,更安全的接口,执行效率和内置数组相同,可以有效替代内置数组。

标准库array的大小也是类型的一部分,当定义一个array时,除了指定元素类型,还要指定容器大小。

array<int, 10>a = {1,2,3,4,5,6,7,8,9,0};  
array<string, 10>  
array<int, 10>b = a;  
//但是内置类型就不支持数组复制 

8.赋值和swap

<1.赋值运算符将左边容器的全部元素替换为右边容器的拷贝。

与内置数组不同,array类型允许赋值,赋值左右两边的对象必须有相同的类型。array不支持assign,也不允许用花括号包围的值列表进行赋值。

<2.交换两个容器保证会很快,元素本身并为交换,swap只是交换了两个容器的内部结构。意味着指向容器的迭代器,引用,指针在swap操作之后都不会失效.

与其他容器不同,swap两个array会真正交换他们的元素。


9.容器大小操作

size( ):返回容器中元素的个数

empty( ):查看容器是否为空,size=0时返回true

max_size( ):返回一个大于或等于该容器所能容纳最大元素数的值

forward_list不支持size( )操作。


10.关系运算符

关系运算符左右两端必须容器类型相同。

只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。


9.3.顺序容器的操作

1.向顺序容器里添加元素

除了array以外,所有标准库容器都能提供灵活的内存管理

#include <list>  
#include <string>  
#include <iostream>  
#include <vector>  
  
using namespace std;  
  
class people  
{  
    public:  
        people() = default;  
        people(double h):height(h), sex(0), name(" "), age(0) { }  
        people(double h, bool s, string na, int ag):  
            height(h), sex(s), name(na), age(ag) { }  
                
  
  
        double height = 0.0;  
        bool sex = 0;  
        string name = "";  
        int age = 0;  
};  
  
int main()  
{  
    vector<people>ivec;  
    people p1(188, 0, "wangweihao", 19);  
    ivec.push_back(p1);                                //c.push_back()尾部"创建"一个元素,返回void,注意创建这个词,说明它会重新建立一个元素,而不是以前的  
    ivec.emplace_back(171, 1, "meiyouren", 19);        //C++11 c.emplace_back()同上,区别是传递的是参数,emplace传递的是参数,不是对象,就如左边  
    list<people>il;                                    //一样,传递的是类的参数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。  
    il.push_front(p1);                                 //c.push_front()头部创建一个元素,返回void  
    il.emplace_front(171, 1, "meiyouren", 19);         //同上  
    il.emplace_front(171);                             //因为emplace会用参数通过容器构造对象,所以只传递了171参数,构造时调用people类的只含h的构造函数  
    auto iter = il.begin();  
    iter = il.insert(iter, p1);                        //c.insert(p, t)  p是迭代器类型,指定位置插入t对象  
    il.emplace(iter, 171, 1, "haha", 20);              //同上,传递的是参数  
    for(people &p : ivec)  
    {  
        cout << "name:" <<p.name << " sex:" << p.sex << " height:" << p.height << " age:" << p.age << endl;  
    }  
    cout << endl;  
    for(people &p : il)  
    {  
        cout << "name:" <<p.name << " sex:" << p.sex << " height:" << p.height << " age:" << p.age << endl;  
    }  
    vector<int>ivec2 = {1};  
    auto iter2 = ivec2.begin();  
    ivec2.insert(iter2, 3, 10);                        //c.insert(p, n, t) 迭代器p位置插入n个t元素  
    vector<int>ivec3 = {1};  
    auto iter3 = ivec3.begin();  
    ivec3.insert(iter3, ivec2.begin(), ivec2.end());   //c.insert(p, b, e) 迭代器p位置插入另一个类型相同容器迭代器(b,e)范围内的元素  
    vector<int>ivec4;  
    //ivec4.insert(ivec.begin(), {1,1,1,1,1,1,1,1});   //c.insert(p, il)迭代器p位置插入il一个花括号包围的初始值列表。但运行会报错不知是编译器的问题还是  
    cout << "ivec2" << endl;  
    for(const int &i : ivec2)  
        cout << i << endl;  
    cout << "ivec3" << endl;  
    for(const int &i : ivec3)  
        cout << i << endl;  
    cout << "ivec4" << endl;  
    for(const int &i : ivec4)  
        cout << i << endl;  
  
}  

注意:

<<1. 向一个vector,string,deque插入元素会使所有指向容器的迭代器,引用,指针失效

<<2.当我们用一个对象来初始化容器时,或将一个对象插入到容器时,实际上放的是一个拷贝,而不是对象本身。

<<3.每个insert都接受一个迭代器作为第一个参数。

<<4.insert函数将元素都插入到指定的位置之前

<<5.将元素插入到vector,deque,string中的任何位置都是合法的。然而这样做可能会很耗时

<<6.c++11新标准下,接受元素的个数或范围的insert版本返回指向第一个元素的迭代器。如果范围为空,不插入任何元素。

<<7.使用emplace:新标准引入了三个成员,emplace_front, emplace, emplace_back,这些操作构造而不是拷贝元素。

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

emplace_back会在容器管理的内存空间中直接创建对象,而调用push_back则会创建一个局部临时对象,并将其压入容器中。

emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。


2.访问元素

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

当然可以用迭代器c.begin( )和(c.end( ))--。但是都要确保容器非空,如果容器为空,行为是未定义的。

顺序容器访问元素的操作例子

    vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
    //vector<int>ivec;  
    cout << ivec[0] << endl;            //c[n]       返回下标为n的元素的引用   n>=c.size() 结果是未定义的  
    cout << ivec.at(0) << endl;         //c.at(n)    返回下标为n的元素的引用,如果下标越界,则抛出异常out_of_range  
    cout << ivec.front() << endl;       //c.front()  返回容器的第一个元素  
    cout << ivec.back() << endl;        //c.back()   返回容器的最后一个元素  
    cout << *(ivec.begin()) << endl; 

注意:
<<1.迭代器c.end( )是末尾元素的下一个位置

<<2.为确保下标是合法的,我们可以使用at成员函数,at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常。


3.删除元素

c.pop_back() 删除尾元素,返回void  
c.pop_front()删除首元素,返回void  
c.erase(p)   删除迭代器p指向的元素,返回被删除元素的下一个元素的迭代器  
c.erase(b,e) 删除迭代器(b,e)范围内的元素,返回e的下一个元素的迭代器  
c.clear()    删除c中所有的元素  
注意:

<<1.删除操作会改变容器的大小,所以不适合array

<<2.forward_list不支持pop_back,   vector和string不支持pop_front

<<3.删除deque中除首尾位置之外的任何元素都会使所有迭代器,引用指针失效

<<4.删除前必须保证他们是存在的

<<5.注意删除返回迭代器的那些操作在循环中需要做出哪些改变!


4.特殊的forward_list操作

forward_list其实就是数据结构的单向链表

注意:

<<1.一般要处理整个容器时,要保存两个迭代器。curr用来找元素,prev用来删元素

auto curr = ft.begin( );  
auto prev = ft.before_begin( );  

<<2.erase_after了一定要记得修改curr。

curr = ft.erase_after(n);  

<<3.一定要注意函数的返回值和每次使用的迭代器指向的位置和使用函数后该迭代器应该怎样变化。


5.改变容器大小

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

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

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

!如果容器保存的是类类型的元素,向容器添加元素时我们必须提供初始值,或元素必须提供默认构造函数。


6.!容器操作可能使迭代器失效

是否会使迭代器失效还要看迭代器具体使用什么数据结构实现的。

比如forward_list这个容器就比较特殊,insert( )返回的是插入元素位置的迭代器。删除erase( p )删除的是p的下一个位置的元素,返回的是删除元素下一个位置的迭代器。

因为它是由单向链表实现,指针是单向的,我们不能返回去访问先前的元素,那么删除必须指定的元素必须要保存prev先前的迭代器,不然无法实现删除。

所以每个容器具体实现的数据结构是重点。

例:复制容器中元素值是奇数的,删除容器中的元素值是偶数的。

int main()  
{  
    //vector  
    vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
    auto iter = ivec.begin();  
    while(iter != ivec.end())  
    {  
        if(*iter %2 == 1)  
        {  
            iter = ivec.insert(iter, *iter);  //新建迭代器
            iter += 2;  //向后移两位
        }  
        else  
            iter = ivec.erase(iter);  
    }  
    for(const int&i : ivec)  
        cout << i << " ";  
    cout << endl;  
  
    //list  
    list<int>il = {1,2,3,4,5,6,7,8,9,0};  
    auto iter2 = il.begin();  
    while(iter2 != il.end())  
    {  
        if(*iter2%2 == 1)  
        {  
            il.insert(iter2, *iter2);  
            iter2++;  //迭代器向后移一位
        }  
        else  
        {  
            iter2 = il.erase(iter2);  
        }  
    }  
    for(const int&i : il)  
        cout << i << " ";  
    cout << endl;  
      
    //forward_list  
    forward_list<int>ft = {1,2,3,4,5,6,7,8,9,0};  
    auto fter1 = ft.before_begin();  
    auto fter2 = ft.begin();  
    while(fter2 != ft.end())  
    {  
        if(*fter2%2 == 1)  
        {  
            fter1 = fter2;  
            fter2 = ft.insert_after(fter2, *fter2);  
            fter1++;  
            fter2++;  //两个迭代器
        }  
        else  
        {  
            fter2 = ft.erase_after(fter1);  
        }  
    }  
    for(const int&i : ft)  
        cout << i << " ";  
    cout << endl;  
  
}  

例:给容器中每个元素后面插入值

#include <vector>  
#include <iostream>  
  
using namespace std;  
  
int main()  
{  
    vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
    auto iter = ivec.begin();  
    while(iter != ivec.end())  
    {  
        iter++; //前插  
        iter = ivec.insert(iter, 1);//插入后迭代器失效,如果不赋值给iter,iter失效。  
        iter++;  
    }  
    for(const int&i : ivec)  
        cout << i << " ";  
    cout << endl;  
}  

注意:

<<1.对于insert来说,除了forward_list插入是后插外,其他容器是前插,前插返回前插的元素的迭代器,记得保存,插入后迭代器会失效。后插返回的是后插元素的迭代器。

总之不管插入位置,只要插入返回的就是插入的元素的迭代器,但是要考虑它的位置是前插还是后插。

<<2.管理迭代器,确认每次改变容器的操作之后都正确的重新定位迭代器。这个对vector,string,deque尤其重要。


7.vector对象是如何增长的

影响容量的操作数量不多。

resize( )设定size()

reserve( )设定capacity( )

8.额外的string操作

<1.构造string的其他方法

int main()  
{  
    //s若拷贝字符数组必须有空字符结尾  
    //注意越界问题  
  
    char a[100] = "abcdefghijklmnopqrstuvwxyz";  
    string s(a, 10);               //string s(cp, n)   s是cp指向的数组中的前n个字符的拷贝,此数组至少应该包含n个字符  
    cout << s << endl;  
    string s2 = "wangweihao";  
    string s3(s2, 5);              //string s(s1, pos)   s是s1字符串从pos后面开始的字符串,如果n>s1.size()结果未定义  
    cout << s3 << endl;  
    string s4(s2, 5, 2);           //string s(s1, pos, len)  s是s1从pos位置后面的长度为len的字符串  
    string s5(a+5, 2);             //这种方式a必须是字符数组,不能是string类型  
    cout << s4 << endl;  
    cout << s5 << endl;  
  
    //substr操作返回一个string, 它是原始string的一部分或者全部的拷贝,可以传递开始和结尾  
    string ss = "hello,world";  
    string ss2;  
    ss2 = ss.substr(0,5);          //复制下标0-4  
    //ss2 = ss.substr(5);          //复制下标5到结束  
    cout << ss2 << endl;  
    ss2 = ss.substr(12);           //越界,发出out_of_range异常  
  
    return 0;  
}
<2.改变string的其他方法
int main()  
{  
    string s = "hello,world!";  
    //string除了assign和insert,erase操作,还定义了自己的insert和erase版本  
    s.insert(s.size(), 5, '!');        //末尾插入5个感叹号  
    cout << s << endl;  
    s.erase(s.size()-5, 5);            //删除最后的5个字符  
    cout << s << endl;  
  
    //标准库string还定义了接受c风格字符数组的insert和assign版本  
    string s1;    
    const char *p = "hello, world!";  
    s1.assign(p, 7);                   //复制p的前7个字符  
    cout << s1 << endl;  
    //s1.insert(s.size(), p+4);        //error: out_of_range  
    //cout << s1 << endl;  
  
    string s2 = "hello", s3 = "world";  
    s2.insert(0, s3);                  //将s3插入到s2的0起始位置  
    cout << s2 << endl;  
    s2.insert(0, s3, 0, s3.size()-1);  //从s2 的0处插入s3的(0, s3.size()-1)范围的元素  
    cout << s2 << endl;  
  
} 
int main()  
{  
    string s("c++ primer");  
    s.append(" 5th Ed.");   //在末尾追加字符串  
    //s.insert(s.size(), " 5th Ed."); 和上面等价  
    cout << s << endl;  
    //replace操作是调用erase和insert的一种简写方式  
    //s.replace(11, 3, "4th");  
    s.erase(11, 3);  
    s.insert(11, "4th");  
    cout << s << endl;  
}  

<3.string搜索操作

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

s.rfind(args)                            查找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          pos位置开始查找字符c,pos默认为0

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

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

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

例:

int main()  
{  
    //find,区分大小写  
    //搜索成功返回一个string::size_type值,若失败,返回一个string::npos的static成员(unsigned)  
    string s("wangweihao");  
    auto pos1 = s.find("wei");                //pos返回的是第一次出现的下标,若找不到则返回一个最大数  
    cout << pos1 << endl;  
    string s1("0123456789");  
    string name("0rrr1r");  
    //查找给定字符串中任何一个字符匹配的位置。  
    auto pos2 = name.find_first_of(s1);       //s1在name中出现的第一个数字在name中的下标  
    cout << pos2 << endl;  
    //搜索第一个不在参数中的字符  
    auto pos3 = s1.find_first_not_of(name);   //s1第一不在name中出现的是0  
    cout << pos3 << endl;  
}  

逆向搜索:

一般的find都是从左到右,rfind提供了从右到左。

find例子2:

找出小写字母和数字区分开。

字母用了—代替,数字用了|代替


int main()  
{  
    string s("abc2fe32f2ds2");  
    string s1("abcdefghijklmnopqrstuvwxyz");    //要去掉字符,那么首先要创建s1,s中的字符可以和s1匹配,然后替换  
    string s2("1234567890");                    //同理去掉数字  
    string::size_type pos = 0;                  //起始位置初始化为0  
    while((pos = s.find_first_of(s1, pos)) != string::npos)     //找到位置返回,如果没找到返回string::npos  
    {  
        s.replace(pos, 1, "-");  
          
        pos++;  
    }  
    cout << s << endl;  
    pos = 0;                                    //记得再次初始化,否则没有作用  
    while((pos = s.find_first_of(s2, pos)) != string::npos)  
    {  
        s.replace(pos, 1, "|");  
        pos++;  
    }  
    cout << s << endl;  
}  
<4.compare函数,compare函数和c函数的strcmp函数很相似

等于大于小于返回0,正数或者负数

compare的6个版本如下

int main()  
{  
    string s1 = "aaacaaaaa";  
    string s2 = "aaaaaaaab";  
    char *p = "aaaaaaaac";  
  
    cout << s1.compare(s2) << endl;             //s1>s2  
    cout << s1.compare(5, 3, s2) << endl;       //s1从第五个字符开始的3个字符小于s2  
    cout << s1.compare(0, 5, s2, 0, 5) << endl; //从s1的0开始的5个字符和s2从0开始的5个字符比较  
    cout << s1.compare(p) << endl;              //比较p指向的地址开始的字符串  
    cout << s1.compare(5, 4, p) << endl;        //比较s1从第5个开始的4个字符和p指向的地址的字符串  
    cout << s1.compare(5, 4, p, 4) << endl;     //比较s1从第5个开始的4个字符和p指向的地址的4个字符  
} 

<5.数值转换

新标准引入了多个函数,可以实现数值数据与标准库string之间的转换

int main()  
{  
    int a = 100;  
    string s;  
    s = to_string(a);  
    cout << s << endl;  
      
    std::size_t m = 2;  
    string s1 = "1000";  
    int i = stoi(s1); //stoi  
    long b = stol(s1);//stol  
    unsigned long c = stoul(s1);//stoul  
    long long d = stoll(s1);//stoll  
    unsigned long long e = stoull(s1);//stoull  
    cout << "int:" << i << "long:" << b << "unsigned long:" << c << "longlong:" << d << "unsigned longlong:" << e << endl;  
      
    string s2 = "1000.11";  
    float f = stof(s2);//stof  
    double g = stod(s2);//stod  
    long double h = stold(s2);//stold  
    cout << "float:" << f << "double:" << g << "long double:" << h << endl;  
} 

<9.容器适配器

container_type是实现适配器的底层容器类型

三种:stack, queue, priority_queue

本质上:适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样

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

理解:容器适配器是基于已有的容器做的功能上的一种改进。

定义一个适配器:

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


<1.stack

stack:一种元素先进后出的一种容器
stack可以使用除了array和forward_list类型的其他任何类型的容器来构造

//stack默认是deque实现,也可以在list或vector实现  
//s.pop()             删除栈顶元素,但不返回元素值  
//s.push(item)        创建一个新元素压入栈顶  
//s.emplace(args)     同前面所说args是参数,动态构造一个对象  
//s.top()             返回栈顶元素,但不将元素出栈 

<2.queue

queue:一种元素先进先出的容器

queue可以由list和deque来构造,但是不能由vector来构造,默认是deque构造

//q.pop()返回queue的首元素或priority_queue的最高优先级元素,不删除元素  
//q.front()返回首元素或尾元素,不删除  
//q.back()  
//q.top()返回最高优先级的元素,不删除,只适用于priority_queue  
//q.push(item)在queue末尾或priority_queue中恰当位置创建一个元素  
//q.emplace(args)动态构造对象 







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值