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等。

下面介绍另一种复合类型——结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hank_W

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值