C++ Primer 第9章顺序容器

本文详细介绍了C++标准库中的顺序容器,包括vector、list和forward_list的特性及操作。重点讨论了迭代器的使用,元素的添加、访问和删除,以及容器的初始化和赋值。还提到了不同容器在添加和删除元素时迭代器可能的失效情况,以及如何安全地进行容器操作。

9.1 顺序容器概述

在这里插入图片描述
在这里插入图片描述

确定使用哪种顺序容器

通常使用vector是最好的选择
在这里插入图片描述

9.2 容器库概述(本节所有容器均适用)

在这里插入图片描述

对容器可以保存的元素类型的限制

  • 有些类没有提供默认构造函数,我们可以定义一个这种类型对象的容器,但我们在构造这种容器时不能只传递给它一个数目参数.
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

9.2.1 迭代器

  • 与容器一样,迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的.但是有一个例外:forward_list迭代器不支持递减运算符

迭代器范围

  • begin和end first和last(指向尾元素之后的元素)
    在这里插入图片描述

使用左闭合范围蕴含的编程假定

在这里插入图片描述
在这里插入图片描述

9.2.2 容器类型成员

  • 为了索引int的vector中的元素使用迭代器:vector<int>::iterator
  • 为了读取string的list中的元素:应该使用的类型是:list<string>::value_type
  • 为了写入数据,应该使用的类型是;list<string>::reference

9.2.3 begin和end成员

在这里插入图片描述

  • 不以c开头的函数都是被重载过的.也就是说,实际上有两个名为begin的成员,一个是const成员返回const_iterator,另外一个返回的是非常量成员iterator.当我们对一个非常量对象调用这些成元返回普通的.
    在这里插入图片描述

9.2.4 容器定义和初始化

在这里插入图片描述

将一个容器初始化为另外一个容器的拷贝

  • 将一个新容器创建为另外一个容器的拷贝的方法有两种:
    • 可以直接拷贝整个容器(除array)
    • 拷贝有一个迭代器对指定的范围
      在这里插入图片描述
    list<string> ls = {"1","2","3"};
    vector<const char *> vs = {"4","5","6"};

    list<string> list2(vs.begin(),vs.end());

在这里插入图片描述

列表初始化

与顺序容器大小相关的构造函数

在这里插入图片描述

标准库array具有固定大小

在这里插入图片描述
在这里插入图片描述

9.2.5 赋值和swap

在这里插入图片描述

使用assign(仅顺序容器)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.2.6 容器大小操作

在这里插入图片描述

9.2.7 关系运算符

  • 除了无序关联容器外的所有容器都支持关系运算符.关系运算符左右两边的运算对象必须是相同的容器,且必须保存相同类型的元素.
  • 运算符的工作方式与string的关系运算类似.

容器的关系运算符使用元素的关系运算符完成比较

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

9.3 顺序容器操作(顺序容器特有操作)

9.3.1 向顺序容器添加元素

在这里插入图片描述

  • 在一个vector或string的尾部之外的任何位置,或是deque的首尾之外的任何位置添加元素都需要移动元素

使用push_back(除array和forward_list之外)

在这里插入图片描述

使用push_front

  • list,forward_list,deque支持
  • deque和vector提供随机访问元素的能力

在容器的特定位置添加元素(insert)

  • insert(iterator,element)

插入范围内元素

  • 传递的迭代器不能指向添加新元素的目标容器
  • 新标准之下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器

使用insert返回值

在这里插入图片描述

使用emplace操作

在这里插入图片描述
在这里插入图片描述
向一个vector,string,deque插入元素会使现有指向容器的迭代器,引用,指针生效.这时就应该结合insert返回的元素来进行操作

9.3.2 访问元素

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

小标操作和安全的随机访问

  • 提供下标运算符的容器(string,vector,deque和arrate都提供下标运算符).
  • 如果我们希望确保下标是合法的,可以使用at成员函数.如果at下标越界,at会抛出一个out_of_range异常

9.3.3 删除元素

在这里插入图片描述

pop_front和pop_back成员函数

  • 与vector和string不支持push_front一样,这些类型也不支持pop_front
  • 不能对一个空容器执行1弹出操作
while(!ilist.empty()){
}

从容器内部删除一个元素(erase)

删除多个元素

slist.clear()
slist.erase(slist.begin(),slist.end())

在这里插入图片描述

