字符串(string),向量(vector)和数组
string和vector是两种最重要的标准库类型,string支持可变长字符串,vector表示可变长集合数组,与之相配套的标准库类型中的迭代器,属于string和vector的配套类型,经常用于访问string中的字符或者vector中的元素
命名空间的using声明
在C++的初步学习之中,我们往往会敲这样的一段代码,
using namespace std;
一开始的学习之中,我们也不知道其中的意思,只需要明白照着敲就行
简单来说,到目前为止,我们目前所能用到的库函数基本上都属于命名空间std,即C++标准程序库,程序也显式地将这一点表示出来了。
例如:
如果不想敲这段代码using namespace std;
而使用cin函数(cin表示从标准输入中读取内容),此处使用作用域操作符(::)的含义:编译器应从操作符左侧名字所示的作用域中寻找那个名字。
因此,std::cin的意思就是要使用命名空间std中的名字cin。
有了using声明就无须专门的前缀(形如命名空间::)也可以使用所需要的名字了。
using声明具有如下的形式:
using namespace::name;
一旦声明了上述的语句,就可以直接访问命名空间中的名字;
1.不使用using声明的代码:
#include<iostream>
int main()
{
int ans;
std::cin>>ans;
std::cout<<ans<<std::endl;
return 0;
}
2.使用using声明的代码:
#include<iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
int ans;
cin>>ans;
cout<<ans<<endl;
return 0;
}
注:按照规定,每个using声明引入命名空间中的一个成员
头文件不应该包含using声明
位于头文件的代码一般来说不应该使用using声明。这个是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就都会有这个声明。则对于某些程序来说,由于不经意间包含了一些名字,反而会产生一些始料未及的名字冲突
**注意事项:**using声明引入的名字所遵循的作用域规则:它的有效范围从using声明的地方开始,一直到using声明所在的作用域结束为止
using指示
using指示(using directive)和using声明类似的地方是,我们可以使用命名空间名字的简写形式;但是,和using声明不同的地方是,我们没有办法控制哪些名字是可见的,因为所有名字都是可见的。
using指示以关键字using开始,后面是关键字namespace以及命名空间的名字(三大部分)
例如:
using namespace std;
using指示使得某个特定的命名空间中所有的名字都可见,这样我们就无须再为他们添加任何前缀限定符了。
简写的名字从using指示开始,一直到using指示所在的作用域结束都能使用
3.使用using指示的代码:
#include<iostream>
using namespace std;
int main()
{
int ans;
cin>>ans;
cout<<ans<<endl;
return 0;
}
标准库类型string
标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件
**头文件:#include< string > **
注: C++标准一方面对库类型所提供的操作做了详细规定,另一方面对库的实现者做出一些性能上的需求。因此,标准库类型对于一般应用场合来所有足够的效率。
定义和初始化string对象
如何初始化string类的对象是由string类本身决定的。
string类提供很多种初始化string类的对象的方式,但是这些方式之间必须有所区别:比如初始值的数量不同,或者是初始值的类型不同
初始化string对象最常用的方式:
string s1; // 默认初始化,s1是一个空字符串
string s2=s1; // s2是s1的副本
string s3="CPP"; // s3是该字符的字面值的副本
string s4(5,'C'); // s4的内容是CCCCC
代码如下:
#include<iostream>
#include<string>
using namespace std; // 使用using指示
int main()
{
string s1; // 默认初始化,s1是一个空字符串
string s2=s1; // s2是s1的副本
string s3="CPP"; // s3是该字符的字面值的副本
string s4(5,'C'); // s4的内容是CCCCC
// ends - 插入空字符,然后刷新ostream缓冲区
// endl - 插入换行,然后刷新ostream缓冲区
cout<<s1<<ends<<s2<<ends<<s3<<ends<<s4<<endl;
return 0;
}
初始化string对象的方式
初始化方式 | 解释 |
---|---|
string s1 | 默认初始化,s1是一个空串 |
string s2 | s2是s1的副本 |
string s3(“NewThread-CPP”) | s3是字面值"value"的副本,除了字面值最后的那个空字符外 |
string s3 = “NewThread-CPP” | 等价于s3(“NewThread-CPP”),s3是字面值"NewThread-CPP"的副本 |
string s4(n,‘C’) | 把s4初始化为由连续n个字符C组成的串 |
直接初始化和拷贝初始化
- 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去
- 如果不使用等号,则执行的是直接初始化(direct initialization)
string s1 = "NewThread-CPP"; //拷贝初始化
string s2("NewThread-CPP"); //直接初始化
string s3(10,'C'); //直接初始化
客观来说,直接初始化的效率高于拷贝初始化
string对象上的操作
string的操作:
操作 | 解释 |
---|---|
os<<s | 将s写到输出流os中,返回os |
is>>s | 从is中读取字符串赋给s,字符串以空白分隔,返回is |
getline(is, s) | 从is中读取一行赋给s,返回is |
s.empty() | 若s为空字符串返回true,否则返回false |
s.size() | 返回s中字符的个数 |
s[n] | 返回s中第n个字符的引用,位置n从0开始计起 |
s1+s2 | 返回s1和s2连接后的结果 |
s1=s2 | 用s2的副本代替s1中原来的字符 |
s1==s2 | 如果s1和s2中所含字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感 |
s1!=s2 | |
<,<=,>,>= | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感 |
读取未知数量的string对象
如下代码可以读入数量未知的字符:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string word;
while(cin>>word) // 反复读取,直至到达文件末尾
cout<<word<<endl; // 逐个输出单词,每个单词后面紧跟一个换行
return 0;
// 输入Crtl+Z可结束循环退出程序
}
注:该循环条件负责在读取时监测流的情况,如果输入流有效,也就是说没有遇到文件结束标记或是非法输入,那么执行while语句内部操作。
使用getline读取一整行
如果我们希望最终得到的字符串中可以保留输入时的空白符,这时候应该用getline函数代替原来的>>运算符。
getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读取内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。
getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得的结果是个空string
#include<iostream>
#include<string>
using namespace std;
int main()
{
string line;
// 每次读入一行,直至到达文件末尾
while(getline(cin,line))
cout<<line<<endl;
return 0;
}
**注:**触发getline函数返回的那个换行符实际上被丢弃掉了,得到的string对象中并不包含该换行符
string的empty和size操作
empty函数根据string对象是否为空返回一个对应的布尔值,改写原先的程序:
// 每次读入一整行,遇到空行直接跳过
while(getline(cin,line))
if(!line.empty())
cout<<line<<endl;
size函数返回string对象的长度(即string对象中字符的个数),可以使用size函数只输出长度超过n个字符的行:
string line;
// 每次读入一整行,输出其中超过n个字符的行
while(getline(cin,line))
if(line.size()>n)
cout<<line<<endl;
string对象的比较(< , <= , > , >= , == , !=)
关系运算符依照(大小写敏感的)字典顺序规则:
- 如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,则就说较短string对象小于较长string对象
- 如果两个string对象在某些对应的位置上不一致,则string对象比较的结果其实string对象中第一对相异字符比较的结果
两个string对象相加
两个string对象相加得到一个新的string对象,其内容是把左侧的运算对象与右侧的运算对象串接而成。
对string对象使用加法运算符(+)的结果是一个新的string对象,它所包含的字符由两个部分组成:前半部分是加号左侧string对象所含的字符,后半部分是加号右侧string对象所含的字符
string s1="NewThread ", s2="CPP\n";
string s3=s1+s2;
复合赋值运算符(+=)负责把右侧string对象的内容追加到左侧string对象的后面
s1 += s2; // 等价于s1 = s1+s2
字面值和string对象相加
标准库允许把字符字面值和字符串字面值转换成string对象,所有在需要string对象的地方就可以使用这两种字面值来替代。
代码案例:
string s1 = "NewThread",s2 = "CPP";
string s3 = s1 + "," + s2 + "\n";
string s4 = s1 + "!"; // 正确:把一个string对象和一个字面值相加
string s5 = "NewThread" + "!"; // 错误,两个运算对象都不是string
// 正确,每个加法运算符都有一个运算对象是string
string s6 = s1 + "CPP";
string s7 = "NewThread" + "," + s2; // 错误:不能把字面值直接相加
**注:**因为某些历史原因,也是为了与C兼容,故C++语言中的字符串字面值并不是标准库类型string的对象。
切记,字符串字面值与string是不同的类型
练习:
编写一段程序读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。
解答:
比较字符串大小的程序如下所示:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1,s2;
cout<<"请输入两个字符串:"<<endl;
cin>>s1>>s2;
if(s1==s2)
cout<<"两个字符串相等"<<endl;
else if(s1>s2)
cout<<s1<<"大于"<<s2<<endl;
else
cout<<s2<<"大于"<<s1<<endl;
return 0;
}
比较字符串长度的程序如下所示:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1,s2;
cout<<"请输入两个字符串:"<<endl;
cin>>s1>>s2;
auto len1=s1.size();
auto len2=s2.size();
if(len1==len2)
cout<<s1<<"和"<<s2<<"的长度都是"<<len1<<endl;
else if(len1>len2)
cout<<s1<<"比"<<s2<<"的长度多"<<len1-len2<<endl;
else
cout<<s2<<"比"<<s1<<"的长度多"<<len2-len1<<endl;
return 0;
}
处理string对象中的字符
在头文件cctype中定义了一组标准库函数处理string对象中的字符的这部分工作
cctype头文件中的函数:
函数 | 解释 |
---|---|
isalnum© | 当c是字母或数字时为真 |
isalpha© | 当c是字母时为真 |
iscntrl© | 当c是控制字符时为真 |
isdigit© | 当c是数字时为真 |
isgraph© | 当c不是空格但可打印时为真 |
isxdigit© | 当c是十六进制字母时为真 |
isprint© | 当c是可打印字符时为真(即c是空格或c具有可视形式) |
ispunct© | 当c是标点符号时为真(即c不是控制字符,数字,字母,可打印空白中的一种) |
isspace© | 当c是空白时为真(即c是空格,横向制表符,纵向制表符,回车符,换行符,进纸符中的一种) |
islower© | 当c是小写字母时为真 |
isupper© | 当c是大写字母时为真 |
tolower© | 如果c是大写字母,输出对应的小写字母;否则原样输出c |
toupper© | 如果c是小写字母,输出对应的大写字母;否则原样输出c |
案例:
1.大小写的转换:
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string ans;
cout<<"请输入您要输入的单词:";
cin>>ans;
for(auto &s : ans)
if(islower(s))
s = toupper(s);
cout<<"该单词的大写形式:"<<ans<<endl;
for(auto &s : ans)
if(isupper(s))
s = tolower(s);
cout<<"该单词的小写形式:"<<ans<<endl;
return 0;
}
2.读取字符串中的数字:
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string str,ans;
cout<<"输入含有数字的字符串:";
cin>>str;
for(auto s : str)
if(isdigit(s))
ans += s;
cout<<"字符串中包含的数字:"<<ans<<endl;
return 0;
}
小知识:使用C++版本的C标准库头文件
C++标准库中除了C++语言特有的功能外,也兼容了C语言的标准库。在C语言中的头文件形如name.h,C++则将这些头文件命名为cname。即去掉了.h的后缀,而在文件名name之前添加了字母c,而这里的c表示这是一个属于C语言标准的头文件。
一般来说,C++程序应该使用名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。
如何更好的处理每个字符?C++11新特性,使用基于范围的for语句;
如果希望能够更好的处理string对象中的每个字符,当下最好的办法就是使用C++11新标准提供的一种语句:范围for(range for)语句
这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式:
for(declaration : expression)
statement
其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。
每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值
使用案例:
1.使用C++11新标准范围for语句
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str;
cin>>str;
for(auto s : str)
cout<<s<<ends;
return 0;
}
2.使用常规循环for语句
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str;
cin>>str;
for(int i=0;i<str.size();i++)
cout<<str[i]<<ends;
return 0;
}
如何使用范围for语句改变字符串中的字符
如果想要改变string对象中字符的值,则必须把循环变量定义成引用类型,其语法形式如下:
for(auto& s : expression)
statement
**注:**所谓引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就可以改变它绑定的值。
案例:
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string ans;
cout<<"请输入您要输入的单词:";
cin>>ans;
for(auto &s : ans)
if(islower(s))
s = toupper(s);
cout<<"该单词的大写形式:"<<ans<<endl;
return 0;
}
如何只处理一部分的字符?
想要访问string对象中的单个字符有两种方式:一种是使用下标,另外一种是使用迭代器
**下标运算符( [] )**接收的输入参数是string::size_type类型的值(即unsigned无符号类型),这个参数表示要访问的字符的位置;返回值是该位置上字符的引用
注:注意检查下标的合法性
string对象的下标必须大于等于0而小于s.size()
超出此范围的下标将引发不可预估的结果,以此推断,使用下标访问空string也会引发不可预知的结果
使用下标执行随机访问案例:
1.把0到15之间的十进制数转换成对应的十六进制形式:
#include<iostream>
#include<string>
using namespace std;
const string hexdigits = "0123456789ABCDEF"; // 十六进制数字
int main()
{
unsigned n;
string ans;
cout<<"输入0~15之间的数字转换为对应的十六进制形式:";
while(cin>>n)
if(n<hexdigits.size()) // 忽略无效输出
ans += hexdigits[n]; // 得到对应的十六进制数字
cout<<"有效数字对应的十六进制数字:";
for(auto s : ans)
cout<<s<<ends;
cout<<endl;
return 0;
}
2.使用范围for语句将字符串内的所有字符用X代替:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s;
cout<<"请输入一个字符串,可以包含空格:"<<endl;
getline(cin,s);
for(auto &c : s)
c = 'X';
cout<<s<<endl;
return 0;
}
3.读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余部分:
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string ans;
cout<<"请输入含有标点符号的字符串:";
getline(cin,ans);
for(auto s : ans) {
if(ispunct(s))
cout<<ends;
if(!ispunct(s))
cout<<s;
}
return 0;
}
标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。
头文件:#include< vector >
集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector“容纳着”其他对象,所以它也常被称作容器(container)
C++语言既有类模板(class template),也有函数模板,而vector是一个类模板
如果想要自定义模板需要对C++有更加深入的了解,而我们可以简单地使用标准库为我们准备好的类模板
模板本身不是类或函数,相反,可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化(instantiation)
当使用模板时,需要指出编译器应该把类或函数实例化成何种类型,即在模板名字后面跟一对尖括号,在括号内放上信息
我们以vector为例,实例化vector:
vector<int> ivec; // ivec保存int类型的对象
vector<Student> stu_vec; // 保存Student类型的对象
vector<vector<int>> iivec; // 该向量元素是vector对象,类似于二维数组
**注:**vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,例如vector< int >
vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector
**注意事项:**某些编译器可能仍需以老式的声明语句来处理元素为vector的vector对象,如vector<vector<int> >
本次学习主要是为了让各位习惯使用vector< int >代替数组的操作,vector< T >的相关深入学习将由各位自己进行了解
定义和初始化vector对象
和任何一种类类型一样,vector模板控制着定义和初始化向量的方法
初始化vector对象的方法:
初始化 | 相关解释 |
---|---|
vector< T > v1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vector< T > v2(v1) | v2中包含有v1所有元素的副本 |
vector< T > v2=v1 | 等价于v2(v1),v2中含有v1所有元素的副本 |
vector< T > v3(n, val) | v3包含了n个重复的元素,每个元素的值都是val |
vector< T > v4(n) | v4包含了n个重复地执行了值初始化的对象 |
vector< T > v5{a,b,c…} | v5包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector< T > v5={a,b,c…} | 等价于v5{a,b,c…} |
可以默认初始化vector对象,从而创建一个指定类型的空vector:
vector<string> svec; // 默认初始化,svec不含任何元素
**注:**事实上,最常见的方式就是先定义一个空vector,然后当运行时获取到元素的值后再逐一添加(例如,使用成员函数push_back())
列表初始化vector对象
C++11新标准还提供了另外一种为vector对象的元素赋初值的方法,即列表初始化
vector<string> svec = {"a","an","the"};
上述vector对象包含三个元素:第一个是字符串"a",第二个是字符串"an",最后一个是字符串"the"
vector初始化方式:
- 使用拷贝初始化(即使用=时),只能提供一个初始值
- 如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化
- 如果提供的是初识元素值的列表,则只能把初始值都放在花括号里面进行列表初始化,而不能放在圆括号里面
vector<string> v1{"a","an","the"}; // 正确的列表初始化
vector<string> v2("a","an","the"); // 错误形式
创建指定数量的元素
可以使用vector对象容纳的元素数量和所有元素的统一初始值来初始化vector对象
vector<int> ivec(10,1); // 10个int类型的元素,每个都初始化为1
vector<string> svec(5,"CPP"); // 10个string类型的元素,每个都初始化为"CPP"
值初始化
通常情况下,可以只提供vector对象容纳的元素数量而略去初始值。此时库会创建一个值初始化的(value-initialized)元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中元素的类型决定
vector<int> ivec(10); // 正确,10个元素,每个都初始化为0
vector<string> svec(10); // 正确,10个元素,每个都是空string对象
vector<int> vi = 10; // 错误,必须使用直接初始化的形式指定向量大写
向vector对象中添加元素(即,成员函数:push_back())
提问?如何在不确定输入数据长度的情况保证容器空间不会溢出
对vector对象来说,直接初始化的方式适用于三种情况:
- 初始值已知且数量较少
- 初始值是另一个vector对象的副本
- 所有元素的初始值都一样
案例:输入0~9共10个元素,分别用数组和vector对象实现
1.数组实现:
#include<iostream>
using namespace std;
int num[15];
int main()
{
for(int i=0;i<10;i++) {
num[i]=i;
cout<<num[i]<<ends;
}
cout<<endl;
return 0;
}
2.用vector对象实现:
#include<iostream>
#include<vector>
using namespace std;
vector<int> ivec;
int main()
{
for(int i=0;i<10;i++) {
ivec.push_back(i);
cout<<ivec[i]<<ends;
}
cout<<endl;
return 0;
}
如果是知道运行时才能知道vector对象中元素的确切个数,也应该使用以上方法创建vector对象并为其赋值
例如:
// 从标准输入中读取单词,将其作为vector对象的元素存储
string word;
vector<string> text; // 空vector对象
while(cin>>word)
text.push_back(word); // 把word添加到text后面
关键概念:vector对象能高效增长
C++标准要求vector应该能在运行时高效快速地添加元素。因此既然vector对象能高效地增长,那么在定义vector对象的时候设定其大小也就没什么必要了。事实上如果这么做性能可能更差。
开始的时候创建空的vector对象,在运行时再动态添加元素,这一做法与C语言及其他大多数语言中内置数组类型的用法不同,特别是C语言学习者进入C++学习的时候,可以预计在创建vector对象时顺便指定其容量是最好的。然而事实上,通常的情况是恰恰相反的。
**注:**如果循环体内部包含有向vector对象添加元素的语句时,则不能使用范围for循环!!!
其他vector操作
vector提供了几种其他操作
操作 | 解释 |
---|---|
v.empty() | 如果v不含有任何元素,返回真;否则返回假 |
v.size() | 返回v中元素的个数 |
v.push_back(value) | 向v的尾端添加一个值为value的元素 |
v[n] | 返回v中第n个位置上元素的引用 |
v1 = v2 | 用v2中的元素的拷贝替换v1中的元素 |
v1 = {a,b,c…} | 用列表中元素的拷贝替换v1中的元素 |
v1 == v2 | v1和v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 | |
<, <=, >, >= | 顾名思义,以字典顺序进行比较 |
vector的下标运算符
使用vector下标运算符的相关规则:
- 使用下标运算符能获取到指定的元素
- 不能用下标形式添加元素
**注:**vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素
提示:只能对确知已存在的元素执行下标操作!
关于下标必须明确的一点:只能对确认已存在的元素执行下标操作。
例如:
vector<int> ivec; // 空vector对象
cout<<ivec[0]; // 错误:ivec不包含任何元素
vector<int> ivec2(10); // 含有10个元素的vector对象
cout<<ivec2[10]; // 错误:ivec2元素的合法索引是从0到9
试图用下标的形式去访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值
但是,这种通过下标访问不存在的元素的行为非常常见,而且会产生很严重的后果。所谓的缓冲区溢出(buffer overflow)指的就是这类错误,这也是导致PC及其他设备上应用程序程序出现安全问题的一个重要原因
确保下标合法的一种有效手段就是尽可能使用范围for语句
案例:
从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写成大写形式。输出改变后的结果
#include<iostream>
#include<vector>
#include<string>
#include<cctype>
using namespace std;
vector<string> vString;
int main()
{
string s;
char cont = 'y'; // 与用户交互,决定是否继续输入
cout<<"输入第一个词:"<<endl;
while(cin>>s) {
vString.push_back(s);
cout<<"您要继续吗,输入(y or n)?" ;
cin>>cont;
if(cont != 'y' && cont != 'Y')
break;
cout<<"请输入下一个词:"<<endl;
}
cout<<endl<<"vString内所包含的字符串:"<<endl;
for(auto item : vString)
cout<<item<<ends;
cout<<endl;
cout<<"转换后的字符串:"<<endl;
for(auto &item : vString) {
for(auto &s : item)
s = toupper(s);
cout<<item<<ends;
}
cout<<endl;
return 0;
}
迭代器介绍
迭代器类似于指针类型,迭代器也提供了对对象的间接访问。
使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。
使用迭代器
和指针不一样的是,获取迭代器不是取地址符,有迭代器的类型(如string,vector)同时拥有返回迭代器的成员
例如:
string str = "NewThreadCPP";
auto b = str.begin(),e = str.end(); // b和e的类型相同
end成员负责返回指向容器(如string)"尾元素的下一位置(one past the end)"的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的”尾后(off the end)"元素
end成员返回的迭代器被称为尾后迭代器或尾迭代器。
**注:**特殊情况下,如果容器为空,则begin和end返回的是同一个迭代器
标准容器迭代器的运算符
操作符 | 解释 |
---|---|
*iter | 返回迭代器iter所指元素的引用 |
iter->men | 解引用iter并获取该元素名为men的成员,等价于(*iter).men |
++iter | 令iter指示容器中的下一个元素 |
–iter | 令iter指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元 |
iter1 != iter2 | 素或者它们是同一个容器的尾后迭代器,则相等;反之,不相等 |
一般来说,拥有迭代器的标准库类型(如string,vector)使用iterator和const_iterator来表示迭代器的类型,iterator的对象可读可写,const_iterator只可读
数组
数组与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。
因为数组大小固定,因此对于某些特殊的应用来说程序运行时性能较好,但是相应的损失了一些灵活性
注:如果不清楚元素的确切个数,请使用vector
定义和初始化内置数组
数组是一种复合类型,在默认的情况下,数组的元素被默认初始化
**注意事项:**和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值
不允许拷贝和赋值
不能将数组的内容拷贝给其他的数组作为其初始值,也不能用数组为其他数组赋值:
int a[] = {0,1,2}; // 含有3个整数的数组
int a2[] = a; // 错误!不允许使用一个数组来初始化另一个数组
a2 = a; // 错误!不能把一个数组直接赋值给另一个数组
标准库函数begin和end
C++11新标准引入了两个名为begin和end的函数。
头文件:#include< iterator >
int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia); // 指向ia首元素的指针
int *last = end(ia); // 指向arr尾元素的下一位置指针
使用数组初始化vector对象
允许使用数组来初始化vector对象。为实现这个目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了
int int_arr[] = {0,1,2,3,4,5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr),end(int_arr));
建议:尽量使用标准库类型而非数组
使用指针和数组很容易出错。一部分原因是概念上的问题:指针常用于底层的操作。因此容易引发一些与烦琐细节有关的错误
现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针