C++ 进阶基础之六

大纲

string 容器

string 容器的概念

string 是 STL 的字符串类型,通常用来表示字符串。而在使用 string 之前,字符串通常是用 char* 表示的。stringchar* 都可以用来表示字符串,两者的区别如下:

  • string 是一个类,char* 是一个指向字符的指针
  • string 封装了 char* 来管理字符串,本质是一个 char* 类型的容器
  • string 不用考虑内存释放和越界的问题
  • string 负责管理 char* 所分配的内存。每一次 string 的复制,取值都由 string 类负责维护,不用担心复制越界和取值越界等问题
  • string 提供了一系列的字符串操作函数,例如:查找(find)、拷贝(copy)、删除(erase)、替换(replace)、插入(insert)

stirng 容器的 API

string 的构造函数

  • 默认构造函数:string();
  • 带参数的构造函数:
    • string(const char *s);,用字符串 s 初始化
    • string(int n, char c);,用 n 个字符 c 初始化
  • 拷贝构造函数:string(const string &str);

string 的长度

  • size_t size() const,返回当前字符串的长度,这里的长度不包括字符串的结尾的 \0 字符
  • size_t length() const;,返回当前字符串的长度,这里的长度不包括字符串的结尾的 \0 字符
  • bool empty() const;,判断当前字符串是否为空

值得一提的是,sizeof() 返回的是对象所占用空间的字节数,strlen() 返回的是字符数组中第一个 \0 前的字节数,string 的成员函数 size()length() 没有任何区别。

string 的赋值

  • string &operator=(const string &s);,把字符串 s 赋给当前的字符串
  • string &assign(const char *s);,把字符串 s 赋给当前的字符串
  • string &assign(const char *s, int n);,把字符串 s 的前 n 个字符赋给当前的字符串
  • string &assign(const string &s);,把字符串 s 赋给当前字符串
  • string &assign(int n, char c);,用 n 个字符 c 赋值给当前字符串
  • string &assign(const string &s, int start, int n);,把字符串 s 中从 start 开始的 n 个字符赋值给当前字符串

string 的子串

  • string substr(int pos=0, int n=npos) const;,返回由 pos 位置开始的 n 个字符组成的子字符串

string 的查找

  • int find(char c, int pos=0) const;,从 pos 位置开始查找字符 c 在当前字符串第一次出现的位置
  • int find(const char *s, int pos=0) const;,从 pos 位置开始查找字符串 s 在当前字符串第一次出现的位置
  • int find(const string &s, int pos=0) const;,从 pos 位置开始查找字符串 s 在当前字符串第一次出现的位置
  • int rfind(char c, int pos=npos) const;,从 pos 位置开始查找字符 c 在当前字符串中最后一次出现的位置
  • int rfind(const char *s, int pos=npos) const;,从 pos 位置开始查找字符串 s 在当前字符串中最后一次出现的位置
  • int rfind(const string &s, int pos=npos) const;,从 pos 位置开始查找字符串 s 在当前字符串中最后一次出现的位置

值得一提的是,当 find()rfind() 函数查找不到时,都会返回 -1;两者不同的是 find() 是正向查找,而 rfind() 是逆向查找,但是最终两个函数返回的位置均是字符 / 字符串出现的正向位置;若有重复字符 / 字符串时,则 rfind() 返回的是逆向查找到的字符 / 字符串在正向的位置(即最后一次出现的正向位置)。

string 的替换

  • string &replace(int pos, int n, const char *s);,删除从 pos 位置开始的 n 个字符,然后在 pos 位置插入字符串 s
  • string &replace(int pos, int n, const string &s);,删除从 pos 位置开始的 n 个字符,然后在 pos 位置插入字符串 s
  • void swap(string &s2);,交换当前字符串与字符串 s2 的值

string 的比较

  • int compare(const string &s) const;,与字符串 s 比较
  • int compare(const char *s) const;,与字符串 s 比较

compare() 函数的结果在 > 时返回 1,< 时返回 -1,= 时返回 0。字符串比较区分大小写,比较时参考字典顺序,排越前面的越小。大写的 A(65) 比小写的 a(97) 小。

string 的字符存储

  • char &at(int n);
  • char &operator[] (int n);
  • operator[]at() 均返回当前字符串中的第 n 个字符,但二者是有区别的
    • at() 在越界时会抛出异常,[] 在刚好越界时会返回 (char)0,再继续越界时,程序异常终止
    • 如果程序希望可以通过 try catch 捕获异常,则建议采用 at()

