C++ Primer读书笔记——3.3 标准库类型vector & 3.4 迭代器介绍(2019.9.2)

3 字符串、向量和数组

3.3 标准库类型vector

vector是容器,是类模板,表示对象的集合,要求所有对象的类型都相同,每个对象都有一个与之对应的索引,索引用于访问对象
使用vector需要包含的头文件和命名空间:

#include <vector>
using namespace std; //vector属于标准库

模板可以看做编译器生成类或者函数编写的一份说明。编译器根据模板创建类或者函数的过程也称为实例化。当使用模板时,需要指出编译器应把类或函数实例化为何种类型。
vector能容纳绝大多数类型的对象作为其元素,因为引用不是对象,所以不存在包含引用的vector

3.3.1 定义和初始化vector对象

初始化vector的方法:

vector<T>  v1;     //默认初始化,可以先定义一个空vector,再向其中添加元素
vector<T>  v1(v2);  //v1包含v2所有元素的副本 ,允许将一个vector对象拷贝给另一个vector对象
vector<T>  v1=v2;   //v1包含v2所有元素的副本  
vector<T>  v1(n,val);  //包含n个重复的元素,每个元素的值都是val,创建指定数量的元素并且统一元素初始值
vector<T>  v1(n);  //包含n个重复执行值初始化的对象
vector<T>  v1(a,b,c,...);
vector<T>  v1={a,b,c,...};  //列表初始化

列表初始化和默认初始化的比较:
一般来说,花括号对应列表初始化,花括号中的内容即为容器中元素的内容,要求花括号中元素类型必须与容器声明的元素类型相同。
圆括号对应直接初始化,如果只传入一个参数,参数代表元素个数,元素值采用默认初始化。
如果传入两个参数,第一个参数代表元素个数,第二个参数代表元素值。
如果花括号中元素类型与容器声明的元素类型不同,即使是花括号,也按照直接初始化的方式来初始化。
关于初始化的一些限制:
①使用拷贝初始化时,只能提供一个初始值;
②如果提供类初始值,只能使用拷贝初始化或者列表初始化;

值初始化:只提供容器中元素的个数,忽略元素的初始值,此时库会创建一个值初始化的元素初值并赋值给每一个元素。初值由容器中元素的类型决定。例如:

vector<int> s(10);  //默认10个元素全部为0
vector<string> s2(10);  //默认10个元素全部为空string

值初始化的局限:一是有些类要求明确提供初始值。二是如果只提供元素个数,不提供初始值,则必须使用直接初始化。
区分列表初始化还是元素的个数:

vector <int>(10);   //值初始化,只指定元素的个数,不指定初始值。该向量有10个元素,每个元素的值为0
vector <int>{10};  //默认初始化,该向量仅一个值为10的元素,默认初始化的等号可以省略掉
vector <int>(10,1);   //值初始化,既指定元素的个数,又指定初始值。该向量有10个元素,每个元素的值为1
vector <int>{10,1};  //默认初始化,该向量有两个元素,一个值为10,一个值为1

使用数组或者string初始化vector,不能使用vector初始化数组
使用数组初始化vector

int array[]={1,2,3,4,5};
vector <int> vec(begin(array),end(array));

使用string初始化vector

string s("asdfdgfd");
vector<int> vec(begin(s),end(s));  //取数组的全部赋值给vector
vector<int> vec2 (s+1,s+3); //也可以只取数组的一部分赋值给vector

使用数组初始化vector & 使用string初始化vector的区别:
数组使用的是普通函数begin和end,string使用的是成员函数begin和end
用string初始化vector的场景:通过命令行输出获取string,将其转换成vector进行操作。

3.3.2 向vector对象添加元素

实时读入数据然后将其赋值给vector对象

string word;
vector <string> text;
while(cin>>word)
   text.push_back(word);

vector对象能高效增长
vector和string对象的增长方式:vector支持快速随机访问,因此其必须以连续空间存储元素。如果没有空间容纳新元素,为了保证内存空间连续,不可能简单将其添加到内存的其他位置,容器必须分配新的内存空间保存已有元素和新元素,将已有元素移到新空间,释放旧空间。
如果每添加一个新元素,vector就执行一次这样的内存分配和释放操作,性能会慢到不能接受。
标准库实现者采用可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可以保存更多新的元素,避免每次添加新元素都重新分配容器的内存空间。会分配多少超过给定容量的额外空间,取决于具体实现,有些是当前容量的翻倍,有些是1.5倍。使用此策略后,vector的扩张操作比list和deque还要快。
vector管理容量的成员函数
管理容量的成员函数允许我们与它的实现中内存分配部分互动。
管理容量的成员函数有
①capacity():不重新分配内存空间的话,容器可以保存的元素的个数
②reserve(n):分配至少能容纳n个元素的空间。如果容器的本来容量大于n,则不用分配新的空间,容器的capacity()值不变。如果容器本来容量小于n,则分配新的空间使得能够容纳元素的个数为n,容器的capacity()值为n。capacity()的值大于等于传给reserve()的参数,调用reserve()永远不会减少容器占用的内存空间。
调用reserve(n)可以指定capacity()的值,不会改变size()的值。
③shrink_to_fit():将capacity()的值减少到和size()相同大小
reserve()和resize()都不会减少容器的内存空间,即容器的capacity()值,使用shrink_to_fit()可以退回不需要的内存空间。

注:只有vector和string可以使用capacity()和reserve(n),因为vector和string独特的内存增长方式。
只有vector,string和deque可以使用shrink_to_fit().

