热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

1.标准库类型vector

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

#include 
using std::vector

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

vector<int> ivec; //ivec保存int类型的对象
vector 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<" ";
}
cout<

只有当元素的值可以比较时,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的元素
string::iterator it1; //it1可以读写string对象中的字符

vector<int>::const_iterator it2; //it2可以读取vector的元素
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<}

某些对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 {
end=mid;

}else
{
beg=mid+1;
}
mid=beg+(end-beg)/2;
}

推荐阅读
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了Linux Shell中括号和整数扩展的使用方法,包括命令组、命令替换、初始化数组以及算术表达式和逻辑判断的相关内容。括号中的命令将会在新开的子shell中顺序执行,括号中的变量不能被脚本余下的部分使用。命令替换可以用于将命令的标准输出作为另一个命令的输入。括号中的运算符和表达式符合C语言运算规则,可以用在整数扩展中进行算术计算和逻辑判断。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
author-avatar
alilx
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有