C++程序设计:原理与实践(进阶篇)15.7 vector、list和string

15.7 vector、list和string


为什么我们对行用list而对字符用vector呢?更准确地说,我们为什么要用list保存行的序列而用vector保存字符序列呢?再有,为什么不用string来存储一行呢?

我们可以把这些问题再一般化一些。到现在为止,我们知道了四种存储字符序列的方法:

char[](字符数组);

vector<char>;

string;

list<char>。

那么对于一个给定问题应该采取哪种存储方式呢?当问题非常简单时,选择哪种方式都无所谓,因为它们都有非常相似的接口。例如,给定一个iterator,我们可以使用++遍历所有字符,用*访问字符。在与Document相关的代码示例中,我们的确可以将vector<char>换成list<char>或string,而不会引起任何逻辑上的问题。这是非常好的特性,因为这令我们只需从性能角度选择存储方式。但是,在考虑性能之前,我们先来看看这些存储方式的逻辑特性:有什么是它能做而其他方式所不能的?

Elem[]:不知道它自己的大小。没有begin()、end()或任何其他有用的容器成员函数。不能系统地实现边界检查。可以作为参数传递给用C或C++编写的函数。其中的元素在内存中连续存储。数组的大小在编译时就确定了。比较(==和!=)和输出(<<)操作使用的是指向数组第一个元素的指针,而不是元素。

vector[Elem]:基本上可以做所有事,包括insert()和erase()。支持下标操作。在其上的列表操作,例如insert()和erase(),通常需要移动字符(当元素很大或元素数目很多时效率会比较低)。可实现边界检查。元素在内存中连续存储。vector可以扩展(例如使用push_back())。向量的元素(连续)存储在数组中。比较运算符(==、!=、<、<=、>、>=)对元素进行比较。

string:提供了所有常见的有用操作,还提供了特殊的文本处理操作,例如字符串的连接(+和+=)。其元素保证在内存中连续存储。string可以扩展。比较运算符(==、!=、<、<=、>、>=)对元素进行比较。

list[Elem]:提供了除下标外所有常见的有用操作。我们在进行insert()或erase()操作时不必移动其他元素。每个元素需要两个额外的字(用来存储链接指针)。list可以扩展。比较运算符(==、!=、<、<=、>、>=)对元素进行比较。

正如我们之前提到的(见12.2节和13.6节),当我们需要在底层和内存打交道或需要和C程序交互时数组是非常有用且必需的(见27.1.2节和27.5节)。在其他情况下,vector由于更方便、灵活且安全,应是首选。

试一试

上述的区别在实际的代码中意味着什么?分别定义一个保存值"Hello"的char数组、vector<char>、list<char>和string,并把它们作为参数传递给一个函数。该函数首先输出传来的字符串中的字符数目,并将其与函数内定义的"Hello"相比较(以判断你是否真的传递了"Hello"),然后再与"Howdy"比较,看看它们在字典中谁更靠前。把参数复制到另一个相同类型的变量中。

试一试

重复上面的“试一试”,这次测试int数组、vector<int>和list<int>,都保存数值{1,2,3,4,5}。

15.7.1 insert和erase

标准库vector是我们使用容器时的首选。它几乎具有所有所需特性,所以我们只在没有办法时才会使用其他的替代品。vector主要的问题在于每当我们执行列表操作(insert()和erase())时,都需要对元素进行移动。当vector中的元素很多或元素很大时,移动元素会产生很高的代价。但也不必太担心这一点。我们可以放心地用push_back()读取50万个浮点数存入一个vector中,实验证明相对于预分配所有内存的方法,push_back()并无明显的性能劣势。在为了性能而做出重大改变前一定要进行性能测试,即使是专家也很难猜测性能。

正如在15.6节中所提到的,移动元素的特性还意味着一个逻辑限制:当你对一个vector执行列表操作(如insert()、erase()和push_back())时一定不要保留指向其元素的迭代器或指针:若元素移动,你的迭代器或指针将会指向错误的元素,甚至根本不指向任何元素。这也正是list相对于vector(以及map,参见16.6节)的根本优势。如果你在程序中需要使用很多大对象,而且会在很多地方(用迭代器或指针)指向它们,则应考虑使用list。

我们来比较一下list和vector的insert()和erase()操作。首先看一个仅用来展示关键点的例子:

 

现在q是无效的。随着向量的大小增长,可能会为其元素分配新的内存。如果v有空闲空间,则它会原地增长,q很可能指向的是值为3的元素而不是值为4的元素,但千万不要认为一定会是这样。

 

 

也就是说,如果在insert()操作后执行一次erase()操作删除刚刚插入的元素,那么我们就回到了起始状态,只是q变成无效了。但是,在这两次操作之间,我们移动了插入点之后的所有元素,随着v增长所有元素可能都被重新分配空间了。

作为对比,我们使用list来完成相同的操作:

 

注意,q仍然指向值为4的元素。

 

 我们又一次发现回到了起始状态。但是,与vector的不同之处在于,我们没有移动任何元素,q始终是有效的。

list<char>与其他三种容器相比需要至少3倍的存储空间——在PC上,一个list<char>需要12个字节来保存每个元素,而vector<char>只需要1个字节。当字符数很多时,这种差距可能很重要。

那vector哪方面优于string呢?从它们的特性中可以发现,string似乎能完成比vector更多的功能。但这也正是部分问题的所在:由于string必须完成更多功能,对它进行优化也就更困难了。实际上,vector设计思想之一就是对push_back()这样的“内存操作”进行优化,而string并没有。取而代之,string对拷贝操作、短字符串处理以及与C风格字符串的交互进行了优化。在文本编辑器的例子中,我们选择vector是因为需要使用insert()和delete(),另一方面也是出于性能的考虑。vector和string逻辑的主要差异在于vector几乎可以用于任何元素类型,而只有在处理字符时我们才需要考虑string。总之,只有当需要进行字符串操作(例如字符串连接或读取空白符间隔的单词)时才考虑使用string,其他情况下,就用vector好了。