string 的区间插入

  • string &insert(int pos, const char *s);,在 pos 位置插入字符串 s,返回修改后的字符串
  • string &insert(int pos, const string &s);,在 pos 位置插入字符串 s,返回修改后的字符串
  • string &insert(int pos, int n, char c);,在 pos 位置插入 n 个字符 c,返回修改后的字符串

string 的区间删除

  • string &erase(int pos=0, int n=npos);,删除从 pos 位置开始的 n 个字符,返回修改后的字符串

string 的字符串拼接

  • string &operator+=(const string &s);,把字符串 s 连接到当前字符串的结尾
  • string &operator+=(const char *s);,把字符串 s 连接到当前字符串的结尾
  • string &append(const char *s); ,把字符串 s 连接到当前字符串的结尾
  • string &append(const char *s, int n);,把字符串 s 的前 n 个字符连接到当前字符串的结尾
  • string &append(const string &s); ,把字符串 s 连接到当前字符串的结尾
  • string &append(const string &s, int pos, int n);,把字符串 s 中从 pos 位置开始的 n 个字符连接到当前字符串的结尾
  • string &append(int n, char c); ,在当前字符串的结尾添加 n 个字符 c

从 string 取得 char*

  • const char *c_str() const;,返回一个以 \0 结尾的字符串的首地址

值得一提的是,char * 可以隐式转换为 string 类型,反过来则不可以,例如右边这种写法是合法的: char *p = "abc"; string str = p;

将 string 拷贝到 char* 指向的内存空间

  • int copy(char *s, int n, int pos=0) const;

将当前串中以 pos 位置开始的 n 个字符拷贝到以 s 为起始位置的字符数组中,返回实际拷贝的字符数量。特别注意,要保证指针 s 所指向的内存空间足以容纳当前的字符串,不然可能会发生越界。

string 容器的常用操作

  • string 容器的构造与赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

using namespace std;

int main() {
// 默认构造函数
string str1;

// 拷贝构造函数
string str2 = str1;

// 有参构造函数
string str3("abced");
string str4(5, 'f');

// 基本赋值
str1 = "123456";
str2 = str3;
str3.assign("mnopq", 3);
str4.assign("45678", 1, 3); // 从0开始索引,1表示第2个字符
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
cout << "str3 = " << str3 << endl;
cout << "str4 = " << str4 << endl;

return 0;
}

程序运行输出的结果如下:

1
2
3
4
str1 = 123456
str2 = abced
str3 = mno
str4 = 567
  • string 容器的 API 调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>

using namespace std;