int main()
{
    int a[] = {0,1,1,2,3,5,8,13,21,55,89};
    vector<int> vi ;
    list<int> li;
    vi.assign(a,a+11); // 将数组上的元素拷贝到vector中
    li.assign(a,a+11);

    vector<int>::iterator vit = vi.begin();

    while (vit != vi.end()){
        if(*vit %2 == 0){
            vit = vi.erase(vit);
        } else{
            vit++;
        }
    }

    vit = vi.begin();
    while (vit != vi.end()){
        cout<<*vit<<ends;
        vit++;
    }


    return 0;
}

9.3.4 特殊的forward_list操作

在这里插入图片描述

  • 由于forwar_list是一个单向列表,删除或者添加元素需要这个元素的前驱才能操作,但是在一个单链表中没有简单的方法获得一个元素的前驱节点,所以删除和添加元素操作都是通过改变给定元素之后的元素来完成的。
  • 由于这些操作与其他容器上的操作不同,forward_list并未定义insert,emplace,erase而是定义了insert_after,emplace_after,erase_after的操作。
    在这里插入图片描述
  • 当在forward_list中添加或删除元素时,我们必须关注两个迭代器,一个指向我们要处理的元素,另一个执行其前驱。

在这里插入图片描述

int main() {
    int a[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89};
    forward_list<int> fi;
    fi.assign(a, a + 11); // 将数组上的元素拷贝到forward_list中

    int x1 = 21, x2 = 2; //查找x1并将x2插入到紧挨x1之后的位置

    forward_list<int>::iterator it = fi.before_begin();//返回第一个元素前面的迭代器
    forward_list<int>::iterator nestIt = fi.begin();//返回首元素

    it = fi.begin();
    while (it != fi.end()) {
        cout << *it << ends;
        it++;
    }
    it = fi.before_begin();
    cout<<endl;
    
    //查找x1
    while (nestIt != fi.end()) {
        if (*nestIt == x1) {
            fi.erase_after(it);  //删除it之后的元素,it在nextIt的前面
            break;
        } else {
            it++;
            nestIt++;
        }
    }

    it = fi.begin();
    while (it != fi.end()) {
        cout << *it << ends;
        it++;
    }

    nestIt = fi.begin();
    //插入x1到x2的位置
    while (nestIt != fi.end()) {
        if (*nestIt == x2) {
            fi.insert_after(nestIt, x1);
            break;
        } else {
            nestIt++;
        }
    }


    cout << endl;
    it = fi.begin();
    //插入x1到x2的位置
    while (it != fi.end()) {
        cout << *it << ends;
        it++;
    }
/*
 * 0 1 1 2 3 5 8 13 21 55 89
0 1 1 2 3 5 8 13 55 89
0 1 1 2 21 3 5 8 13 55 89
 */
    return 0;
}

9.3.5 改变容器大小(resize)

在这里插入图片描述
在这里插入图片描述

  • 对于元素是类类型,则单参数resize版本要求改类型必须提供一个默认构造函数。

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

  • 向容器中添加元素和从容器中删除元素的操作可能会使指向元素的指针,引用或者迭代器失效。而一个失效的指针,引用或迭代器将不再表示任何元素
    在这里插入图片描述
  • 容器是vector或者string的情况下,在向容器添加元素后,如果容器扩容,即容器的存储空间重新分配,则指向容器的迭代器失效
  • 容器是vector或者string的情况下,在向容器添加元素后,如果容器未扩容,则指向插入位置之前的元素的迭代器有效,但指向插入位置之后元素的迭代器将会失效,这是因为插入位置后的元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。
  • 容器是deque的情况下,插入到任何位置都会导致迭代器。
  • 容器是list和forward_list的情况下,插入到任何位置,指向容器的迭代器仍然有效,
    在这里插入图片描述

编写改变容器的循环程序

在这里插入图片描述

  • insert在给定位置之插入新元素,然后返回指向新元素的迭代器,所以必须加两次,越过插入的新元素和正在处理的元素

不要保存end返回的迭代器

在这里插入图片描述
如果在一个循环中插入,删除deque或vector中的元素,不要缓存end返回的迭代器’
在这里插入图片描述
在这里插入图片描述