时间: 2024-08-07 03:38:21

C++程序设计:原理与实践(进阶篇)15.7 vector、list和string的相关文章

c++-关于《C++程序设计原理与实践》第3章例子的一个问题

问题描述 关于<C++程序设计原理与实践>第3章例子的一个问题 本人菜鸟,现正在学习C++.<C++程序设计原理与实践>第3章有一个例子,代码如下: #include #include #include #include #include using namespace std; inline void keep_window_open(){ char ch; cin >> ch; } int main() //C++ Programs start by executi

源代码-C++程序设计原理与实践

问题描述 C++程序设计原理与实践 #include "std_lib_facilities.h" int main() { cout<<"Hello,world!n"; return 0; } 我下了源代码,放到那里才能猜VC98编译时不出错?最好详细点,带有图解 解决方案 ...大哥,都什么年代了还用98

《 C++程序设计:原理与实践(进阶篇.》导读

本节书摘来自华章出版社< C++程序设计:原理与实践(进阶篇)>一书中作者[美] 本贾尼·斯特劳斯特鲁普(Bjarne Stroustrup) 著 刘晓光 李忠伟 王刚 译     前 言 Programming: Principles and Practice Using C++, Second Edition 该死的鱼雷!全速前进. --Admiral Farragut 程序设计是这样一门艺术,它将问题求解方案描述成计算机可以执行的形式.程序设计中很多工作都花费在寻找求解方案以及对其求精上

100分求java语言程序设计进阶篇pdf

问题描述 求java语言程序设计进阶篇pdf 解决方案 解决方案二:同求啊!!!解决方案三:这个网上是没有的,我也在网上找过,我建议你去网上找java核心技术<上下卷>pdf这本书写的也是不错的,,这个网上有电子书的,,这两本书配合着java编程思想,相当的不错的解决方案四:真正的进阶是需要项目练习的,纸上得来终觉浅解决方案五:引用2楼xinzailiulei的回复: 这个网上是没有的,我也在网上找过,我建议你去网上找java核心技术<上下卷>pdf这本书写的也是不错的,,这个网上

学一点 mysql 双机异地热备份----快速理解mysql主从,主主备份原理及实践

原文 学一点 mysql 双机异地热备份----快速理解mysql主从,主主备份原理及实践 感谢大家在上一篇 学一点Git--20分钟git快速上手 里的踊跃发言.这里再次分享干货, 简单介绍mysql双机,多机异地热备简单原理实战. 双机热备的概念简单说一下,就是要保持两个数据库的状态自动同步.对任何一个数据库的操作都自动应用到另外一个数据库,始终保持两个数据库数据一 致. 这样做的好处多. 1. 可以做灾备,其中一个坏了可以切换到另一个. 2. 可以做负载均衡,可以将请求分摊到其中任何一台上

推荐系统——从原理到实践,还有福利赠送!

之前流水账似的介绍过一篇机器学习入门的文章,大致介绍了如何学习以及机器学习的入门方法并提供了一些博主自己整理的比较有用的资源.这篇就尽量以白话解释并介绍机器学习在推荐系统中的实践以及遇到的问题... 也许很多点在行家的眼里都是小菜一碟,但是对于刚刚接触机器学习来说,还有很多未知等待挑战. 所以读者可以把本篇当做是机器学习的玩具即可,如果文中有任何问题,还请不吝指教. 本篇将会以下面的步骤描述机器学习是如何在实践中应用的: 1 什么是推荐系统? 2 机器学习的作用 3 机器学习是如何使用的? 4

Node.js Stream - 进阶篇

上篇(基础篇)主要介绍了Stream的基本概念和用法,本篇将深入剖析背后工作原理,重点是如何实现流式数据处理和 back pressure 机制. 目录 本篇介绍 stream 是如何实现流式数据处理的. 数据生产和消耗的媒介 为什么使用流取数据 下面是一个读取文件内容的例子: const fs = require('fs') fs.readFile(file, function (err, body) { console.log(body) console.log(body.toString(

SQL Server调优系列进阶篇(如何索引调优)

原文:SQL Server调优系列进阶篇(如何索引调优) 前言 上一篇我们分析了数据库中的统计信息的作用,我们已经了解了数据库如何通过统计信息来掌控数据库中各个表的内容分布.不清楚的童鞋可以点击参考. 作为调优系列的文章,数据库的索引肯定是不能少的了,所以本篇我们就开始分析这块内容,关于索引的基础知识就不打算深入分析了,网上一搜一片片的,本篇更侧重的是一些实战项内容展示,希望通过本篇文章各位看官能在真正的场景中找到合适的解决方法足以. 对于索引的使用,我希望的是遇到问题找到合适的解决方法就可以,

WPF 4 DataGrid 控件(进阶篇二)

上一篇<WPF 4 DataGrid 控件(进阶篇一)>中我们通过DataGridTemplateColumn 类自定义编辑了日期列的样式,当然也可以根据个 人需要设置任何样式模板.上例中Pass Exam 列显示学生是否通过考试,但我们并不知道该学生每门学科的成绩是多少.本篇将为 DataGrid 行增加这些详细信息,使得DataGrid 数据更加充实. 首先,我们仍然先更新一下Member 类,增加Math 和History 两门学科: public class Member { publ