文章目录
从本篇开始将开启C++里的STL库专题,网上有句话说:“不懂STL,不要说你会C++
”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发
1.为什么要学习string?什么是string?
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,
不太符合OOP的思想
,而且底层空间需要用户自己管理,稍不留神可能还会越界访问
因此创建了string类,比STL库还要早出现,所以有一定的缺陷和冗余
string的主要特征可总结为:
- 字符串是
表示字符序列的类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
- string在底层实际是:
basic_string
模板类的别名
typedef basic_string<char, char_traits, allocator> string
- 这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然
按照字节
(而不是实际编码的字符
)来操作
2.string类对象的常见构造
string
作为一个类也有构造函数
,析构函数
,=运算符重载
,我们重点介绍构造函数里的功能
函数名 | 功能说明 |
---|---|
string() | 构造空的string 类对象,即空字符串 |
string (const char* s) | 用C-string 来构造string 类对象 |
string (const string& str) | 拷贝构造函数 |
string (const char* s, size_t n) | 从 s 指向的字符数组中复制前 n 个字符 |
string (size_t n, char c) | string 类对象中包含n 个字符c |
string (const string& str, size_t pos, size_t len = npos) | 复制从字符位置 pos 开始并跨越 len 字符的 str 部分(或者直到 str 的末尾,如果 str 太短或 len 为 string::npos) |
string (InputIterator first, InputIterator last) | 按顺序复制范围 [first,last) 中的字符序列 |
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s0("Initial string");
string s1;
string s2(s0);
string s3(s0, 8, 3);
string s4("Hello");
string s5("Best wish!", 4);
string s6a(10, 'x');
string s6b(10, 42); // *的ASCII值是42
string s7(s0.begin(), s0.begin() + 7);
cout << "s1: " << s1 << "\ns2: " << s2 << "\ns3: " << s3;
cout << "\ns4: " << s4 << "\ns5: " << s5 << "\ns6a: " << s6a;
cout << "\ns6b: " << s6b << "\ns7: " << s7 << '\n';
return 0;
}
⌨️代码输出示例:
3.string类对象的容量操作
通常在C语言阶段,想要知道字符串的长度,或者对其大小容量进行操作,都要自己手撕函数,不仅耗时还费力,所以string提供了现成的函数
函数名 | 功能说明 |
---|---|
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度(不常用) |
max_size | 返回的是 string 理论上能够容纳的最大字符数 |
resize | 将有效字符的个数增加或减少 n 个,多出的空间用字符 c 或空格填充,少的截断字符串 |
capacity | 返回空间总大小,即容量 |
reserve | 为字符串增加预留空间,即增加预留容量 |
clear | 移除 string 对象中存储的所有字符 |
empty | 检测字符串释放为空串,是返回 true ,否则返回 false |
shrink_to_fit | 请求 string 对象将其容量缩小到和当前字符串长度相匹配的大小 |
🔥值得注意的是:
size()
与length()
方法底层实现原理完全相同,引入size()
的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
clear()
只是将string
中有效字符清空,即大小size
缩为0
,不改变底层空间大小,即容量capacity
不改变resize(size_t n)
与resize(size_t n, char c)
都是将字符串中有效字符个数改变到n
个,不同的是当字符个数增多时:resize(n)
用空格来填充多出的元素空间,resize(size_t n, char c)
用字符c
来填充多出的元素空间。注意:resize
在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变reserve(size_t res_arg=0)
:为string
预留空间,不改变有效元素个数,当reserve
的参数小于string
的底层空间总大小时,reserver
不会改变容量大小
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("Hello World!");
cout << "size:" << str.size() << endl;
cout << "lenth:" << str.length() << endl;
cout << "max_size:" << str.max_size() << endl;
cout << "capacity:" << str.capacity() << endl;
str.reserve(100);
cout << "reserve:" << str.capacity() << endl;
str.resize(17, '*');
cout << "resize:" << str << endl;
str.shrink_to_fit();
cout << "shrink_to_fit:" << str.capacity() << endl;
str.clear();
cout << "clear:" << str << endl;
cout << "empty:" << str.empty() << endl;
return 0;
}
⌨️代码输出示例:
4.string类对象的迭代器
迭代器(Iterator)
是一种强大的抽象概念,它提供了一种统一的方式来访问和操作容器(如 string
、vector
等)中的元素,简单来说就是提供了另一种遍历修改数据的方法
函数名 | 功能说明 |
---|---|
begin + end | 迭代器:begin 获取开头一个字符 + end 获取最后一个字符下一个位置 |
rbegin + rend | 反向迭代器:rbegin 获取最后一个字符 + end 获取开头一个字符上一个位置 |
cbegin + cend | 和 begin + end 一样,但是常量迭代器只读 |
crbegin + crend | 和 rbegin + rend 一样,但是反向常量迭代器只读 |
🔥值得注意的是: 定义开头变量前的类型,可以用 auto
代替自动推导,避免了很长的类型,比如后续学习 map
的迭代器类型是这样的 std::map< std::string,std::string >::iterator
,就很有必要用 auto
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("Hello World!");
string::iterator it1 = str.begin();
while (it1 != str.end())
{
cout << *it1 << ' ';
it1++;
}
cout << endl;
string::reverse_iterator it2 = str.rbegin();
while (it2 != str.rend())
{
cout << *it2 << ' ';
it2++;
}
cout << endl;
return 0;
}
⌨️代码输出示例:
5.string类对象的元素访问
string
的元素访问提供了能够像数组那样自由访问字符串中的数组的函数
,极大的提高了字符修改的效率
🔥值得注意的是: at
用于访问指定位置元素的成员函数。与 operator[ ]
不同的是,at
会进行边界检查,如果传入的索引超出容器的有效范围,会抛出 std::out_of_range
异常
函数名 | 功能说明 |
---|---|
operator[ ] | 像数组一样,使用方括号语法来访问其内部数据 |
at | 访问指定位置元素 |
back | 返回容器中最后一个元素的引用 |
front | 返回容器中第一个元素的引用 |
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("Hello World!");
cout << "operator[ ]:" << str[11] << endl;
cout << "at:" << str.at(10) << endl;
cout << "back:" << (str.back() = '~') << ' ' << str << endl;
cout << "front:" << (str.front() = 'h') << ' ' << str << endl;
return 0;
}
⌨️代码输出示例:
6.string类对象的元素修改
string
还提供了 一系列像修改链表那样能够修改字符串的函数
函数名 | 功能说明 |
---|---|
operator+= | 字符串后追加字符串 str |
append | 字符串后追加字符串 str |
push_back | 字符串后尾插字符 c |
assign | 将新的内容赋值给字符串 |
insert | 在容器的指定位置插入元素 |
erase | 从容器里移除指定的元素或元素范围 |
replace | 在容器或字符串中替换特定元素或子串 |
swap | 交换两个 string 对象的内容 |
pop_back | 移除 string 中的最后一个元素 |
🔥值得注意的是:
- 在
string
尾部追加字符时,s.push_back(c)
/s.append(1, c)
/s += 'c'
三种的实现方式差不多,一般情况下string
类的+=
操作用的比较多,+=
操作不仅可以连接单个字符,还可以连接字符串 - 对
string
操作时,如果能够大概预估到放多少字符,可以先通过reserve
把空间预留好
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
cout << "operator+=:" << (str += "hello") << endl;
cout << "append:" << str.append(" world") << endl;
str.push_back('!');
cout << "push_back:" << str << endl;
str.assign("new word");
cout << "assign:" << str << endl;
str.insert(8, "!!!");
cout << "insert:" << str << endl;
str.erase(8, 3);
cout << "erase:" << str << endl;
str.replace(0, 3, "fashion");
cout << "replace:" << str << endl;
string tmp("happy day!");
swap(tmp, str);
cout << "swap:" << str << endl;
str.pop_back();
cout << "pop_back:" << str << endl;
return 0;
}
⌨️代码输出示例:
7.string类对象的查找、提取、对比
string
也提供了一些查找、提取、对比的函数
,分配器(allocator)
是标准库中一个重要的组件,它将内存分配和对象构造分离,使得容器的内存管理更加灵活
函数名 | 功能说明 |
---|---|
c_str | 返回 C 格式字符串 |
data | 返回一个指向字符串内部字符数组的指针 |
get_allocator | 获取容器当前使用的分配器实例 |
copy | 将字符串的一部分复制到一个字符数组中 |
find | 在字符串中查找子字符串或字符第一次出现的位置 |
rfind | 在字符串中从后往前查找指定的子字符串或字符 |
find_first_of | 查找字符串中任意参数在另一个字符串中第一次出现的位置 |
find_last_of | 从后往前查找字符串中任意参数在另一个字符串中第一次出现的位置 |
find_first_not_of | 在字符串中查找第一个不在指定字符集中的字符 |
find_last_not_of | 从后往前在字符串中查找第一个不在指定字符集中的字符 |
substr | 从字符串中提取子字符串 |
compare | 对两个字符串进行比较 |
🔥值得注意的是:
c_str()
返回的是一个const char*
类型的指针,这意味着不能通过该指针修改其所指向的字符串内容- 从
C++11
开始,string::data()
和string::c_str()
的行为基本一致,都会返回一个以'\0'
结尾的字符数组指针 - 对于
copy
,目标字符数组必须有足够的空间来容纳要复制的字符,并且需要手动添加字符串结束符以形成有效的C
风格字符串 find
系列没找到就返回npos
- 对于
compare
,逐个比较,如果当前字符串小于比较对象,返回一个负整数;如果当前字符串等于比较对象,返回0
;如果当前字符串大于比较对象,返回一个正整数
💻代码测试示例:
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
int main()
{
string str("Hello World");
const char* str1 = str.c_str();
printf("c_str:%s\n", str1);
const char* str2 = str.data();
printf("data:%s\n", str2);
char buffer[10];
str.copy(buffer, 5, 0);
buffer[5] = '\0';
cout << "copy:" << buffer << endl;
string subStr = "World";
int pos1 = str.find(subStr);
cout << "find:" << pos1 << endl;
int pos2 = str.rfind('h');
cout << "rfind:" << pos2 << endl;
int pos3 = str.find_first_of(subStr);
cout << "find_first_of:" << pos3 << endl;
int pos4 = str.find_last_of(subStr);
cout << "find_last_of:" << pos4 << endl;
int pos5 = str.find_first_not_of(subStr);
cout << "find_first_not_of:" << pos5 << endl;
int pos6 = str.find_last_not_of(subStr);
cout << "find_last_not_of:" << pos6 << endl;
string Substr = str.substr(0, 5);
cout << "substr:" << Substr << endl;
cout << "compare:" << str.compare(subStr);
return 0;
}
⌨️代码输出示例:
8.string类的非成员函数及npos
正是有了非成员函数,才能实现大小交换、自定义交换、输出输入等操作,npos
也提供了一种特殊表达方式
函数名 | 功能说明 |
---|---|
npos | 值为-1,表示查找操作失败或者某个位置不存在 |
operator+ | 将两个字符串拼接 |
relational operators | 大小比较 |
swap | 交换两个 string 对象的内容 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
🔥值得注意的是:
npos
值为-1
,被定义为size_t
类型的最大值。-1
原码为1000 ... 0001
,补码则为1111 ... 1111
,但赋给size_t
时,符号位就用不了了,所以整个补码就为size_t
能表示的最大值表示查找操作失败或者某个位置不存在string
中operator+
尽量少用,因为传值返回,导致效率低- 当使用
>>
读取字符串时,它会在遇到空白字符(如空格、制表符、换行符等)时停止读取;getline
函数会读取输入流中的一行文本,直到遇到换行符为止,也就是遇到空格不会停止
💻代码测试示例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("Hello ");
string str2("World");
cout << "npos:" << str1.npos << endl;
cout << "operator+:" << (str1 + str2) << endl;
cout << "relational operators:" << (str1 < str2) << endl;
swap(str1, str2);
cout << "swap:" << str1 << " " << str2 << endl;
return 0;
}
⌨️代码输出示例: