字符串、向量和数组(二)

1.标准库类型vector

标准库类型 vector表示对象的集合,里面对象的类型都相同,每个 对象都有一个对应的索引用来访问对象。它常被称作“容器”,使用vector,必须使用头文件,并且vector在命名空间std中。vector是一个模板类。

#include <vector>
using std::vector

由于vector是类模板,所以需要通过提供一些额外的信息来指定模板到底实例化成什么样的类,提供信息的方式为:在模板名字后面跟一对尖括号,在括号内放上信息。下面举几个例子:

vector<int> ivec; //ivec保存int类型的对象
vector<Sales_item> sales_vec; //sales_vec保存自定义Sales_item类的对象
vector<vector<string>> file; //该向量的元素是vector对象 

编译器根据模板vector分别生成了三种不同的类型。
vector能容纳绝大部分的对象作为其元素,甚至元素可以是vector,但需要 注意的是,因为引用不是对象,所以vector中是不能包含引用的。在C++11前后,如果vector的元素为vector,那么定义的形式略有不同:

vector<vector<int> > //C++11之前,注意有一个空格
vector<vector<int>>  //C++11

1.1定义和初始化vector对象

列表初始化vector对象

C++11新标准提供了列表初始化方法来为vector对象的元素赋初值。例如:

vector<string> ss={"aa","b","cc"};

C++提供额初始化方式在大多数情况下 都可以互相等价地使用,但是也有几种特殊情况。第一,拷贝初始化只能提供一个初始值;第二,如果提供的是一个类内初始值,那么只能使用拷贝初始化或者是花括号的形式;第三,就是如果提供的是初始元素值的列表,那么 只能把初始值 放在 花括号里面进行列表初始化,而不能使用圆括号。

vector<string> ss1={"a","bb","cc"}; //正确
vector<string> ss2{"a","bb","cc"}; //正确,等价于ss1
vector<string> ss3("a","bb","cc"); //错误 ,不能使用圆括号

创建指定数量的元素

可以使用vector容纳的元素数量和元素值的统一初始值来初始化vector对象。

vector<int> ivec(10,-1);//ivec被初始化为包含10个初始值为-1的int类型的元素
vector<string> ssvec(10,"haha"); //ssvec被初始化为包含10个初始值为"haha"的string类型的元素

值初始化

使用vector对象时候,可以只提供容纳的元素数量而不提供初始值,这个时候库会创建一个值初始化的元素初值,这个值将赋给容器内的所有元素。初值是由vector对象中的元素的类型决定。例如:

vector<string> svec(10); //vector对象含有10个元素,每个元素都是一个空字符串
vector<int> ivec(10); //vector对象含有10个元素,每个都初始化为0

这种初始化方式有两个限制:第一,如果vector对象中元素的类型不支持默认初始化,那么就必须提供初始化的元素值,对于这种类型的对象来说,只提供元素的数量而不设定初始值是无法完成初始化工作的。第二,如果只提供了元素的数量而没有设定初始值,只能使用直接初始化。

vector<int> vi=10; //错误,只能使用直接初始化的形式指定向量大小

列表初始值还是元素数量

在某些情况下,初始化的真实含义依赖于 传递初始值时是使用花括号还是圆括号。通过例子可以清晰看出区别:

vector<int> v1(10); //v1含有10个元素,每个元素的值为0
vector<int> v2{10}; //v2含有1个元素。每个元素的值为10
vector<int> v3(10,1); //v3有10个元素,每个元素都被初始化为1
vector<int> v4{10,1}; //v4有2个元素,值分别是10,1

如果用的是圆括号,提供的值是用来构造vector对象的;如果用的是花括号,表明使用列表初始化vector对象。另一方面,如果初始化提供了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了。

vector<string> v1{"hi"}; //列表初始化,v1含有一个元素
vector<string> v2("hello"); //错误,不能使用字符串字面值构建vector对象
vector<string> v3{10}; //v3有10个默认初始化的元素
vector<string> v4{10,"hi"}; //v4有10个值为"hi"的元素

1.2. 向vector对象中添加元素

对于vector对象而言,直接初始化适用于三种情况:初始值数量较少以及值已知、初始值是另一个vector对象的副本、所有元素的初始值都一样。对于其他的情况,比较 好的处理方法就是先创建一个空的vector,然后在运行时使用vector的成员函数push_back向其中添加元素,push_back负责把一个值当成vector对象的尾元素”压到”vector对象的”尾端”。下面是一个将1-100依次添加到vector对象中的例子:

