字符串string
一、介绍
字符串类型,属于抽象数据类型库
,支持可变长字符串
。
内置类型
是由C++语言直接定义的。比如数字和字符,体现了大多数计算机硬件本身具备的能力。
标准库
定义了另外一组更高级性质的类型,它们尚未直接实现到计算机硬件中。
string
表示可变长字符序列
,使用时必须包含string
头文件,作为标准库的一部分,string
定义在命名空间std
中。
#include <string>
using std::string;
二、定义和初始化string
对象
如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式,只不过这些方式之间必须有所区别。如下是初始化string
对象最常用的一些方式:
string s1; // 默认初始化,s1是一个空字符串,即该string对象中没有任何字符
string s2(s1); // s2是s1的副本
string s2 = s1; // 等价于s2(s1)
// 如果提供了一个字符串字面值,则该字面值中除了最后那个空字符外其他所有的字符都被拷贝到新创建的
// string对象中去
string s3("value"); // s3是字面值"value"的副本,除了字面值最后的那个空字符外
string s3 = "value"; // 等价于s3("value")
// 如果提供的是一个数字和一个字符,则string对象的内容是给定字符连续重复若干次后得到的序列
// 把s4初始化为由连续n个字符c组成的串
string s4(10, 'c'); // s4的内容是10个c拼接在一起
直接初始化
和拷贝初始化
拷贝初始化:如果使用=
号初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始化拷贝到新创建的对象中去。
直接初始化:如果不使用=
号,则执行的是直接初始化。
当初始值只有一个时,使用直接初始化或拷贝初始化都行。
如果初始化要用到的值有多个,一般来说只能使用直接初始化的方式。
string s5 = "huhu"; // 拷贝初始化
string s6("huhu"); // 直接初始化
string s7(10, 'c'); // 直接初始化
三、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从0计起 |
s1 + s2 | 返回s1和s2连接后的结果 |
s1 = s2 | 用s2的副本代替s1中原来的字符 |
s1 == s2 |
如果s1和s2中所含的字符完全一样,则它们相等; string对象相等性判断区分大小写 |
s1 != s2 |
s1和s2中所含的字符不完全一样,则它们不相等 |
< ,<= ,> , >= |
利用字符在字典中的顺序进行比较, 且对字母的大小写敏感 |
读写string对象
#include <string>
#include <iostream>
using std::string;
int main() {
string s; // s 是空字符串
cin >> s; // 将string对象读入s,直到遇到空白停止
cout << s << endl; // 输出 s
return 0;
}
在执行读取操作时,string
对象会自动忽略开头的空白(即空格符、换行符、制表符等),并从第一个真正的字符开始读起,直到遇见下一个空白为止。
多个输入或者多个输出可以连写在一起:
string s1, s2;
cin >> s1 >> s2; // 把第一个输入读到s1中, 第二个输入读到s2中
cout << s1 << s2 << endl; // 输出两个string对象
读取未知数量的string
对象
#include <string>
#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main() {
string word;
// 检测流是否有效
while (cin >> word) { // 反复读取, 直至到达文件末尾或遇到空白符
cout << word << endl; // 逐个输出单词, 每个单词后面紧跟一个换行
}
return 0;
}
使用getline
读取一整行
使用场景:希望能在最终得到的字符串中保留输入时的空白符
入参:一个输入流、一个string
对象
getline
只要一遇到换行符就结束读取操作并返回结果
int main() {
string line;
// 每次读取一整行,直至到达文件的末尾
while (getline(cin, line)) {
cout << line << endl;
}
return 0;
}
因为line中不包含换行符,所以我们手动地加上换行操作符。
使用endl
结束当前行并刷新显示缓冲区。
string
的empty
和size
操作
empty
:判断string对象是否为空,空返回true,否则为false
string line;
// 每次读取一行, 遇到空行直接跳过
while (getline(cin, line)) {
if (!line.empty()) {
cout << line << endl;
}
}
size
:返回string对象的长度。即string对象中字符的个数
string line;
// 每次读取一行, 输出其中超过80个字符的行
while (getline(cin, line)) {
if (line.size() > 80) {
cout << line << endl;
}
}
string::size_type
类型
size
函数返回的类型是string::size_type
,是一个无符号类型的值,足够存放下任何string
对象的大小,即所有用于存放string
类的size
函数返回值的变量,都应该是string::size_type
类型的。
字面值和string
对象相加
当把string
对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符+
的两侧的运算对象至少有一个是string
:
string s1;
string s4 = s1 + ", "; // 正确:把一个string对象和一个字面值相加
string s5 = "hello" + ", "; // 错误:两个运算对象都不是string
因为历史原因,也为了与C兼容。所以C++语言中的字符串字面值并不是标准库类型string
的对象。切记,字符串字面值与string
是不同的类型。
处理string
对象中的字符
在cctype
头文件中定义了一组标准库函数处理这部分工作,如下表:
操作 | 说明 |
---|---|
isalum(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标准库头文件
处理每个字符
范围for
语句
for (declaration : expression) {
statement
}
一个string
对象表示一个字符序列,因此string
对象可以作为范围for
语句中的expression
部分。
// 打印输出string对象中的每一个字符
string str("some string");
for (auto c : str) {
cout << c << endl;
}
改变字符串中的字符
如果想要改变string
对象中字符的值,必须把循环变量定义成引用类型。
记住,所谓引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就能改变绑定它绑定的字符。
// 小写转大写
string s("Hello World!!!");
for (auto &c : s) {
c = toupper(c); // c是一个引用,因此赋值语句将改变s中字符的值
}
cout << s << endl;
只处理一部分字符
有时只需要处理其中的一个字符。
访问string
对象中单个字符有两种方式:1、下标 2、迭代器
下标运算符[]
接受的输入参数是string::size_type
类型的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。
下标从0
计起,s[0]
表示第一个字符,s[s.size() - 1]
表示最后一个字符
string
对象的下标必须大于等于0
而 小于s.size()
下标的值称作“下标”或者“索引”
使用下标迭代遍历
string s("Hello EveryBody !!!");
// 依次处理s中的字符直至我们处理完全部字符或者遇到一个空白
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) {
s[index] = toupper(s[index]); // 将当前字符转换成大写
}
四、额外的string
操作
这些操作大部分是string
类和C风格字符数组之间的相互转换。
构造string
的其他方法
n、len2、和pos2都是无符号值
操作 | 说明 |
---|---|
string s(cp, n) | s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符 |
string s(s2, pos2) | s是string s2从下标pos2开始的字符的拷贝。若pos2 > s2.size(),构造函数的行为未定义 |
string s(s2, pos2, len2) | s是string s2从下标pos2开始len2个字符的拷贝,若pos2 > s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size() - pos2 个字符 |
这些构造函数接受一个string
或一个const char*
参数,还接受指定拷贝多少个字符的参数。当我们传递给它们的是一个string
时,还可以给定一个下标来指出从哪里开始拷贝:
const char *cp = "Hello World!!!"; // 以空字符结束的数组
char noNull[] = {'H', 'i'}; // 不是以空字符结束
string s1(cp); // 拷贝cp中的字符直到遇到空字符: s1 == "Hello World!!!"
string s2(noNull, 2); // 从noNull拷贝两个字符: s2 == "Hi"
string s3(noNull); // 未定义:noNull不是以空字符结束
string s4(cp + 6, 5); // 从cp[6]开始拷贝5个字符: s4 == "World"
string s5(s1, 6, 5); // 从s1[6]开始拷贝5个字符: s5 == "World"
string s6(s1, 6); // 从s1[6]开始拷贝,直至s1末尾: s6 == "World!!!"
string s7(s1, 6, 20); // 正确,只拷贝到s1末尾;s7 == "World!!!"
string s8(s1, 16); // 抛出一个out_of_range异常
通常我们从一个const char*
创建string
对象时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。
substr
操作
substr
返回一个string
,它是原始string
的一部分或全部的拷贝。
可以传递给substr
一个可选的开始位置和计数值。
操作 | 说明 |
---|---|
s.substr(pos, n) | 返回一个string, 包含s中从pos开始的n个字符的拷贝。pos的默认值为0, n的默认值为s.size() - pos ,即拷贝从pos开始的所有字符 |
string s("hello world");
string s2 = s.substr(0, 5); // s2 = hello
string s3 = s.substr(6); // s3 = world
string s4 = s.substr(6, 11); // s3 = world
string s5 = s.substr(12); // 抛出一个out_of_range异常
改变string
的其他方法
string
类型支持顺序容器的赋值运算符以及assign
、insert
、erase
操作。此外,还定义了额外的insert
、erase
版本。
除了接受迭代器版本的insert
和erase
,string
还提供了接受下标的版本。
s.insert(s.size(), 5, '!'); // 在s末尾插入5个感叹号
s.erase(s.size() - 5, 5); // 从s删除最后5个字符
string
类型还提供了接受C风格字符数组的insert
和 assign
版本。例如:我们可以将以空字符结尾的字符数组insert
到或assign
给一个string
:
const char *cp = "Stately, plump Buck";
s.assign(cp, 7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"
append
和replace
函数
append
是在string
末尾进行插入操作的一种简写形式。
string s("C++ Primer"), s2 = s; // 将s和s2初始化为 "C++ Primer"
s.insert(s.size(), " 4th Ed."); // s == "C++ Primer 4th Ed."
s2.append(" 4th Ed."); // 等价方法: 将" 4th Ed."追加到s2: s == s2
replace
是调用erase
和insert
操作的一种简写形式。
// 将"4th"替换为"5th"的等价方法
s.erase(11, 3); // s == "C++ Primer Ed."
s.insert(11, "5th"); // s == "C++ Primer 5th Ed."
// 从位置11开始,删除3个字符并插入"5th"
s2.replace(11, 3, "5th"); // 等价方法: s == s2
操作 | 说明 |
---|---|
s.insert(pos, args) | 在pos之前插入args指定的字符, pos可以是一个下标或一个迭代器。接受下标的版本返回一个指向s的引用。接受迭代器的版本返回指向第一个插入字符的迭代器 |
s.erase(pos, len) | 删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始直至s末尾的所有字符。返回一个指向s的引用。 |
s.assign(args) | 将s中的字符替换为args中指定的字符。返回一个指向s的引用 |
s.append(args) | 将args追加到s。返回一个指向s的引用 |
s.replace(range, args) | 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用。 |
string
搜索操作
每个搜索操作都返回一个string::size_type
值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos
的static
成员。标准库将npos
定义为一个const string::size_type
类型,并初始化为值 -1
。由于npos
是一个unsigned
类型,此初始值意味着npos
等于任何string
最大的可能大小。
操作 | 说明 |
---|---|
s.find(args) | 查找s中args第一次出现的位置 |
s.rfind(args) | 查找s中args最后一次出现的位置 |
s.find_first_of(args) | 在s中查找args中任何一个字符第一次出现的位置 |
s.find_last_of(args) | 在s中查找args中任何一个字符最后一次出现的位置 |
s.find_first_not_of(args) | 在s中查找第一个不在args中的字符 |
s.find_last_not_of(args) | 在s中查找最后一个不在args中的字符 |
string name("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 == 0
compare
函数
s.compare 的几种参数形式 | 说明 |
---|---|
s2 | 比较s和s2 |
pos1, n1, s2 | 将s中从pos1开始的n1个字符与n2个字符进行比较 |
pos1, n1, s2, pos2, n2 | 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较 |
cp | 比较s与cp指向的以空字符结尾的字符数组 |
pos1, n1, cp | 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较 |
pos1, n1, cp, n2 | 将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较 |