3 字符串、向量和数组
引入
笔试题:
解答:
测试代码:
#include <iostream>
using namespace std;
int main()
{
char p[]="2018";
string p1="2018";
char *p2="2018";
cout<<p<<endl;
cout<<p1<<endl;
cout<<*p2<<*(p2+1)<<*(p2+2)<<*(p2+3);
}
运行结果:
同时会给出警告信息:c++禁止将字符串常量转变为char*,但是又没有报错,还可以正常运行,这里p可以理解为指向字符串常量(本质是字符串数组)首元素的指针。
根据运行结果,及理论支持:c++中存储字符串有两种方法,c++string方法和c串方法,
string做法:string str="hello";
c串方法:char str[]="hello";
选A
除了内置类型,c++还定义了一个内容丰富的抽象数据类型库,string和vector是两种重要的标准库类型,string支持可变长字符串,vector支持可变长集合。迭代器也是标准库类型,是string和vector的配套类型,常用于访问string或者vector中的元素。
除了内置类型,标准库定义了一组具有更高级性质的类型,与内置类型与硬件密切相关不同,它们尚未直接实现到计算机硬件中。
内置数组与string、vector相比,灵活性不足。
3.1 命名空间的using声明
在了解标准库类型之前,先学习访问标准库中名字的简单方法。
访问标准库中名字的方式
std::cin>>a; //方式一:命名空间::函数名,每次使用前都需要前缀,比较繁琐
using std::cin; //方式二:使用using声明:using 命名空间::函数名,声明了上述语句可直接访问命名空间中的名字
using std::cout;//方式二注意事项:每个名字都需要独立的using声明
注意:头文件不应包含using声明,因为头文件本身的内容会拷贝到引用它的文件中,不经意的包含可能会造成名字冲突。
3.2 标准库类型string
使用string类型必须包括以下using声明和#include指令:
#include <string> //包含string头文件
using std::string; //访问标准库的string名字
标准库类型对于一般应用场合具有足够的效率,因此能使用标准库类型尽量不要使用自定义类型。
3.2.1 定义和初始化string对象
如何初始化类的对象是由类本身决定的,一个类可以定义很多种初始化对象的方式,这些方式之间必须有所区别。
初始化string对象的方式:
string s1; //默认初始化,s1是空串
string s2(s1); //s2是s1的副本
string s2=s1; //s2是s1的副本
string s3("value"); //s3是字面值“value”的副本,除了最后一个空字符
string s3="value"; //s3是字面值“value”的副本,除了最后一个空字符
string s4(n,'c'); //把s4初始化为由连续n个字符c组成的串
直接初始化和拷贝初始化:
直接初始化 | 拷贝初始化 |
---|---|
不使用等号 | 使用等号 |
初始化一个或多个值 | 初始化一个值 |
总:c++中不同的初始化方式有哪些?直接初始化、拷贝初始化=、默认初始化、列表初始化{}
3.2.2 string对象上的操作
补充:c风格字符串和string类型字符串的区别:c风格字符串(或字符串字面值)以“\0”结尾。string类型不包含‘\0’,c风格字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。
一个类处理要规定初始化其对象的方式外,还要定义对象上能执行的操作。关于操作,类既能定义通过函数名调用的操作,也能定义<<,+等各种运算符在该类对象上的新含义(即运算符重载)。
string对象上的操作
os<<s; //将s写出输出流os中,返回os
is>>s; //从is中读取字符串赋给s,字符串以空格分隔,返回is
getline(is,s); //从is中读取一行给s,返回is
s.empty();
s.size();
s.find(); //如果存在,find返回字母所在位置的下标,若不存在,返回npos
s[n] //返回s中第n个字符的引用,位置n从0计起
s1+s2 //返回两个字符串连接后的结果
s1=s2 //用s2的副本代替s1中原来的字符
s1==s2
s1!=s2 //对大小写敏感
< <= > >= 利用字符在字典中的顺序进行比较,对字母的大小写敏感
注意:<<和>>都是返回运算符左侧的对象
问题1:既然string的运算符>>只能以空格分隔,如何获取一串用逗号隔开的数据?
将所有数据作为整个字符串读入,替换字符串中的逗号为空格,借助stringstream将分割用空格分隔的字符串
代码如下:
#include <string>
#include <iostream>
#include <sstream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
//当命令行需要向main函数传递数组时使用
//argc是一个形参,表示数组中字符串的数量
//argv是一个数组,其元素是指向c风格的字符串的指针
//也可以写成:int main(int argc, char** argv){}
{
int temp;
string strTemp;
vector <int>array;
int i = 0;
stringstream sStream;
//从命令行获取输入,全部存入字符串strTemp
cin >> strTemp;
//将strTemp中的逗号全部用空格代替
int pos = strTemp.find(','); //如果存在,find返回字母所在位置的下标,若不存在,返回npos
while (pos != strTemp.npos)
{
strTemp = strTemp.replace(pos, 1, 1, ' '); //将字符串中的','用空格代替
pos = strTemp.find(',');
}
//将替换后的字符串导入stringstream流中,stringstream可用于分割被空格、制表符等符号分割的字符串
sStream << strTemp;
//使用>>依次读取以空格分隔的元素
while (sStream >> temp)
{
array.push_back(temp);
}
for(int i=0;i<array.size();i++)
cout<<array[i]<<" ";
cout<<endl;
return 0;
}
和内置类型一样,可以使用标准库的iostream来读写string对象。在读string对象时,自动忽略开头的空格,从第一个真正的字符开始读起,直至遇见下一个空格。
由于<<和>>是返回运算符左侧的运算对象作为其结果,因此可以多个输入或者多个输出连写一起。
string s1,s2;
cin>>s1>>s2; //把第一个输入读到s1中,第二个输入读到s2中
cout<<s1<<s2; //第一个输出的是s1,第二个输出的是s2
读取未知数量的string:
int main()
{
string word;
while(cin>>word)
cout<<word<<endl;
return 0;
}
使用getline读取一整行:当希望最终得到的字符串中保留输入时的空白符,应该使用getline代替>>,getline的参数是一个输入流和一个string对象,getline以换行符为结束的标志,如果一开始就是换行符,则读入为空字符串。getline的返回值也是它的流参数。
int main()
{
string line;
while(getline(cin,line))
cout<<line<<endl;
return 0;
}
getline(is,s)和empty()结合使用,按行读取元素,并且只输出不为空的行
int main()
{
string line;
while(getline(cin,line))
{
if(!line.empty())
cout<<line<<endl;
}
return 0;
}
string::size_type类型
string的size()函数返回的是string::size_type类型。
标准库类型会定义一些配套类型,体现了标准库类型与机器无关的特性。
size_type的具体实现细节不清楚,但是可以确定的是size_type是一个无符号类型。由于不清楚size_type的具体实现,所以c++11允许使用auto或者decltype来推断变量类型。
size_type是一个无符号数,不要与有符号数混用
string类型比较的原则:
①逐一比较字符;
②大小写敏感;
③如果字符串的长度不同,且较短string对象的每个字符都与较长string对象对应位置字符相同,较短string对象小于较长string对象。
字符字面值和string对象:“+”运算符只在string中重载成为可以连接两个字符串的运算符,所以使用“+”时必须保证两侧的运算对象至少有一个是string。以下语句是错误的:
string s3="rain"+"today";
c++中的字符串字面值和string是不同的类型。
3.2.3 处理string对象中的字符
改变某个字符的特性: cctype头文件
isalnum(c); //当c为字母或者数字时为真
isalpha(c); //当c为字母时为真
iscntrl(c); //当c为控制字符时为真
isdigit(c); //当c为数字是为真
isxdigit(c); //当c为十六进制数字是为真
islower(c); //当c为小写字母时为真
isupper(c); //当c为大写字母时为真
isprint(c); //当c时可打印字符时为真
ispunct(c); //当c是标点符号时为真
isspace(c); //当c是空格时为真
tolower(c); //当c为大写字母时,输出对应的小写字母;否则原样输出
toupper(c); //当c为小写字母时,输出对应的大写字母;否则原样输出
c++ 标准库兼容c语言标准库,cctype是c++版本的c标准库头文件,是c++从c中沿袭下来的头文件。c++中从c沿袭的头文件在命名方面会去掉末尾“.h”,在前面加上‘c’,标识其从c语言标准库沿袭。
如果是对每一个字符进行处理,可以考虑使用范围for语句,范围for语句遍历给定序列中的每个元素并对序列中的值执行操作。
for(基础元素:序列)
执行操作
例如:
string s("string");
for(auto c:s)
if(ispunc(c))
cout<<c<<endl;
如果要使用范围for语句修改string类型的字符,必须把循环变量定义为引用。
例如:把string对象转换成大写形式
string s("string");
for(auto &c:s)
c=toupper(c);
cout<<s<<endl;
如果只是对string类型的部分字符进行操作,可以考虑使用下标运算符(从0开始,注意下标的合法性,上限是size()-1)或者迭代器
使用下标访问空string也会引发不可预知的结果。因此在使用下标访问之前,首先需要判定string是否为空。
为了安全,借助decltype总是设下标的类型是string::size_type无符号型 decltype(s.size()) index=0;
c++编译器并不检查下标是否合法,所以需要程序员在写程序时格外注意。
标准库容器都是类模板,在使用时都需要指定类型。string比较特殊,因为其是字符序列,所以其类型已经确定是字符,不是类模板,不需要指定类型。
迭代器类似指针类型,迭代器指向某个元素,可以从一个元素移向另一个元素,有有效和无效之分,可以通过解引用运算符来获取某个元素。begin()返回指向第一个元素的迭代器,end()返回最后一个元素下一个位置的迭代器。若容器为空,begin和end都返回尾后迭代器。**不知道也无需知道迭代器的准确类型,**可以使用auto。在使用迭代器时,for循环中尽量使用!=而不是<,因为所有容器都定义了!=,但不是所有容器都定义了<。