int main() {
// 存储字符
string str1 = "abcde";
for (int i = 0; i < str1.size(); i++) {
// 第一种方式
cout << str1[i] << " ";
// 第二种方式
// cout << str1.at(i) << " ";
}
cout << endl;

// 字符串拼接
string str2 = "hello ";
string str3 = "world ";
str2 += str3;
str3.append("where");
cout << "str2 = " << str2 << endl;
cout << "str3 = " << str3 << endl;

// 字符串查找
string str4 = "My name is Peter";
int index1 = str4.find("name");
cout << "index1 = " << index1 << endl;
int index2 = str4.rfind("e");
cout << "index2 = " << index2 << endl;

// 字符串替换
string str5 = "abc123";
str5.replace(3, 3, "def");
cout << "str5 = " << str5 << endl;
string str6 = "123456";
string str7 = "654321";
str6.swap(str7);
cout << "str6 = " << str6 << endl;

// 字符串比较
string str8 = "ABC";
string str9 = "abc";
int result = str8.compare(str9); // 返回值小于等于-1
cout << "result = " << result << endl;

// 截取子字符串
string str10 = "124abc";
string str11 = str10.substr(1, 3);
cout << "str11 = " << str11 << endl;

// 字符串的区间插入
string str12 = "abcdef";
str12.insert(2, "123");
cout << "str12 = " << str12 << endl;

// 字符串的区间删除
string str13 = "123456";
str13.erase(2, 2);
cout << "str13 = " << str13 << endl;

// 从字符串取得 char *
string str14 = "hijkl";
const char *p1 = str14.c_str();
cout << "p1 = " << p1 << endl;

// char * 隐式类型转换为 string
char *p2 = "abc123";
string str15 = p2;
cout << "str15 = " << str15 << endl;

// 将 string 拷贝到 char* 指向的内存空间
char *p3 = new char[3];
string str16 = "hello jim";
int number = str16.copy(p3, 3, 2);
cout << "number = " << number << endl;
cout << "p3 = " << p3 << endl;
delete[] p3;

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a b c d e 
str2 = hello world
str3 = world where
index1 = 3
index2 = 14
str5 = abcdef
str6 = 654321
result = -32
str11 = 24a
str12 = ab123cdef
str13 = 1256
p1 = hijkl
str15 = abc123
number = 3
p3 = llo

pair 的使用

pair 的基本概念

pair 是将 2 个数据组合成一组数据,当有这样的需求时就可以使用 pair,如 STL 中的 map 就是将 key 和 value 放在一起来保存。另一个应用是,当一个函数的返回值需要返回 2 个数据的时候,可以选择 pair。 值得一提的是,pair 的实现是一个结构体,主要的两个成员变量是 firstsecond;因为是使用 struct 不是 class,所以可以直接使用 pair 的成员变量。pair 定义如下:

  • 类模板:template<class T1,class T2> struct pair
  • 参数:T1 是第一个值的数据类型,T2 是第二个值的数据类型
  • 功能:pair 将一对值 (T1 和 T2) 组合成一个值,这一对值可以具有不同的数据类型

pair 的 API 介绍

1
2
3
4
p1 < p2;                   // 两个 pair 对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回 true
p1 == p2; // 如果两个对象的 first 和 second 依次相等,则这两个对象相等,该运算使用元素的 == 操作符
p1.first; // 返回对象 p1 中名为 first 的公有数据成员
p1.second; // 返回对象 p1 中名为 second 的公有数据成员

pair 的构造与赋值

pair 的构造函数

1
2
3
pair<T1, T2> p1;            // 创建一个空的 pair 对象(使用默认构造函数),它的两个元素分别是 T1 和 T2 类型,采用值初始化。
pair<T1, T2> p1(v1, v2); // 创建一个 pair 对象,它的两个元素分别是 T1 和 T2 类型,其中 first 成员初始化为 v1,second 成员初始化为 v2。
make_pair(v1, v2); // 以 v1 和 v2 的值创建一个新的 pair 对象,其元素类型分别是 v1 和 v2 的类型。

pair 包含两个数值,与容器一样,pair 也是一种模板类型,但是又与之前介绍的容器不同。在创建 pair 对象时,必须提供两个类型名,两个对应的类型名的类型可以不相同。

1
2
3
pair<string, string> anon;        // 创建一个空对象 anon,两个元素类型都是 string
pair<string, int> word_count; // 创建一个空对象 word_count, 两个元素类型分别是 string 和 int 类型
pair<string, vector<int> > line; // 创建一个空对象 line,两个元素类型分别是 string 和 vector 类型

当然也可以在定义时进行成员初始化

1
2
3
pair<string, string> author("James", "Joy");    // 创建一个 author 对象,两个元素类型分别为 string 类型,并默认初始值为 James 和 Joy
pair<string, int> name_age("Tom", 18);
pair<string, int> name_age2(name_age); // 拷贝构造初始化

pair 类型的使用相当的繁琐,如果定义多个相同的 pair 类型对象,可以使用 typedef 关键字简化声明

1
2
3
typedef pair<string,string> Author;
Author proust("March", "Proust");
Author Joy("James","Joy");

pair 的赋值操作

pair 变量之间的赋值操作如下:

1
2
3
4
pair<int, double> p1(1, 1.2);
pair<int, double> p2 = p1; // 拷贝构造初始化
pair<int, double> p3;
p3 = p1; // 重载等号操作符

pair 的常用操作

创建新的 pair 对象

可以调用 make_pair() 函数创建新的 pair 对象。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

using namespace std;

int main() {
int age = 18;
string name = "James";
pair<string, int> newone;
newone = make_pair(name, age);
cout << "name: " << newone.first << ", age: " << newone.second << endl;
return 0;
}

程序运行输出的结果如下:

1
name: James, age: 18

访问 pair 的两个元素

可以通过 firstsencond 访问 pair 中的两个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

using namespace std;

int main() {
pair<int, double> p1;
p1.first = 1;
p1.second = 2.5;
cout << p1.first << ' ' << p1.second << endl;

string firstBook;
pair<string, string> author("James", "Joy");
if (author.first == "James" && author.second == "Joy") {
firstBook = "Stephen Hero";
}
cout << firstBook << endl;

return 0;
}

程序运行输出的结果如下:

1
2
1 2.5
Stephen Hero

通过 tie 获取 pair 元素值

可以通过 tie 获取 pair 元素值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <tuple>

using namespace std;

pair<string, int> getPreson() {
return make_pair("Sven", 25);
}

int main() {
int age;
string name;
tie(name, age) = getPreson();
cout << "name: " << name << ", age: " << age << endl;
return 0;
}

程序运行输出的结果如下:

1
name: Sven, age: 25

参考资料