紧接上一篇,我们继续深入探讨
string的使用。
在上一篇中,我们已经了解了 string 的基本概念与部分常见操作。本文将在其基础上进一步展开,介绍更多实用的用法与细节,帮助你更全面地掌握 string 在实际开发中的应用。
上一篇地址:string的使用(1)
Element access

operator\[\]
以数组下标的形式访问字符串内容。

cpp
string s1("hello world");
//利用数组下标遍历
for (size_t i = 0;i < s1.size();i++)
{
cout << s1[i];
}
cout << endl;

编译器会根据情况选择不同的重载版本。这里我们使用数组下标访问但未对内容进行更改,因此会调用const版本。
与普通数组下标访问不同的是,string重载的operator[]会检查下标的合理性,超出范围则会报错。
cpp
cout << s1[100];

可以看到发生了报错。
同样是遍历字符串,其实还有一种更简便的方法,这里就不再赘述。
C++11标准:auto关键字和范围for
在 C++11 中引入了关键字 auto,它的用法是auto 变量名 = 初始值。auto会根据变量内容自动识别该变量的类型。
cpp
auto d = 10;//识别类型为int
auto c = 3.14f;//识别类型为float
cout << d << endl;
cout << c << endl;

对于数组,我们可以用范围 for 来遍历并打印。范围 for 的使用方法如下:
cpp
int arr[] = { 1,2,3,4,5,6,7,8,9 };
for (auto a : arr)//auto 自定义变量名 : 要迭代的对象
{
cout << a << " ";
}
cout << endl;
这样就可以遍历数组 arr 了。
范围 for 的特点是 auto 自动识别变量类型,并且不用我们手动往后 ++(如 i++ 之类的),它会自动往后走,遍历一遍该对象。
a自动获取数组arr中的数据,auto将a的类型识别为int,并主动往后走遍历数组直到数组结束。

我们也可以用范围 for 来遍历 string 对象。
cpp
string s2("123456");
for (auto ch : s2)
{
cout << ch;
}
cout << endl;

那么auto将ch识别为了什么类型呢?我们可以看一下。

可以看到 ch 被识别为 char 类型,中途出现了 iterator,其实这跟范围 for 的底层实现有关,不过这里先不展开说明。
其他成员函数
at 和 operator[] 功能一样,也是根据下标访问字符串内容。至于 front 和 back,根据名字就知道功能了,这里就不再赘述。
Modifiers

operator+=
字符串追加,可以追加一个 string 对象、一个字符,也可以是一个字符串。

cpp
string s1("123456");
s1 += ' ';
s1 += "hello";
string s2("world");
s1 += s2;
cout << s1 << endl;

push_back
用于在字符串末尾插入一个字符的函数。

cpp
string s3("123");
s3.push_back('x');
cout << s3 << endl;

append
append 追加,这是用来追加字符串的函数,重载了多种版本,这里我们挑几个用一下。

cpp
string s4("123456");
string s5("hello");
//追加一个字符,用空格填充
s4.append(1, ' ');
//追加一个string对象
s4.append(s5);
//追加一个字符串
s4.append("world");
cout << s4 << endl;

insert
insert 插入,可以在字符串长度范围内的任意位置插入字符或字符串,它同样重载了很多版本,这里选几个来演示一下。

cpp
string s6("hello world");
//在开头插入字符串
s6.insert(0, "hi,");
//插入两个字符
s6.insert(0, 2,'x');
cout << s6 << endl;

erase
erase 删除,可以选择字符串中的任一位置指定要清除内容的大小,如果不传参默认全清除,效果和 clear 一样。

cpp
string s1("hello world");
cout << s1 << endl;
s1.erase(0, 6);
cout << s1 << endl;
s1.erase();
cout << s1 << endl;

swap
用于交换两个字符串内容的函数。

cpp
string s2("hello world");
string s3("123456");
cout << "old: s2:" << s2 << endl;
cout << " s3:" << s3 << endl;
s2.swap(s3);
cout << "new: s2:" << s2 << endl;
cout << " s3:" << s3 << endl;

String operations

c_str
如果我们同时使用 C 语言和 C++ 编写代码,可能会出现接口不匹配的情况,例如写文件时:
cpp
string s1 = "test.txt";
FILE* ptr = (FILE*)fopen(s1, "w");
fputc('c',ptr);
fclose(ptr);
这里的文件名是 string 类型,而 fopen 中的第一个参数是 const char* 类型,直接传 s1 过去参数类型会不匹配,所以需要用到 c_str。

c_str 就是用来给 C 语言程序提供接口的一个成员函数。

copy
将原字符串的子串拷贝到传入的字符数组中,并且不会在复制过来的子串末尾添加空字符 \0,所以需要我们手动添加,返回的是拷贝过来的子串的长度。

cpp
string s2("123456");
char s[4];
//将s2的6个字符拷贝到s数组里
s2.copy(s, 6);
cout << s << endl;
如果该长度比字符数组大则会破坏数组附近的栈,也就是越界了。

cpp
string s3("hello world");
char s[15];
size_t length = s3.copy(s, 5, 6);
s[length] = '\0';
cout << s << endl;

看得出来很不好用。
find
find 是用于查找某个字符或子串首次出现位置的函数,找到则返回对应下标,没有找到则返回 npos,npos 是 string 类内部的公有成员变量,也就是理论上字符串可达到的最大长度。

一般用的最多的函数版本就是最后一个,用于查找某个字符,没有指定从哪开始查找则从开头开始查找。通常和 substr(查找字符串子串)一起用。
substr
用于查找字符串子串的函数,可以指定子串的起始位置和长度。

cpp
string s4("hello world");
size_t pos = s4.find('w');
if(pos != string::npos)
cout << s4.substr(pos) << endl;

compare
简单来说也就是比较两个 string 对象大小的函数,比较逻辑和 strcmp 一样。

Non-member function overloads

后面 operator<< 实际上一直在使用,重载为全局函数的 swap 版本功能也和成员函数的版本一样,这里就不再赘述。
operator+
operator+ 函数为什么要重载为全局函数呢?因为它有一个重载版本的第一个参数是 const char*。而我们知道如果重载为成员函数,第一个参数就默认为该类类型的 this 指针。

cpp
string s1("hello ");
string s2("world");
string s3 = s1 + s2;
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;

Relational Operators
也就是一些与字符串比较相关的函数(>、<、== 等)。

operator>>
从流中提取字符串的函数,也就是从键盘输入读取字符串。

cpp
string s4;
cin >> s4;
cout << s4 << endl;

getline
当我们使用 cin>> 输入字符串的时候,如果有两个变量,我们通常是以 ' ' 或 '\n' 作为分隔,将不同内容分别输入到两个对象中。
cpp
string s1, s2;
//输入到两个对象中
cin >> s1 >> s2;
cout << s1 << endl;
cout << s2 << endl;


但是如果我们想把 s1 的内容输入为 "hello world",就会出现问题,该如何修改呢?
getline 函数就可以帮我们做到,它可以修改 string 对象读取时的定界标志。
delim 就有定界、分割的意思。如果我们不传,默认是以 ' ' 和 '\n' 来分界,但我们可以修改。

cpp
string s1;
//将s1内容的读取分界线改为'w'
getline(cin, s1, 'w');
cout << s1 << endl;
