C++PrimerPlus 第四章 复合类型 - 4.3 string类简介
4.3 string类简介
ISO/ANSI C++98标准通过添加string类扩展了C++库,因此现在可以string类型的变量(使用C++的话说是对象)而不是字符数组来存储字符串。您将看到,string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用string类,必须在程序中包含头文件string。string类位于名称空间std中,因此您必须提供一条using编译指令,或者使用std::string来引用它。string类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。程序清单4.7说明了string对象与字符数组之间的一些相同点和不同点。
程序清单4.7 strtype1.cpp
//strtype1.cpp -- using the C++ string class
#include<iostream>
#include<string> //make string class available
int main()
{
using namespace std;
char charr1[20]; //create an empty array
char charr2[20] = "jaguar"; //create an initialized array
string str1; //create an empty string object
string str2 = "panther"; //create an initialized string
cout << "Enter a kind of feline: ";
cin >> charr1;
cout << "Enter another kind of feline: ";
cin >> str1;
cout << "Here are some felines:\n";
cout << charr1 << " " << charr2 << " "
<< str1 << " " << str2 //use cout for output
<< endl;
cout << "The third letter in " << charr2 << " is "
<< charr2[2] << endl;
cout << "The third letter in " << str2 << " is "
<< str2[2] << endl; //use array notation
return 0;
}
下面是该程序的运行情况:
Enter a kind of feline: ocelot
Enter another kind of feline: tiger
Here are some felines:
ocelot jaguar tiger panther
The third letter in jaguar is g
The third letter in panther is n
从这个示例可知,在很多方面,使用string对象的方式与使用字符数组相同。
- 可以使用C-风格字符串来初始化string对象。
- 可以使用cin来将键盘输入存储到string对象中。
- 可以使用cout来显示string对象。
- 可以使用数组表示法来访问存储在string对象中的字符。
程序清单4.7表明,string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组:
string str1; //create an empty string object
string str2 = "panther"; //create an initialized string
类设计让程序能够自动处理string的大小。例如,str1的声明创建一个长度为0的string对象,但程序将输入读取到str1中时,将自动调整str1的长度:
cin >> str1; //str1 resized to fit input
这使得与使用数组相比,使用string对象更方便,也更安全。从理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体。
4.3.1 C++字符串初始化
正如您预期的,C++11也允许将列表初始化用于C-风格字符串和string对象:
char first_date[] = { "Le Chapon Dodu" };
char second_date[] { "The Elegant Plate" };
string third_date = { "The Bread Bowl" };
string fourth_date{ "Hank's Fine Eats" };
4.3.2 赋值、拼接和附加
使用string类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象:
char charr1[20]; //create an empty array
char charr2[20] = "jaguar"; //create an initialized array
string str1; //create an empty string object
string str2 = "panther"; //create an initialized string
charr1 = charr2; //INVALID, no array assignment
str1 = str2; //VALID, object assignment ok
string类简化了字符串合并操作。可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾。继续前面的代码,您可以这样做:
string str3;
str3 = str1 + str2; //assign str3 the joined strings
str1 += str2; //add str2 to the end of str1
程序清单4.8演示了这些用法。可以将C-风格字符串或string对象与string对象相加,或将它们附加到string对象的末尾。
程序清单4.8 strtype2.cpp
//strtype2.cpp -- assigning, adding, and appending
#include<iostream>
#include<string> //make string class available
int main()
{
using namespace std;
string s1 = "penguin";
string s2, s3;
cout << "You can assign one string object to another: s2 = s1\n";
s2 = s1;
cout << "s1: " << s1 << ", s2: " << s2 << endl;
cout << "You can assign a C-style string to a string object.\n";
cout << "s2 = \"buzzard\"\n";
s2 = "buzzard";
cout << "s2: " << s2 << endl;
cout << "You can concatenate strings: s3 = s1 + s2\n";
s3 = s1 + s2;
cout << "s3: " << s3 << endl;
cout << "You can append strings.\n";
s1 += s2;
cout << "s1 += s2 yields s1 = " << s1 << endl;
s2 += " for a day";
cout << "s2 += \" for a day\" yields s2 = " << s2 << endl;
return 0;
}
转义序列\”表示双引号,而不是字符串结尾。该程序的输出如下:
You can assign one string object to another: s2 = s1
s1: penguin, s2: penguin
You can assign a C-style string to a string object.
s2 = “buzzard”
s2: buzzard
You can concatenate strings: s3 = s1 + s2
s3: penguinbuzzard
You can append strings.
s1 += s2 yields s1 = penguinbuzzard
s2 += " for a day" yields s2 = buzzard for a day
4.3.3 string类的其他操作
在C++新增string类之前,程序员也需要完成诸如给字符串赋值等工作。对于C-风格字符串,程序员使用C语言库中的函数来完成这些任务。头文件cstring(以前为string.h)提供了这些函数。例如,可以使用函数strcpy()将字符串复制到字符数组中,使用函数strcat()将字符串附加到字符数组末尾:
strcpy(charr1, charr2); //copy charr2 to charr1
strcat(charr1, charr2); //append contents of charr2 to charr1
程序清单4.9对用于string对象的技术和用于字符数组的技术进行了比较。
程序清单4.9 strtype3.cpp
//strtype3.cpp -- more string class features
#include<iostream>
#include<string> //make string class available
#include<cstring> //C-style string library
int main()
{
using namespace std;
char charr1[20];
char charr2[20] = "jaguar";
string str1;
string str2 = "panther";
//assignment for string objects and character arrays
str1 = str2; //copy str2 to str1
strcpy(charr1, charr2); //copy charr2 to charr1
//appending for string objects and character arrays
str1 += " paste"; //add paste to end of str1
strcat(charr1, " juice"); //add juice to end of charr1
//finding the length of a string object and a C-style string
int len1 = str1.size(); //obtain length of str1
int len2 = strlen(charr1); //obtain length of charr1
cout << "The string " << str1 << " contains "
<< len1 << " characters.\n";
cout << "The string " << charr1 << " contains "
<< len2 << " characters.\n";
return 0;
}
下面是该程序的输出:
The string panther paste contains 13 characters.
The string jaguar juice contains 12 characters.
处理string对象的语法通常比使用C字符串函数简单,尤其是执行较为复杂的操作时。例如,对于下述操作:
str3 = str1 + str2;
使用C-风格字符串时,需要使用的函数如下:
strcpy(charr3, charr1);
strcpy(charr3, charr2);
另外,使用字符数组时,总是存在目标数组过小,无法存储指定信息的危险,如下面的示例所示:
char site[10] = “house”;
strcat(site, “ of pancakes”); //memory problem
函数strcat()试图将全部12个字符复制到数组site中,这将覆盖相邻的内存。这可能导致程序终止,或者程序继续运行,但数据被损坏。string类具有自动调整大小的功能,从而能够避免这种问题。C函数库确实提供了与strcat()和strcpy()类似的函数——strncat()和strncpy(),它们接受指出目标数组最大允许长度的第三个参数,因此更为安全,但使用它们进一步增加了编写程序的复杂度。
下面是两种确定字符串中字符数的方法:
int len1 = str1.size(); //obtain length of str1
int len2 = strlen(charr1); //obtain length of charr1
函数strlen()是一个常规函数,它接受一个C-风格字符串作为参数,并返回该字符串包含的字符数。函数size()的功能基本上与此相同,但句法不同:str1不是被用作函数参数,而是位于函数名之前,它们之间用句点连接。与第3章介绍的put()方法相同。这种句法表明,str1是一个对象,而size()是一个类方法。方法是一个函数,只能提供所属类的对象进行调用。在这里,str1是一个string对象,而size()是string类的一个方法。总之,C函数使用参数来指出要使用哪个字符串,而C++string类对象使用对象名和句点运算符来指出要使用哪个字符串。
4.3.4 string类I/O
正如您知道的,可以使用cin和运算符>>来将输入存储到string对象中,使用cout和运算符<<来显示string对象,其句法与处理C-风格字符串相同。但每次读取一行而不是一个单词时,使用的句法不同,程序清单4.10说明了这一点。
程序清单4.10 strtype4.cpp
//strtype4.cpp -- line input
#include<iostream>
#include<string> //make string class available
#include<cstring> //C-style string library
int main()
{
using namespace std;
char charr[20];
string str;
cout << "Length of string in charr before input: "
<< strlen(charr) << endl;
cout << "Length of string in str before input: "
<< str.size() << endl;
cout << "Enter a line of text:\n";
cin.getline(charr, 20); //indicate maximum length
cout << "You entered: " << charr << endl;
cout << "Enter another line of text:\n";
getline(cin, str); //cin now an argument; no length specifier
cout << "You entered: " << str << endl;
cout << "Length of string in charr after input: "
<< strlen(charr) << endl;
cout << "Length of string in str after input: "
<< str.size() << endl;
return 0;
}
下面是一个运行该程序时的输出示例:
Length of string in charr before input: 31
Length of string in str before input: 0
Enter a line of text:
peanut butter
You entered: peanut butter
Enter another line of text:
blueberry jam
You entered: blueberry jam
Length of string in charr after input: 13
Length of string in str after input: 13
在用户输入之前,该程序指出数组charr中的字符串长度为31,这比该数组的长度还要大。这里要两点需要说明。首先,为初始化的数组的内容是未定义的;其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度可能与此不同。
另外,用户输入之前,str中的字符串长度为0。这是因为未被初始化的string对象的长度被自动设置为0。
下面是将一行输入读取到数组中的代码:
cin.getline(charr, 20);
这种句点表示法表明,函数getline()是istream类的一个类方法(还记得吗,cin是一个istream对象)。正如前面指出的,第一个参数是目标数组;第二个参数数组长度,getline()使用它来避免超越数组的边界。
下面是将一行输入读取到string对象中的代码:
getline(cin, str);
这里没有使用句点表示法,这表明这个getline()不是类方法。它将cin作为参数,指出到哪里去查找输入。另外,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小。
那么,为何一个getline()是istream的类方法,而另一个不是呢?在引入string类之前很久,C++就有istream类。因此istream的设计考虑到了诸如double和int等基本C++数据类型,但没有考虑string类型,所以istream类中,有处理double、int和其他基本类型的方法,但没有处理string对象的类方法。
由于istream类中没有处理string对象的类方法,因此您可能会问,下述代码为何可行呢?
cin >> str; //read a word into the str string object
像下面这样的代码使用istream类的一个成员函数:
cin >> x; //read a value into a basic C++ type
但前面处理string对象的代码使用string类的一个友元函数。有关友元函数及这种技术为何可行,将在第11章介绍。另外,您可以将cin和cout用于string对象,而不用考虑其内部工作原理。
4.3.5 其他形式的字符串字面值
本书前面说过,除char类型外,C++还有类型wchar_t;而C++11新增了类型char16_t和char32_t。可创建这些类型的数组和这些类型的字符串字面值。对于这些类型的字符串字面值,C++分别使用前缀L、u和U表示,下面是一个如何使用这些前缀的例子:
wchar_t title[] = L”Chief Astrogator”; //w_char string
char16_t name[] = u”Felonia Ripova”; //char_16 string
char32_t car[] = U”Humber Super Snipe”; //char_32 string
C++11还支持Unicode字符编码方案UTF-8。在这种方案中,根据编码的数字值,字符可能存储为1~4个八位组。C++使用前缀u8来表示这种类型的字符串字面值。
C++11新增的另一种类型是原始(raw)字符串。在原始字符串中,字符表示的就是自己,例如,序列\n不表示换行符,而表示两个常规字符——斜杠和n,因此在屏幕中显示时,将显示这两个字符。另一个例子是,可在字符串中使用”,而无需像程序清单4.8中那样使用繁琐的\”。当然,既然可在字符串字面量包含”,就不能再使用它来表示字符串的开头和末尾。因此,原始字符串将”(和)”用作定界符,并使用前缀R来标识原始字符串:
cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n';
上述代码将显示如下内容:
Jim “King” Tutt uses “\n” instead of endl.
如果使用标准字符串字面值,将需编写如下代码:
cout << "Jim \"King\" Tutt uses \"\\n\" instead of endl." << '\n';
在上述代码中,使用了\来显示\,因为单个\表示转义序列的第一个字符。
输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符。
如果要在原始字符串中包含)”,该如何办呢?编译器见到第一个)”时,会不会认为字符串到此结束?会的。但原始字符串语法允许您在表示字符串开头的”和(之间添加其他字符,这意味着表示字符串结尾的”和)之间也必须包含这些字符。因此,使用R”+* (标识原始字符串的开头时,必须使用)+* ”标识原始字符串的结尾。因此,下面的语句:
cout << R"+*("(Who wouldn't?)", she whispered.)+*" << endl;
将显示如下内容:
“(Who wouldn’t?)”, she whispered.
总之,这使用"+* ( 和 )+ *“替代了默认定界符”(和)"。自定义定界符时,在默认定界符之间添加任意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。
可将前缀R与其他字符串前缀结合使用,以标识wchar_t等类型的原始字符串。可将R放在前面,也可将其放在后面,如Ru、UR等。
下面介绍另一种复合类型——结构。