补充:resize()是改变容器大小的成员函数。
resize()可以传入两个参数,第一个参数代表元素个数,第二个参数代表参数值。第二个参数可以省略。
第一个参数如果大于容器原本元素个数,会在容器尾部添加新元素;第一个参数如果小于容器原本元素个数,会删除容器后部的元素。
即 c.resize(n):调整容器c中元素个数为n。
c.resize(n,t):调整容器c中元素个数为n,任何新添加的元素值为t。
resize()只设定容器的size()值,不会改变容器的capacity()值。

如果循环体包含向vector对象添加元素的语句,则不能使用范围for语句,因为范围for语句体内不应改变其所遍历序列的大小

3.3.3 其他vector操作
v.empty();  //同string
v.size(); //同string
v.push_back(t);  
//容量相关
v.reserve(n);   //如果n大于capacity,修改capacity为n;否则不变
v.resize();   //修改size
v.shrink_to_shift(); //修改capacity与size相同大小
//清除元素
v.clear(); //删除所有元素,只清除数组,不释放内存空间,即size变为0,capacity不变
v.erase(v.begin(),v.end());  //erase可以只删除一个元素,也可以删除一个区间的元素,注意删除区间时左边边界元素不会删除
v[n];  //同string,下标应该在有效范围内取值,下标的类型应是vector<T>::size_type(无符号),不能对空vector进行该操作
v1=v2; //同string,用v2中的元素拷贝替换v1中的元素
v1={a,b,c,d...} //同string,列表初始化
v1==v2;  v1!=v2;
< <= > >=

获取vector对应的size_type类型,需要指明vector中元素的类型:

vector::size_type  //错误
vector<int>::size_type  //正确

vector及string的下标运算符可以用来访问已存在的元素,不能用于添加元素。
只能对确知已存在的元素执行下标操作,不能对空vector或者string执行下标操作。确保下标合法的一种有效手段就是使用范围for语句。
笔试题:
在这里插入图片描述
解析:vector.reserve(n)可能会改变容器的capacity。如果n大于capacity,将capacity的值设为n;如果n小于capacity,保持n的值不变。
分两种情况讨论:
1 如果n小于10,则输出为空,2-n中的确不存在同时被3和5整除的数
2 如果n大于 10,输出的结果是2-n中能同时被3和5整除的数
拓展:
1 区分:vector.reserve(n)和reverse(vector.begin(),vector.end())
vector.reserve(n)会改变容器的capacity大小
reverse(vector.begin(),vector.end())是将容器反转
2 区分:vector.reserve( ) & vector.resize( ) & vector.shrink_to_shift( )
vector.resize( )只改变容器的size,不改变容器的capacity
vector.reserve( )只改变容器的capacity,不改变容器的size
vector.shrink_to_shift( )改变容器的capacity与容器的size一样大

3.4 迭代器介绍

访问string或者vector中部分元素的方法:一是下标,二是迭代器。
所有标准库的容器都可以使用迭代器,但只有少数几种才支持下标运算符(仅有存储连续的容器才支持下标)。
严格来说,string不属于容器,但是string支持很多与容器类型类似的操作,例如迭代器。
迭代器类似指针,提供了对对象的间接访问。使用迭代器可以访问某个元素,也可以从一个元素移动到另一个元素。迭代器有有效和无效之分,有效迭代器指向某个元素或者尾后元素,其他情况都属于无效。

3.4.1 使用迭代器

获取指针是通过取地址符,获取迭代器是通过begin和end成员。指针通过解引用来获取其指向元素,迭代器也通过解引用来获取其指向元素。执行解引用的指针或者迭代器都必须指向一个有效元素。
如果容器为空,begin和end都指向尾后迭代器。判定容器是否为空,可以通过比较begin和end的返回值是否一致

string s("string");
if(s.begin()!=s.end())//确保s非空
  {
  ...
  }

一般不清楚也不在意迭代器的类型是什么,借助auto。
迭代器运算符:

*iter  //返回迭代器所指元素的引用
iter->mem //等价于(*iter).mem
++iter; //指向下一个元素
--iter; //指向上一个元素
iter1==iter2 或者 iter1!=iter2

迭代器的递增:整数递增是在数值上加1,指针和迭代器递增是指向向前移动的一个元素
不能对end执行解引用或者递增操作
泛型编程:for循环中使用!=而不是<进行判断,因为所有标准库容器的迭代器都定义了==和!=,而大多数都没有定义<运算符。
迭代器类型:无需知道迭代器的精确类型,拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。

vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string的元素
vector<int>::const_iterator it3; //it3只读vector<int>的元素
string::const_iterator it4; //it4只读写string的元素

如果vector或者string对象是常量,只能使用const_iterator
如果对象只需读操作,无需写操作的话,最好使用常量类型const_iterator
如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator
c++ 11引入cbegin和cend,不论对象是否是常量,其返回值是const_iterator
解引用和成员访问操作符的优先级:
解引用迭代器可以获得迭代器所指的对象,如果该对象的类型恰好是类,有可能希望进一步访问它的成员。成员访问操作符的优先级大于解引用的优先级,所以需要写成如下形式(*it).empty(),圆括号不可少。也可以写成it->empty(),箭头运算符=先解引用再进行成员访问。
不能在范围for语句中向vector对象添加元素
任何一种改变vector对象容量的操作,都会使该vector对象的迭代器失效。
凡是使用了迭代器的循环体,都不要向迭代器所属容器添加元素。

3.4.2 迭代器运算

所有标准库容器都有支持递增运算的迭代器,也能用==和!=对任意标准库类型的两个有效迭代器进行比较。
string和vector的迭代器提供了更多额外的运算符,一方面使得迭代器可以移动跨过多个元素,另外也支持迭代器进行关系运算。
iter1-iter2 两个迭代器相减的结果是它们之间的距离,计算结果是difference_type的带符号整形数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值