vector<int> v;
for(int i=0;i<=100;i++)
{
   v.push_back(i); //一一"压到"v的"尾端"
}

如果直到运行时才知道vector对象中元素的确切个数,一般就使用上述的方法。下面是一个实时读入数据然后将其赋值给vector对象的例子:

string word;
vector<string> text;
while(cin>>word)
{
  text.push_back(word); //把word添加到text后面
}

向vector对象添加元素蕴含的编程假定

1) 必须要确保所写的循环正确无误,特别是在循环中可能改变vector对象容量的时候。
2) 如果在循环体中使用vector对象添加元素的语句,那么将不能使用范围for循环。

1.3 其他vector操作

除了上述的push_back函数,vector还提供了其他几种操作,如图:
这里写图片描述
我们通过元素在vector中的位置来访问vector对象中的元素,和string对象对象一样,vector对象可以使用范围for语句来处理vector对象中所有的元素。

vector<int> vector{1,2,3,4,5,6,7,8,9};
for(auto  &c:v)
{
    c*=c;
}
for(auto c:v)
{
    cout<<c<<" ";
}
cout<<endl;

只有当元素的值可以比较时,vector对象才能被比较。关系运算符依照字段顺序进行比较:如果两个vector对象的容量不同,但是两个对象在相同的位置上对应的元素一致,则容量小的那个对象小于容量大的那个对象;如果在相同位置对应的值不同,那么vector对象的大小由第一组相异的元素值大小关系决定。

计算vector内对象的索引

vector对象和string一样,可以通过计算vector内对象的索引,从而通过索引直接访问索引位置上的元素。下面是一个统计学生分数的例子:

vector<int> scores(11,0); //满分100,以10为一个区间,分成0-9、10-19...、100这11段 scores有11个值,初始值都为0
unsigned grade;
while(cin>>gade)
{
     ++scores[grade/10]; //grade与10取整,将对应分段的计数值加1
}

不能用下标形式添加元素

我们不能通过vector对象的下标形式来添加元素,下面是一个错误的案例:

vector<int> ivec;
for(decltype(ivec.size()) ix=0;ix!=0;ix++)
{
    ivec[ix]=ix;
}

正确的做法是使用push_back成员函数

vector<int> ivec;
for(decltype(ivec.size()) ix=0;ix!=0;ix++)
{
    ivec.push_back(ix);
}

记住,我们只能对已知的 元素进行下标操作,确保下标操作合法化的一种方式就是尽可能地使用范围for语句。

2. 迭代器介绍

除了使用下标运算符来访问string或者是vector对象的元素外,还能使用更通用的机制–迭代器来实现。迭代器提供了对对象的间接访问,就迭代器而言,它对象是容器中的元素或者是string中的字符。使用迭代器可以访问某个元素,也可以从一个元素移动到 另一个元素。迭代器有有效和无效之分,这点 和指针差不多。有效的迭代器或者指向某个元素,或者指向尾元素的下一位置;其他的情况都属于无效。

2.1 使用迭代器

获取 迭代器不使用取地址符,有迭代器的类型同时拥有返回迭代器的成员,一般这些类型都拥有名为begin和end的成员,其中begin成员 负责返回指向第一个 元素的迭代器,end成员负责返回指向容器”尾元素的下一位置”的迭代器。end成员返回的迭代器通常称为尾后迭代器,在特殊情况下,如果容器为空,begin和end返回的是同一个迭代器。

迭代器运算符

下表列出了标准容器迭代器的运算符
这里写图片描述
和指针相似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素。视图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。下面是一个例子:

string s("hello");
while(s.begin()!=s.end())
{
   auto it=s.begin();
   *it=toupper(*it); //将第一个字母改成大写
}

将迭代器的一个元素移动到另一个元素

迭代器使用递增运算符来从一个元素移动到另一个元素,从逻辑上讲,迭代器的递增是将迭代器”向前移动一个位置”。下面是将string对象全部变成大写的例子:

string s("hello world");
for(auto it=s.begin();it!=s.end()&&!isspace(*it);++it)
{
    *it=toupper(*it); //将空格之外的字符都变成大写
}

迭代器类型

我们不知道string和vector的size_type成员到底是什么类型,同样,我们也不知道迭代器的确切类型。实际上,那些拥有迭代器的标准库使用iterator和const_iterator来表示迭代器的类型。

