文章目录
标准库类型string表示可变长的字符序列,使用 string 类型必须首先包含string头文件。
一、定义和初始化string对象
如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式,只不过这些方式之间必须有所区别:或者是初始值的数量不同,或者是初始值的类型不同。
//初始化string对象的方式
string s1; //默认初始化,s1是一个空串
string s2(s1); //s2是s1的副本
string s2 = sl; //等价于s2(s1),s2是s1的副本
string s3("value"); //s3是字面值"value"的副本,除了字面值最后的那个空字符外
string s3 = "value"; //等价于s3 ("value"),s3是字面值"value"的副本
string s4(n, 'c'); //把s4初始化为由连续n个字符c组成的串
可以通过默认的方式初始化一个string对象,这样就会得到一个空的string,也就是说,该string对象中没有任何字符。
如果提供了一个字符串字面值,则该字面值中除了最后那个空字符外其他所有的字符都被拷贝到新创建的string对象中去。
如果提供的是一个数字和一个字符,则string对象的内容是给定字符连续重复若干次后得到的序列。
直接初始化和拷贝初始化
-
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。
-
当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果像上面的s4那样初始化要用到的值有多个,一般来说只能使用直接初始化的方式:
string s5 = "hiya"; //拷贝初始化 string s6("hiya"); //直接初始化 string s7(10, 'c');//直接初始化,s7的内容是cccccccccc
-
对于用多个值进行初始化的情况,非要用拷贝初始化的方式来处理也不是不可以,不过需要显式地创建一个(临时)对象用于拷贝:
string s8 = string (10,'C');//拷贝初始化,s8的内容是 cccccccccc
二、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从О计起 |
s1+s2 | 返回s1和 s2连接后的结果 |
s1=s2 | 用s2的副本代替s1中原来的字符 |
s1==s2 | 如果s1和s2中所含的字符完全一样,则它们相等 |
s1!=s2 | 等性判断对字母的大小写敏感 |
<,<=,>,>= | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感 |
1.读写string对象
string s; //空字符串
cin >> S; //将string 对象读入s,遇到空白停止
cout << s << endl; //输出s
在执行读取操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。
多个输入或者多个输出可以连写在一起。
2.读取未知数量的string对象
string word;
while (cin >> word) //反复读取,直至到达文件末尾
cout << word << endl; //逐个输出单词,每个单词后紧跟一个换行
3.使用getline读取一整行
getline可以在最终得到的字符串中保留输入时的空白符。
getline 函数的参数是一个输入流和一个 string 对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果。
string s;
//每次读入一整行,直至到达文件末尾
while (getline(cin, s))
cout<< s << endl;
4.string的empty和size操作
-
empty函数根据string对象是否为空返回一个布尔值。empty也是string 的一个成员函数。调用该函数的方法:使用点操作符指明是哪个对象执行了empty函数。
string s; //每次读入一整行,遇到空行直接跳过 while (getline (cin,s)) if( ! s.empty()) //逻辑非运算符(!),它返回与其运算对象相反的结果 cout << s << endl;
-
size函数返回string对象的长度(即string对象中字符的个数),可以使用size函数只输出长度超过80个字符的行:
string s; //每次读入一整行,输出其中超过80个字符的行 while (getline(cin, s) ) if(s.size() > 80) cout<< s << endl;
5.string::size_type类型
size 函数返回的是一个string: :size_type类型的值。
string类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size_type即是其中的一种。在具体使用的时候,通过作用域操作符来表明名字size_type是在类string 中定义的。
尽管我们不太清楚string : :size_type类型的细节,但有一点是肯定的:它是一个无符号类型的值而且能足够存放下任何string对象的大小。所有用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。
我们通过auto或者decltype来推断变量的类型:
auto a = s.size();// a的类型是string: :size_type
由于size函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。
6.比较string对象
相等性运算符(==和!=)分别检验两个string对象相等或不相等,string对象相等意味着它们的长度相同而且所包含的字符也全都相同。关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。上述这些运算符都依照(大小写敏感的)字典顺序:
1.如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string 对象小于较长 string对象。
2.如果两个string 对象在某些对应的位置上不一致,则string对象比较的结果其实是string对象中第一对相异字符比较的结果。
7.为string对象赋值
一般来说,在设计标准库类型时都力求在易用性上向内置类型看齐,因此大多数库类型都支持赋值操作。对于string类而言,允许把一个对象的值赋给另外一个对象:
string s1(10,'C'),s2; // s1的内容是cccccccccc; s2是一个空字符串
s1 = s2; //赋值:用s2的副本替换s1的内容
//此时sl和s2都是空字符串
8.两个string对象相加
两个string对象相加得到一个新的string对象,其内容是把左侧的运算对象与右侧的运算对象串接而成。另外,复合赋值运算符(+=)负责把右侧string对象的内容追加到左侧string对象的后面:
string s1 = "hello, ", s2 = "world\n" ;
string s3 = s1 +s2; // s3的内容是hello, world\n
s1 += s2; //等价于s1 = s1 + s2
9.字面值和string对象相加
标准库允许把字符字面值和字符串字面值转换成string对象,所以在需要string对象的地方就可以使用这两种字面值来替代。利用这一点将之前的程序改写为如下形式:
string s1 = "hello",s2 = "world"; //在sl和s2中都没有标点符号
string s3 = s1 + "," +s2 + ' \n' ;
当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string:
string s4 = s1 + ","; //正确:把一个string对象和一个字面值相加
string s5 = "hello" + ","; //错误:两个运算对象都不是string
string s6 = s1 +"," + "world"; //正确:每个加法运算符都有一个运算对象是string
//等价于string s6 = (s1 +",")+"world" ;
string s7 = "hello" + "," +s2; //错误:不能把字面值直接相加
因为某些历史原因,也为了与C兼容,所以C++语言中的字符串字面值并不是标准库类型string的对象。切记,字符串字面值与string是不同的类型。
三、处理string对象中的字符
我们经常需要单独处理string对象中的字符,比如检查一个string对象是否包含空白,或者把string对象中的字母改成小写,再或者查看某个特定的字符是否出现等。这类问题处理的关键一个是如何获取字符本身,另一个是要知道能改变某个字符的特性。在cctype头文件中定义了一组的标准库函数处理这部分工作详细可见下方:
cctype头文件中的函数 | |
---|---|
isalnum(c ) | 当c是字母或数字时为真 |
isalpha (c ) | 当c是字母时为真 |
iscntrl (c ) | 当c是控制字符时为真 |
isdigit (c ) | 当c是数字时为真 |
isgraph(c ) | 当c不是空格但可打印时为真 |
islower(c ) | 当c是小写字母时为真 |
isprint (c ) | 当c是可打印字符时为真(即c是空格或c具有可视形式) |
ispunct (c ) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c ) | 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c ) | 当c是大写字母时为真 |
isxdigit(c ) | 当c是十六进制数字时为真 |
tolower (c ) | 如果c是大写字母,输出对应的小写字母;否则原样输出c |
toupper(c ) | 如果c是小写字母,输出对应的大写字母;否则原样输出c |
- 建议:使用C++版本的C标准库头文件
C++标准库中除了定义C++语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++则将这些文件命名为cname。也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里的c表示这是一个属于C语言标准库的头文件。
因此,cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范上来讲更符合CH+语言的要求。特别的,在名为cname的头文件中定义的名字从属于命名空间 std,而定义在名为.h的头文件中的则不然。
一般来说,C++程序应该使用名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。如果使用.h形式的头文件,程序员就不得不时刻牢记哪些是从C语言那儿继承过来的,哪些又是C++语言所独有的。
1.处理每个字符?使用基于范围的for语句
-
如果想对string对象中的每个字符做点什么操作,目前最好的办法是使用范围for 语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
for (declaration : expression) statement
其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。
-
string s("some string"); //每行输出s中的一个字符. for (auto c : s) //对于s中的每个字符 cout << c << endl; //输出当前字符,后面紧跟一个换行符
for循环把变量c和s联系了起来,其中我们定义循环控制变量的方式与定义任意一个普通变量是一样的。每次迭代,s的下一个字符做被拷贝给c,因此该循环可以读作“对于字符串s中的每个字符c,”执行某某操作。此例中的”某某操作”即输出一个字符,然后换行。
举个稍微复杂一点的例子,使用范围for语句和ispunct函数来统计string对象中标点符号的个数:
string s( "Hello world! ! ! "); decltype(s.size()) p = 0; //p的类型和s.size的返回类型一样; //统计s中标点符号的数量 for (auto c : s) //对于s中的每个字符 if (ispunct (c)) //如果该字符是标点符号 ++p; //将标点符号的计数值加1 cout << p << s << endl;
2.使用范围for语句改变字符串中的字符
如果想要改变string对象中字符的值,必须把循环变量定义成引用类型。引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就能改变它绑定的字符。
把字符串改写为大写字母的形式,可以使用标准库函数toupper,该函数接收一个字符,然后输出其对应的大写形式。把整个string对象转换成大写,只要对其中的每个字符调用toupper函数并将结果再赋给原字符就可以了:
string s("Hello world!! !"); //转换成大写形式。
for (auto &c : s) //对于s 中的每个字符(注意:c是引用)
c= toupper(c); // c是一个引用,因此赋值语句将改变s中字符的值
cout<<s<<endl;
3.只处理一部分字符
要想访问string对象中的单个字符有两种方式:一种是使用下标,另外一种是使用迭代器.
下标运算符([ ])接收的输入参数是string : : size_type类型的值,这个参数表示要访问的字符的位置;返回值是该位置上字符的引用。
string对象的下标从0计起。如果string对象s至少包含两个字符,则s [0]是第1个字符、s [1]是第2个字符、s [s.size ()-1]是最后一个字符。
string对象的下标必须大于等于0而小于s.size()。使用超出此范围的下标将引发不可预知的结果,以此推断,使用下标访问空string也会引发不可预知的结果。
下标的值称作“下标”或“索引",任何表达式只要它的值是一个整型值就能作为索引。如果某个索引是带符号类型的值将自动转换成由 string: :size type表达的无符号类型。
if ( !s.empty()) //确保确实有字符需要输出
cout << s[0]<<endl; //输出s的第一个字符
在访问指定字符之前,首先检查s是否为空。其实不管什么时候只要对string对象使用了下标,都要确认在那个位置上确实有值。如果s为空,则s [0]的结果将是未定义的。
只要字符串不是常量,就能为下标运算符返回的字符赋新值。
string s("some string");
if (!s.empty()) //确保s[0]的位置确实有字符
s[O]= toupper(s[0]); //为s的第一个字符赋一个新值
4.使用下标执行迭代
//把s的第一个词改成大写形式:
//依次处理s中的字符直至我们处理完全部字符或者遇到一个空白
for (decltype(s.size()) n = 0;n != s.size( ) && !isspace(s[n]); ++n)
s[n] = toupper(s[n]);//将当前字符改成大写形式
注意下标的合法性,即下标必须大于等于0并且小于字符串size()的值。
逻辑与运算符(&&):如果参与运算的两个运算对象都为真,则逻辑与结果为真;否则结果为假。只有当左侧运算对象为真时才会检查右侧运算对象的情况。
5.使用下标随机访问
其实也能通过计算得到某个下标值,直接获取对应位置的字符,并不是每次都得从前往后依次访问。
例如,想要编写一个程序把0到15之间的十进制数转换成对应的十六进制形式,只需初始化一个字符串令其存放16个十六进制“数字”:
const string h = "0123456789ABCDEF";//可能的十六进制数字
cout<< "Enter a series of numbers between 0 and 15" << " separated by spaces. Hit ENTER when finished: " << endl;
string r; //用于保存十六进制的字符串
string::size_type n; //用于保存从输入流读取的数
while (cin >> n)
if (n < h.size()) //忽略无效输入
r += h[n]; //得到对应的十六进制数字
cout << "Your hex number is: " << r << endl;