[技术] OIer的C++标准库 : 字符串库
引入
上次我在博客里介绍了OI中可能用到的STL中的功能, 今天我们接着来发掘C++标准库中能为OI所用的部分.
众所周知, OI中经常用到字符串相关的处理, 这时善用字符串库可以使一些操作更加简洁易懂并减少手打代码量与错误概率, 特别是在一些对效率要求不太严格的应用或者随机数据的应用下.
字符串库
C++的字符串库的内容主要定义在头文件 <string> 中, (注意要和 <cstring> 和 <string.h> 区分开, 前者是C++字符串库, 后面的两个都是用来操作C风格字符串(C-Style String, C++中似乎还叫做空终止字符串(Null-terminated String), 本质上是一个以空字符为结尾标志的char类型的数组)的库), 还有一些字符分类与本地化函数分布在 <cctype> <cstring> <cstdlib> <cwchar> <cuchar> <cwctype> 中. 空终止字符串还分为空终止单字节字符串/空终止宽字符串/空终止多字节字符串. 由于OI中处理的字符多半都是ASCII字符, 宽字符和多字节字符的使用极少(除非是各种OJ上的毒瘤出题人出的奇葩毒瘤题), 本文在空终止字符串的部分中仅对空终止单字节字符串的有关操作进行说明.
由上述头文件所共同定义的C++字符串库定义了两种(在C++17前)字符串通用类型: std::basic_string 和空终止字符串. (C++17新增了 std::basic_string_view 类, 可以以轻量且对数据无所有权的只读方式访问字符串的子序列. 但是OI里似乎没什么用而且OI连C++11的支持都是问题(笑))
<string>与std::basic_string
<string> 头文件定义了 std::basic_string 类型, 提供对于字符序列的各种操作, 并使用 typedef 定义了一些常用类型的特化版本. 列表如下:
特化类型 | 原类型 |
std::string | std::basic_string<char> |
std::wstring | std::basic_string<wchar> |
std::u16string | std::basic_string<char16_t> |
std::u32string | std::basic_string<char32_t> |
其中 std::u16string 与 std::u32string 为C++11及以后所定义的特化类型.
std::basic_string 像 std::vector 一样连续存储保存在其中的数据. 模板参数如下:
template< class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT> > class basic_string;
第一个必须指定的模板参数为字符类型, 第二个参数指定字符串的容器, 第三个参数指定内存分配方式. 其中如果要指定第二个参数的话必须保证二者所指名的类型为同一类, 否则会UB. OI里会用到的估计也就是特化类型 std::string 了OwO
std::basic_string的成员函数
成员函数均可在basic_string对象上使用 . 运算符来调用
operator=
用于支持字符串间的赋值. (数组不是一等公民系列23333)
at 和 operator[]
访问字符串中的成员字符. 前者提供下标越界检查并在下标越界时抛出 std::out_of_range 异常, 后者提供像普通字符数组一样的访问方式, 然而并不检查下标OwO
data
返回指向第一个字符的指针. 可以用这个指针来进行C风格的修改操作, 或者其他需要用到可更改的char指针的应用. (一般没有必须这样的操作因为 std::basic_string 的接口已经很完备了OwO)
c_str
返回一个不可用于修改字符值的指向第一个字符的指针, 用于将 std::basic_string 传递给标准库中需要C风格字符串的地方(多为原来C标准库的内容) 比如 std::system 啥的
begin 与 cbegin
返回一个指向第一个字符的迭代器. 关于迭代器可以见我上一篇关于C++标准库的博文. 不同的是前者返回的迭代器是可变迭代器而后者为常迭代器.
迭代器满足随机访问迭代器的要求.
这是C++11及以后支持的特性.
end 与 cend
返回指向最后一个字符的下一个位置的迭代器. 这两个函数仍然是可变迭代器与常迭代器的区别.
这是C++11及以后支持的特性.
rbegin 与 crbegin
返回指向字符串最后一个字符的反向迭代器. 后者为常迭代器.
这是C++11及以后支持的特性.
empty
与STL容器类似, 返回字符串是否为空.
size 与 length
返回字符串的长度. 二者(似乎)并没有什么区别(除了函数名的长度)
clear
良心操作, 清空字符串OwO (谜一样的容器适配器)
insert
将一个字符/一个basic_string/一个字符数组(C风格字符串/空终止字符串)插入指定位置. 第一个参数为插入位置, 第二个参数为要插入的东西.
对于字符来说第二个参数为要插入的数量, 第三个参数为要插入的字符
对于basic_string来说可以选择第三/四个参数来指定一个子串, 分别指定开始插入的位置与要插入的子串长度.
对于空终止字符串来说可以选择第三个参数来指定要插入的长度.
erase
可以指定两个整数参数, $index$ 和 $count$ ,表示从下标 $index$ 开始删除 $count$ 个字符.
或者删除一个迭代器指向的一个字符.
也可以通过两个迭代器在字符串内划分一个左闭右开区间来将其从字符串中删除.
push_back
向字符串末尾添加一个字符. 时间复杂度 $O(1)$
operator+=
将一个字符/一个basic_string/一个空终止字符串附到字符串末尾.
compare
比较两个字符串.
1. 与另一个basic_string比较
2. 先接受两个参数指定要比较以某个下标开始的若干字符, 用指定的字符得到的子串与第三个参数(basic_string)比较
3. 先指定要比较的子串, 然后指定要比较的basic_string, 然后两个参数用于在要比较的basic_string中指定一个子串, 比较划分出的两个子串.
4. 与另一个空终止字符串比较
5. 划定一个子串与另一个空终止字符串比较
6. 接受两个参数划定一个子串用于与另一个空终止字符串的前若干字符比较.
如果该字符串的字典序小于要比较的字符串则该函数返回一个小于零的值
如果比较结果为相等则返回零.
如果该字符串的字典序大于要比较的字符串则该函数返回一个大于零的值.
replace
将字符串的一个子串替换成指定的字符串. 划分子串可以用起始下标+长度划分, 也可以用一对迭代器划分.
这个指定的字符串可以是basic_string, 也可以是basic_string后跟着两个用于划分子串的参数(将字符串的子串替换成指定字符串的子串), 或者是空终止字符串, 空终止字符串可以配合长度数据来截取.
substr
通过起始下标与长度来截取一个子串, 返回这个截得的子串.
find , rfind , find_first_of , find_first_not_of , find_last_of , find_last_not_of
在字符串中按照各自的规则查找给定的模式串, 成功则返回对应下标, 否则返回 std::basic_string::npos
以上六个查找函数的参数格式都一样, 支持匹配basic_string格式的字符串/空终止字符串/一个字符, 且都支持指定要查找的模式串的要匹配的长度/区间.
以上函数的查找规则如下表:
函数 | 匹配规则 |
find | 在字符串中寻找字符(串) |
rfind | 寻找字符(串)最后一次出现的位置 |
find_first_of | 寻找字符串中任意字符第一次出现的位置 |
find_first_not_of | 寻找字符串中任意字符第一次缺失的位置 |
find_last_of | 寻找字符串中任意字符最后一次出现的位置 |
find_last_not_of | 寻找字符串中任意字符最后一次缺失的位置 |
需要注意的是这里使用的是朴素匹配算法, 在随机数据中表现出色, 但是可能被特殊数据卡掉.
std::basic_string的静态成员常量
npos
一个特殊值, 具体含义根据上下文可以解释为查找失败/字符串的结束等等
访问方式为直接使用 std::basic_string::npos
std::basic_string的友元函数
operator+
可以用于连接一个std::basic_string和另一个std::basic_string/另一个空终止字符串/另一个字符. 就像平常理解的加号一样OwO
六种大小比较运算符 != == < <= > >=
按字典序比较两个basic_string的内容.
<cctype>与<cwctype>
<cctype> 和 <cwctype> 分别针对 char 类型和 wchar_t 类型定义了一系列字符分类与字符操作函数. 字符分类函数可以判断字符是否属于某类型, 比如数字/字母/特殊符号/可见字符/控制字符等等. 字符操作函数可以给字符转一下大小写啥的OwO
字符分类函数
字符分类函数有一大坨来着....懒癌发作直接列表(逃
函数 | 功能 |
isalnum iswalnum | 检查字符是否是字母或数字 |
isalpha iswalpha | 检查字符是否是字母 |
islower iswlower | 检查字符是否是小写字母 |
isupper iswupper | 检查字符是否是大写字母 |
isdigit iswdigit | 检查字符是否是数字 |
isxdigit iswxdigit | 检查字符是否是十六进制数字 |
iscntrl iswcntrl | 检查字符是否是控制字符 |
isgraph iswgraph | 检查字符是否是可见字符 |
isspace iswspace | 检查字符是否是空白符 |
isprint iswprint | 检查字符是否是可打印字符 |
ispunct iswpunct | 检查字符是否是标点符号 |
isblank iswblank | 检查字符是否是空格或Tab |
其中 isblank 是C++11及以后支持的函数.
这些函数对于各种字符的返回值结果如下表:
字符操作函数
用于对字符进行一些转换(OI里主要就是转大小写啥的OwO不过C++也提供了比较强大的本地化库, 可以进行一些如平假名片假名转换之类的操作)
toupper 和 towupper
将字符转化为大写. 前者用于 char , 后者用于 wchar_t
tolower 和 towlower
将字符转化为小写. 前者用于 char , 后者用于 wchar_t
<cstring>与空终止字符串
<cstring> 库原来是C库中的 <string.h> , 提供了操作现在被叫做空终止字符串的字符数组的各种函数.
空终止字符串由于本质上是数组, 然而有句话说得好: C++中数组不是一等公民. 对数组很多操作都不能用非常简洁的语句实现, 而是需要大坨大坨的循环.
所以为了在一定程度上弥补这些缺陷, C++中提供了一些空终止字符串的操作函数来让C++使用者的语句尽量简洁. 其中主要包括字符串操作函数, 字符串检查函数与字符数组操作函数.
字符串操作函数
用于对字符串中的数据进行一定批量修改的函数. OI里用得到的主要是复制和连接两种操作.
strcpy
将一个字符串复制到另一个字符串.
第一个参数为复制目标, 第二个参数为复制源. 二者类型都为 char* (复制源参数还加了 const 限定符). 自动复制到空字符为止. 如果没有空字符或者字符串重叠的话产生UB.
strncpy
将一个字符串中的前若干字符复制到另一个字符串.
第一个参数为复制目标, 第二个参数为复制源. 参数类型同上. 第三个参数指定要复制的字符个数.
如果要复制的字符个数大于源串的长度(复制计数未达到指定值就读取到空字符), 则复制出的字符串并不是空终止字符串, 并且会在复制目标的空字符后补上一系列空字符直到计数达到指定值.
如果复制目标的下标越界了就UB了OwO
strcat
连接两个字符串.
将第二个参数指向的字符串复制到第一个参数指向的字符串后面.
复制过程中第一个串空间不足产生下标越界则造成UB.
strncat
strcat 的可指定复制字符数的版本. 作用与其基本一致.
前两个参数与 strcat 相同, 第三个参数指定要复制的字符数量. 一样是提前终止补空字, 下标越界产UB.
字符串检查函数
用于扫描一个或多个空终止字符串来获取一定信息的函数, 包括长度计数/模式匹配/字典序比较等操作. (在basic_string里都是封装好的w)
strlen
扫描一遍字符串并返回字符串的长度. 遇到的第一个空字符则判定为字符串结束.
如果这个字符串里没有空字符的话....恭喜你又UB了OwO
strcmp
按照字典序比较两个字符串. 接受两个参数, 分别指向要比较的两个字符串.
第一个串字典序小于第二个串返回一个负整数, 两个串相等返回 $0$ , 第一个串字典序大于第二个串则返回一个正整数.
如果两个参数不是指向空终止字符串的指针的话还是UB(突然感觉C库UB好多啊OwO)
strncmp
strcmp 的计数版本OwO, 多出来的第三个参数指定最多匹配多少个字符(也就是说如果一个字符串提前终止的话比较结束).
两个参数不是指向空终止字符串的话UB.
strchr
查找在一个字符串中某个字符第一次出现的位置.
第一个参数指向要进行查找的字符串, 第二个参数为要查找的字符.
返回指向给定字符第一次出现位置的指针. 如果未找到的话返回空指针.
strrchr
strchr 的逆序版本, 查找最后一次出现的位置. 参数与返回值与其相同.(日常懒癌发作)
strpbrk
查找字符串中的任意一个字符在另一个字符串中第一次出现的位置. 返回指向找到的位置的指针. 未找到返回空指针.
第一个参数指向要查找的字符串, 第二个参数指向包含要查找的字符的字符串.
strstr
在一个字符串中查找另一个模式串. 或者说检查一个字符串是否是另一个字符串的子串. 返回指向找到的子串的第一个字符的指针.
第一个参数指向要扫描的串, 第二个参数为要匹配的子串.
未找到则返回NULL.
字符数组操作函数
用于对整个字符数组进行各种设置, OI里拿来初始化普通数组也很方便OwO
memchr
在字符数组里查找指定字符第一次出现的位置.
memcmp
按字典序比较两个数组的内容.
前两个参数分别指向要比较的数组, 第三个参数指定要比较的字节数量.
第一个数组字典序较小则返回负整数, 相等返回 $0$ , 第一个字典序较大则返回正整数.
memset
将一个数组的每一个字节字节设置为一个指定的整数.
第一个参数指向要重设的数组, 第二个参数指定要重设的值($-128$ 到 $127$, 或者 $0x80$ 到 $0x7F$), 第三个参数指定要重置的字节数.
一般重设值设为为 $0x80$ 为重置为无穷小, $0x7F$ 为重置为无穷大, $0x3F$ 也可以重置为无穷大但是保证两个无穷大加起来不会炸边界. 相应的加和防炸的无穷小初始化值为 $0x40$.
memcpy
将一个数组的内容整个复制到另一个数组的位置.
第一个参数指定复制目标, 第二个参数指定复制源, 第三个参数指定要复制的字节数量.
结语
C++的字符串库给我们提供了进行各种字符串操作的简便方法. 虽然查找算法多数为朴素算法, 在实际OI应用中的字符串处理题目中可能被出题人的数据卡掉, 但是平常造数据或者在一些主要算法并非字符串算法等对字符串操作复杂度要求不高的题目/情况下应用很方便. 而且字符串库还顺便给我们提供了操作普通数组的库函数(实际上后面操作字符数组的函数都可以用于普通数组, 但是如果是比较字典序的话要注意数据存储的大小端). 所以善用C++标准库是也是OIer的一个重要技能.
表示后续可能还会更新输入输出部分的奇技淫巧或者其他标准库中的实用工具OwO
(厚脸皮求推荐qwq)
(技术博就不放图包了qwq有需要请去本人其他博文自取(雾))
本博客已弃用, 新个人主页: https://rvalue.moe, 新博客: https://blog.rvalue.moe