vector<int>::iterator it; //it可以读写vector<int>的元素
string::iterator it1;  //it1可以读写string对象中的字符

vector<int>::const_iterator it2;  //it2可以读取vector<int>的元素
string::const_iterator it3;   //it3可以读取string对象中的字符

这里写图片描述

begin和end运算符

begin和end返回的具体类型由对象是否是常量决定。如果对象是常量,那么它们返回的类型为const_iterator;如果对象不是常量 ,则返回的类型是iterator。

vector<int> ivec;
const vector<int> civec;
auto it1=ivec.begin(); //it1的类型是iterator
auto it2=civec.begin(); //it2的类型是const_iterator

为了方便得到const_iterator类型的返回值,C++11提供了cbegin和cend这两个新函数,下面是使用方法:

vector<int> ivec;
const vector<int> civec;
auto it1=ivec.cbegin(); //it1的类型为const_iterator
auto it2=civec.cbegin(); //it2的类型为const_iterator

由此可以看出,无论对象是否是常量,只要使用cbegin和cend,返回的类型都是const_iterator。

结合解引用和成员访问操作

解引用迭代器可以获得迭代器所指的内容,如果该对象的类型恰好是类,就可以进一步访问它的成员。例如,对于一个由字符串组成的vector对象来说,要检查其元素是否为空,可以令it是该vector对象的迭代器,只需要检查it所指字符串是否为空就可以了。指示代码为:

(*it).empty();

注意,上述代码的括号必不可少,加上括号的意思是解引用迭代器,获取迭代器指示的string对象,随后调用string对象的empty成员方法,判断是否为空。而如果不加括号,则是先访问迭代器的empty成员方法,然而迭代器根本没有这个方法,因此将发生错误。
为了简化上述表达式,C++定义了箭头运算符(->),该运算符将解引用和访问成员这两个操作结合在一起,写法如下:

it->mem; //箭头运算符
(*it).mem; //先解引用迭代器,在访问成员

下面是一个使用迭代器遍历内部元素类型为string的向量。

vector<string> text;
for(it=text.cbegin;it!=text.cend() && !it->empty();++it)
{
     cout<<*it<<endl;
}

某些对vector对象的操作会使迭代器失效

vecotor对象可以动态地增长,但同时也具有一定的副作用。之前介绍过的一个限制就是不能在范围for循环中向vector对象添加元素,另一个限制就是任何能改变vector对象容量的操作,都将使该vector对象的迭代器失效。

2.2 迭代器运算

迭代器的递增运算符使迭代器每次移动一个元素,所以标准库容器都支持递增运算的迭代器,也能使用==和!=对任意标准库的两个有效迭代器进行比较。
string和vector的迭代器提供了更多额外的运算符,一方面可以使得迭代器的每次移动跨过多个元素,另外也支持迭代器进行关系运算,所有这些运算称为迭代器运算。下面列出的就是迭代器的运算。

迭代器的算术运算

可以使迭代器和一个整数进行相加(减),其返回值是向前(向后)移动了若干个位置的迭代器。执行这样的操作时,结果迭代器或者是指示原vector对象(string对象)内的一个元素,或者是指示vector对象(string对象)尾元素下一个位置。下面是一个简单的例子:

vector<int> vec(20); //假设 vec对象有20个元素
auto it=vec.begin()+vec.size()/2; //得到的迭代器指示对象vec内的第11个元素

对于string或者是vector的迭代器而言,除了判断是否相等,还能使用关系运算符(<、<=、>、>=)进行比较关系。参加比较的迭代器必须合法,而且是指向同一个容器的元素。只要两个迭代器指向的是同一个容器中的元素或者是尾元素的下一位置,就能将其相减,所得到的结果就是两个迭代的距离。这里的距离是指右侧的迭代器向前移动多少位置,移动到左侧的迭代器,其类型是名为difference_type的带符号整型数。string和vector对象都定义了difference_type,因为这个距离可正可负,所以difference_type是带符号的。

使用迭代器运算

下面是一个使用迭代器进行二分查找的例子:

//text是有序的的vector对象
//beg和end是搜索的范围
auto beg=text.begin(); //指向第一个元素
auto end=text.end(); //指向尾元素的下一个位置
auto mid=(beg+end)/2;
while(mid!=end && sought!=*mid)  //如果最终mid和end相等,那么说明搜索不到
{
     if(*mid<sought)
     {
         end=mid;
     }else
     {
          beg=mid+1;
     }
     mid=beg+(end-beg)/2;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值