第一题:

  • list和forward_list与其他容器的一个不同是,迭代器不支持加减运算,因为链表中的元素并非在内存中联系存储,因此无法通过地址的加减在元素间远距离移动。因此,应多次调用++来实现与迭代器加法相同的效果。
  • list和forward_list支持++不支持+=`
    vector<int> vi = {0,1,2,3,4,5,6,7,8,9};

    auto iter = vi.begin();
    while(iter != vi.end())
    {
        if(*iter % 2)
        {
            iter = vi.insert(iter,*iter);
            iter+=2;
            cout<<1<<endl;
        }
        else
        {
            iter = vi.erase(iter); //删除之后返回我们删除的元素之后的元素
        }
    }
int main() {
    list<int> vi = {0,1,2,3,4,5,6,7,8,9};

    auto iter = vi.begin();
    while(iter != vi.end())
    {
        if(*iter % 2) //奇数
        {
            iter = vi.insert(iter,*iter);
            iter++;
            iter++;
        }
        else
        {
            iter = vi.erase(iter); //删除之后返回我们删除的元素之后的元素
        }
    }

    iter = vi.begin();
    while (iter != vi.end()){
        cout<<*iter<<ends;
        iter++;
    }
    return 0;
}
/删除奇数,复制偶数
int main() {
    
    //对于forward_list删除元素时,由于是单链表结构,需要维护前驱和后继两个迭代器
    forward_list<int> vi = {0,1,2,3,4,5,6,7,8,9};

    auto iter = vi.begin();
    auto prev = vi.before_begin();
    while(iter != vi.end())
    {
        if(*iter % 2) //奇数
        {
            iter = vi.insert_after(iter,*iter); //返回插入的元素
            prev = iter;
            iter++;
        }
        else
        {
            iter = vi.erase_after(prev); //删除之后返回我们删除的元素之后的元素
        }
    }

    iter = vi.begin();
    while (iter != vi.end()){
        cout<<*iter<<ends;
        iter++;
    }
    return 0;
}

第二题

  • 编译器处理形参的顺序可能是不同的,由右向左或者有左向右都有可能。

9.4 vector对象是如何增长的

管理容量的成员函数

在这里插入图片描述
在这里插入图片描述

capacity和size

  • capacity是在不分配内存空间的前提下它最多可以保存多少元素,size是指它已经保存的元素的数目。
  • 实际上只要没有操作需求超过vector的容量,vector就不能重新分配内存空间

9.5 额外的string操作

9.5.1 构造string的其他方法

在这里插入图片描述
在这里插入图片描述

substr操作

在这里插入图片描述

习题

在这里插入图片描述

9.5.2 改变string的其他方法

在这里插入图片描述

append和replace函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

改变string的多种重载函数

在这里插入图片描述

void test(string &s, string& oldVal, string& newVal)
{
	if (!s.size())
		return; //s为空
	string::iterator its = s.begin();
	int j = 0; //记录正在遍历的s的位置
	while (its != s.end()) {

		if (*its == oldVal[0] && j < s.size())
		{
			cout << "找到首元素" << endl;
			string::iterator oldIt = oldVal.begin();
			while (oldIt !=oldVal.end() && *its == *oldIt && j< s.size())
			{
				oldIt++;
				its++;
			}
			if (oldIt == oldVal.end())
			{ //s中有oldval并找到其位置
				cout << "找到了" << endl;
				s.erase(j,oldVal.size());
				s.insert(j, newVal);
				break;
			}
		
		}
		else
		{
			its++;
			j++;
		}
	
	}
}
int main(int argc, char** argv)
{
	string s = "newtho";
	string old = "tho";
	string news = "news";
	test(s, old, news);
	cout << s << endl;   //newsnew
	return 0;
}

9.5.3 string搜索操作

在这里插入图片描述
在这里插入图片描述

指定在哪里开始搜索

在字符串中循环的搜索字符串出现的所有位置


void test(string &s, string& findStr)
{
	string::size_type pos = 0;

	while ((pos = s.find_first_of(findStr,pos)) != string::npos)
	{
		cout << "pos = " << pos<<"Element is " << s[pos]<< endl;
		++pos;
	}
}

s.find_first_of(str)查找的是str中的任意一个字符在s首次出现的位置

逆向搜索

rfind

习题

for-each语句遍历string会乱码

string::size_type pos = 0;
	string  str = "qwertyuiopasdfghjklzxcvbnm";
	string::size_type len = str.size();
	 for(int i =0;i< len;i++)
	 {	
		 str += toupper(str[i]);
		 cout << str[i] << "  " << toupper(str[i]) << " " << str << endl;
		 
	 }
	 cout << str << endl;

9.5.4 compare函数

在这里插入图片描述

9.5.5 数值转换

在这里插入图片描述
在这里插入图片描述

9.5 容器适配器

在这里插入图片描述

定义一个适配器

在这里插入图片描述

栈适配器

在这里插入图片描述

队列适配器

在这里插入图片描述
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值