内置数组是一种更基础的类型,string和l vector都是对它的某种抽象。本章将介绍标准库类型string。
目录
前言
C++语言还定义了一个内容丰富的抽象数据类型库。其中,string和 vector是两种最重要的标准库类型,前者支持可变长字符串,后者则表示可变长的集合。还有-种标准库类型是迭代器,它是string和vector的配套类型,常被用于访问string 中的字符或vector中的元素。
内置数组是一种更基础的类型,string和l vector都是对它的某种抽象。本章将分别介绍数组以及标准库类型string和l vector。
一、命名空间的using 声明
目前所用到的库函数基本上都属于命名空间std,而程序也显式地将这一点标示了出来。例如,std::cin表示从标准输入中读取内容。此处使用作用域操作符(::)的含义是:编译器应从操作符左侧名字所示的作用域中寻找右侧那个名字。std : :cin的意思就是要使用命名空间std中的名字cin。
上面的方法显得比较烦琐,通过更简单的途径也能使用到命名空间中的成员。例如其中一种最安全的方法,也就是使用using声明( using declaration)。有了 using声明就无须专门的前缀(形如命名空间::)也能使用所需的名字了。
using声明具有如下的形式:
using namespace std;
或者
using namespace::name;
每个名字都需要独立的using声明
按照规定,每个using 声明引入命名空间中的一个成员。例如,可以把要用到的标准库中的名字都以using声明的形式表示出来:
#include <iostream>
//通过下列using声明,我们可以使用标准库中的名字
using std::cin;
using std::cout;
using std::endl;
int main()
{
cout << "Enter two numbers: " << endl;
int v1, v2;
cin >> v1 >> v2;
cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << endl;
return 0;
}
在上述程序中,一开始就有对cin、cout和 endl的using声明,这意味着我们不用再添加std::形式的前缀就能直接使用它们。C++语言的形式比较自由,因此既可以一行只放一条using声明语句,也可以一行放上多条。不过要注意,用到的每个名字都必须有自己的声明语句,而且每句话都得以分号结束。
头文件不应包含using声明
位于头文件的代码一般来说不应该使用using声明。这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就都会有这个声明。
对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。
注意事项
但凡用到的标准库中的名字都已经使用using语句声明过了。例如,我们将在代码中直接使用cin,而不再使用std: :cin。
二、标准库类型string
标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。作为标准库的一部分,string定义在命名空间std中。
#include <string>
using std::string;
1.定义和初始化string对象
如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式,只不过这些方式之间必须有所区别:或者是初始值的数量不同,或者是初始值的类型不同。
string s1;
//默认初始化,s1是一个空串
string s2(s1);
//s2是s1的副本
string s2 = sl;
//等价于s2(s1),s2是sl的副本
string s3("value");
//s3是字面值"value"的副本,除了字面值最后的那个空字符外
//小括号是调用string对象的构造函数
string s3 = "value";
//等价于s3("value"),s3是字面值"value"的副本
string s4(n, 'c ');
//把s4初始化为由连续n个字符c组成的串
直接初始化和拷贝初始化
C++语言有几种不同的初始化方式,通过string我们可以清楚地看到在这些初始化方式之间到底有什么区别和联系。如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化( direct initialization)。
当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果像上面的s4那样初始化要用到的值有多个,一般来说只能使用直接初始化的方式:
string s5 = "hiya" ; //拷贝初始化
string s6 ( "hiya" ) ; //直接初始化
string s7(10, 'c') ; //直接初始化,s7的内容是cccccccccc
对于用多个值进行初始化的情况,非要用拷贝初始化的方式来处理也不是不可以,不过需要显式地创建一个(临时)对象用于拷贝:
string s8 = string(10,'c');
//拷贝初始化,s8的内容是cccccccccc
s8的初始值是string (10,' c'),它实际上是用数字10和字符c两个参数创建出来的一个string对象,然后这个string对象又拷贝给了s8。这条语句本质上等价于下面的两条语句:
string temp (10,'C'); // temp的内容是cccccccccc
string s8 = temp; //将temp拷贝给s8
尽管初始化s8的语句合法,但和初始化s7的方式比较起来可读性较差,也没有任何补偿优势。
2.string对象上的操作
一个类除了要规定初始化其对象的方式外,还要定义对象上所能执行的操作。其中,类既能定义通过函数名调用的操作,就像sales_item类的isbn函数那样,也能定义<<、+等各种运算符在该类对象上的新含义。
读写string对象
使用标准库中的iostream来读写int、double等内置类型的值。同样,也可以使用IO操作符读写string对象:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string name;
cin >> name;
cout << name << endl;
return 0;
}
这段程序首先定义一个名为name的空.string,然后将标准输入的内容读取到s中。在执行读取操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。
和内置类型的输入输出操作一样,string对象的此类操作也是返回运算符左侧的运算对象作为其结果。因此,多个输入或者多个输出可以连写在一起:
读取未知数量的string对象
读取的对象是string而非 int,但是while语句的条件部分和之前版本的程序是一样的。该条件负责在读取时检测流的情况,如果流有效,也就是说没遇到文件结束标记或非法输入,那么执行 while 语句内部的操作。此时,循环体将输出刚刚从标准输入读取的内容。重复若干次之后,一旦遇到文件结束标记或非法输入循环也就结束了。
使用getline读取一整行
有时希望能在最终得到的字符串中保留输入时的空白符,这时应该用getline函数代替原来的>>运算符。getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得的结果是个空string。
和输入运算符一样,getline 也会返回它的流参数。因此既然输入运算符能作为判断的条件,也能用getline 的结果作为条件。例如,可以通过改写之前的程序让它一次输出一整行,而不再是每行输出一个词:
因为line 中不包含换行符,所以我们手动地加上换行操作符。和往常一样,使用endl结束当前行并刷新显示缓冲区.。
触发getline函数返回的那个换行符实际上被丢弃掉了,得到的string对象中并不包含该换行符。
string 的empty和size操作
empty函数根据string对象是否为空返回一个对应的布尔值。和sales_item类的isbn成员一样,empty也是string 的一个成员函数。调用该函数的方法很简单,只要使用点操作符指明是哪个对象执行了empty函数就可以了。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string line;
while (getline(cin , line) )
if(!line.empty())
cout << line << endl;
return 0;
}
if语句的条件部分使用了逻辑非运算符(!),它返回与其运算对象相反的结果。此例中,如果str不为空则返回真。size函数返回string对象的长度(即string对象中字符的个数),可以使用size函数只输出长度超过80个字符的行:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string line;
while (getline(cin , line) )
if(line.size() > 80)
cout << line << endl;
return 0;
}
string:size_type类型
对于size函数来说,返回一个int或者一个unsigned似乎都是合情合理的。但其实size 函数返回的是一个string : :size_type类型的值,下面就对这种新的类型稍作解释。
string类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size_type即是其中的一种。在具体使用的时候,通过作用域操作符来表明名字size_type是在类string中定义的。
尽管我们不太清楚string : :size_type类型的细节,但有一点是肯定的:它是一个无符号类型的值(参见2.1.1节,第 30页)而且能足够存放下任何string对象的大小。所有用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。
过去,string ::size_type这种类型有点儿神秘,不太容易理解和使用。在C++11新标准中,允许编译器通过auto或者decltype来推断变量的类型:
auto len = line.size(); // len 的类型是string ::size_type
由于size函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。例如,假设n是一个具有负值的int,则表达式s.size ()<n 的判断结果几乎肯定是true。这是因为负值n会自动地转换成一个比较大的无符号值。
比较string对象
string类定义了几种用于比较字符串的运算符。这些比较运算符逐一比较string对象中的字符,并且对大小写敏感,也就是说,在比较时同一个字母的大写形式和小写形式是不同的。
相等性运算符(==和!=)分别检验两个string对象相等或不相等,string对象相等意味着它们的长度相同而且所包含的字符也全都相同。关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。
上述这些运算符都依照(大小写敏感的)字典顺序:
- 如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长 string对象。
- 如果两个string对象在某些对应的位置上不一致,则string对象比较的结果其实是string对象中第一对相异字符比较的结果。
为string对象赋值
一般来说,在设计标准库类型时都力求在易用性上向内置类型看齐,因此大多数库类型都支持赋值操作。对于string类而言,允许把一个对象的值赋给另外一个对象:
string st1 (10,' c' ), st2; //st1的内容是cccccccccc; st2是一个空字符串
st1 = st2 ; //赋值:用st2的副本替换st1的内客//此时st1和st2都是空字符串
两个string 对象相加
两个string对象相加得到一个新的string对象,其内容是把左侧的运算对象与右侧的运算对象串接而成。也就是说,对 string对象使用加法运算符(+)的结果是一个新的string对象,它所包含的字符由两部分组成:前半部分是加号左侧string对象所含的字符、后半部分是加号右侧string对象所含的字符。另外,复合赋值运算符(+=)负责把右侧string对象的内容追加到左侧 string对象的后面:
字面值和string 对象相加
即使一种类型并非所需,我们也可以使用它,不过前提是该种类型可以自动转换成所需的类型。因为标准库允许把字符字面值和字符串字面值转换成string对象,所以在需要string对象的地方就可以使用这两种字面值来替代。
3.处理string对象中的字符
经常需要单独处理string对象中的字符,比如检查一个string对象是否包含空白,或者把string对象中的字母改成小写,再或者查看某个特定的字符是否出现等。这类处理的一个关键问题是如何获取字符本身。有时需要处理string对象中的每一个字符,另外一些时候则只需处理某个特定的字符,还有些时候遇到某个条件处理就要停下。
处理每个字符?使用基于范围的for语句
如果想对string对象中的每个字符做点儿什么操作,目前最好的办法是使用C++11新标准提供的一种语句:范围for (range for)语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作。
#include <iostream>
#include<string>
using namespace std;
int main()
{
string str("hello");
cout << str[0] << endl;
cout << str[4] << endl;
for (string::size_type i = 2; i != str.size(); ++i)
{
str[i] = '*';
}
cout << str << endl;
return 0;
}
总结
以上就是今天要讲的内容string类型。内置数组是一种更基础的类型,string和l vector都是对它的某种抽象。