ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OI-Wiki 字符串标准库函数全解析:C 字符数组函数与 C++ std::string 常用操作速查

OI-Wiki 字符串标准库函数全解析:C 字符数组函数与 C++ std::string 常用操作速查 OI-Wiki 字符串标准库函数全解析C 字符数组函数与 C std::string 常用操作速查【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki导读在 OI / ICPC 竞赛与日常算法开发中字符串处理是最基础也最高频的操作之一。本篇文章以 OI-Wiki 的 lib-func.md 为主体系统梳理 C 标准库string.h/cstring中操作字符数组的核心函数以及 C 标准库std::string的常用成员函数并结合仓库内 string 类文档、字符串基础定义 与 哈希示例源码 进行原理级扩充。读完本文你将能够快速查阅每个字符串函数的签名、行为边界与复杂度避开strlen进循环、size_t与负数比较、strcmp返回值误解等经典坑在竞赛代码中正确选择字符数组或std::string完成读入、拼接、查找、截取、替换与删除。一、两种字符串表示字符数组与 string 类在进入函数细节之前先明确本文操作的两类对象。OI-Wiki 的 字符串基础篇 给出了规范定义字符串是将 $n$ 个字符顺次排列形成的序列$n$ 称为字符串长度字符串的存储方式有三种用char数组存储并以空字符\0结尾C 风格字符串、使用 C 标准库std::string类、以及用双引号括起来的字符串字面量。C 标准库函数面向的是字符数组char[]/const char*C 标准库std::string面向的是字符串对象同时向下兼容字符数组。两者互补竞赛中经常混用例如用s.c_str()把std::string交给printf输出。二、C 标准库字符数组操作函数详解C 标准库操作字符数组char[]/const char*对应头文件为 C 语言的string.h或 C 中的cstring。以下是 OI-Wiki 列出的全部常用函数。2.1 格式化输入输出printf / scanf / sprintf / sscanfprintf(%s, s)用%s输出一个字符串字符数组。注意传入的是数组首地址输出到第一个\0为止。scanf(%s, s)用%s读入一个字符串字符数组。scanf以空白字符空格、换行、制表符为分隔符因此它无法读入含空格的字符串若需要读入一行含空格的文本应改用gets或 C 的std::getline。仓库中大量示例代码采用std::cin读入后再处理例如 prefix-sum_1.cpp 中std::cin a[i]的常规用法。sscanf(const char *__source, const char *__format, ...)从字符串__source中按格式读取变量。例如sscanf(str, %d, a)会把字符串str解析出的整数写入a。这在解析数字以字符串形式给出的题目时非常实用。sprintf(char *__stream, const char *__format, ...)将格式化内容输出到字符数组__stream中。例如sprintf(str, %d, i)把整数i转成字符串存入str。使用时要确保目标数组容量足够否则会产生缓冲区溢出C11 提供了更安全的snprintf可指定写入上限。2.2 长度与比较strlen / strcmpstrlen(const char *str)返回从str[0]开始直到\0为止的字符数。关键注意点未开启 O2 优化时如果把它写在循环条件中如for (int i 0; i strlen(s); i)每次迭代都会重新调用strlen扫描整串使整体复杂度退化为 $\Theta(N^2)$而strlen本身的复杂度是 $\Theta(N)$因此应提前将长度存入变量。OI-Wiki 在 hash_1.cpp 中正是先int len strlen(str);再进入循环就是这一原则的实践。strcmp(const char *str1, const char *str2)按照字典序比较两个字符串若str1字典序更小返回负值两者相同返回0str1更大则返回正值。常见误区不要认为返回值只有0、1、-1三种——不同平台下的实现返回值都遵循正负零语义但具体数值未必是1或-1。因此正确用法是只判断 0、 0、 0而不是与1、-1直接比较。字典序的定义参见 basic.md以第 $i$ 个字符作为第 $i$ 关键字进行大小比较且空字符小于字符集内任何字符即a aa。strcmp正是按此规则比较的。2.3 复制strcpy / strncpystrcpy(char *str, const char *src)把src中的字符含结尾的空终止符\0复制到str中str与src均为字符数组头指针返回值为str。安全提示strcpy不检查目标数组容量目标空间不足时会越界写竞赛数据规模大或字符串长时尤其要注意预分配足够大的数组。strncpy(char *str, const char *src, int cnt)至多复制cnt个字符到str中。若src提前终止而数量未达cnt则继续向str写入空字符直到总共写入cnt个字符。注意当src长度恰好不小于cnt时strncpy不会自动补\0此时str可能不是以空字符结尾的合法 C 字符串使用printf(%s)前需手动在str[cnt]处置\0。2.4 拼接与查找strcat / strstr / strchr / strrchrstrcat(char *str1, const char *str2)把str2接到str1的结尾用str2的首字符替换掉str1末尾的\0返回str1。同样存在缓冲区溢出风险且要求str1有足够剩余空间。strstr(char *str1, const char *str2)若str2是str1的子串返回str2在str1中首次出现位置的地址否则返回NULL。常用于判断包含关系与定位子串起点。strchr(const char *str, int c)返回字符c在字符串str中第一次出现的位置地址找不到则返回NULL。strrchr(const char *str, int c)返回字符c在字符串str中最后一次出现的位置地址找不到则返回NULL。实现提示从源码结构看这些函数都依赖\0作为终止标记因此传入的字符数组必须保证以空字符结尾否则会越界扫描。2.5 C 函数速查总表函数功能返回值关键注意点printf(%s, s)输出字符串输出字符数以\0为结尾scanf(%s, s)读入字符串成功读入项数遇空白停止不能读含空格串sscanf(src, fmt, ...)从字符串解析变量成功解析项数与fscanf行为一致数据源为内存字符串sprintf(buf, fmt, ...)格式化写入字符数组写入字符数注意缓冲区容量strlen(s)求长度size_t循环条件中反复调用会退化为 $\Theta(N^2)$strcmp(a, b)字典序比较负/零/正返回值不一定是±1只判断符号strcpy(dst, src)复制含\0dst目标数组需足够大strncpy(dst, src, cnt)至多复制cnt个dstsrc不够长时补\0够长时不自动补strcat(a, b)把b接到a尾a空间需足够strstr(a, b)子串首次出现地址地址或NULL判断子串包含关系strchr(s, c)字符首次出现地址地址或NULL按单字符查找strrchr(s, c)字符最后出现地址地址或NULL从尾部向前找三、C 标准库std::string 对象操作详解C 标准库操作字符串对象std::string定义于string头文件本质是std::basic_stringchar的别名同时也提供对字符数组的兼容。本节结合 string 类文档 中的示例与输出逐项展开。3.1 拼接、赋值与访问重载加法运算符当两边是string/char/char[]/const char*类型时将两个变量连接返回拼接后的新字符串。这是std::string相比字符数组最大的便利——C 风格拼接必须手动保证空间并调用strcat。赋值运算符右侧可以是const string/string/const char*/char*。string支持动态分配空间因此无需像字符数组那样预知字符串长度对应代价是std::cin输入string的速度比scanf慢可用cin.tie(nullptr)-sync_with_stdio(false)加速见 hash_1.cpp。访问运算符[cur]返回cur位置的引用因此可以读写如s[0] A。访问函数data()/c_str()返回一个指向相同内容的const char*指针。C11 之前c_str()保证末尾有空字符而data()不保证C11 起两者行为一致。典型用法是交给printf输出printf(%s, s); // 不能保证编译通过行为未定义 printf(%s, s.data()); // C11 前行为未定义C11 后正确输出 printf(%s, s.c_str()); // 一定能够正确输出3.2 容量与长度size / length容量函数size()返回字符串的字符个数同义的还有length()。printf(s 的长度为 %zu, s.size()); printf(s 的长度为 %zu, s.length()); printf(s 的长度为 %zu, strlen(s.c_str()));复杂度strlen(s.c_str())一定是与字符串长度线性相关的size()和length()的复杂度在 C98 标准中未指定在C11 中被指定为常数复杂度且在常见编译器上即使是 C98 也是常数。类型警告这三个函数以及后文find的返回值类型都是size_tunsigned long。size_t是无符号类型不支持直接与负数比较或运算需要时请显式强制转换如(int)s.find(a)。3.3 查找find / rfindfind(ch, start 0)查找并返回从start开始含start第一次出现字符/字符串ch的位置从0计数找不到时返回string::npos定义为-1但类型仍是size_t。注意原文档指出返回-1指的是npos的取值直接以size_t打印会得到巨大的无符号数。rfind(ch)从末尾向前查找返回第一个找到的位置从0计数找不到同样返回string::npos。示例与输出来自 string.mdstring s OI Wiki, t OI, u i; int pos 5; printf(字符 I 在 s 的 %lu 位置第一次出现\n, s.find(I)); printf(字符 a 在 s 的 %lu 位置第一次出现\n, s.find(a)); printf(字符 a 在 s 的 %d 位置第一次出现\n, s.find(a)); printf(字符串 t 在 s 的 %lu 位置第一次出现\n, s.find(t)); printf(在 s 中自 pos 位置起字符串 u 第一次出现在 %lu 位置, s.find(u, pos));字符 I 在 s 的 1 位置第一次出现 字符 a 在 s 的 18446744073709551615 位置第一次出现 // 即为 size_t(-1)具体数值与平台有关 字符 a 在 s 的 -1 位置第一次出现 // 强制转换为 int 类型则正常输出 -1 字符串 t 在 s 的 0 位置第一次出现 在 s 中自 pos 位置起字符串 u 第一次出现在 6 位置实战建议判断是否存在时写if (s.find(x) ! string::npos)而不是if (s.find(x) ! -1)后者在无符号比较下会产生与平台相关的怪异行为。3.4 截取子串substrsubstr(start, len)从start从0计数开始截取长度为len的子串缺省len时截取到字符串末尾若从start开始的后缀长度不足len则截取整个后缀不会越界报错。返回类型是新的string可以用c_str()交给printf。示例与输出string s OI Wiki, t OI; printf(从字符串 s 的第四位开始的最多三个字符构成的子串是 %s\n, s.substr(3, 3).c_str()); printf(从字符串 t 的第二位开始的最多三个字符构成的子串是 %s, t.substr(1, 3).c_str());从字符串 s 的第四位开始的最多三个字符构成的子串是 Wik 从字符串 t 的第二位开始的最多三个字符构成的子串是 I3.5 追加appendappend(s)将字符串s整体添加到当前字符串末尾。append(s, pos, n)将字符串s中从pos开始的n个字符连接到当前字符串末尾。适用于只取源串一部分追加的场景。3.6 替换replacereplace(pos, n, s)删除从pos开始的n个字符然后在pos处插入串s替换总长度不受限制结果可能变长或变短。示例与输出string s OI Wiki; s.replace(2, 5, ); printf(将字符串 s 的第 3~7 位替换为空串后得到的字符串是 %s\n, s.c_str()); s.replace(s.begin(), s.begin() 2, NOI); printf(将字符串 s 的前两位替换为 NOI 后得到的字符串是 %s, s.c_str());将字符串 s 的第 3~7 位替换为空串后得到的字符串是 OI 将字符串 s 的前两位替换为 NOI 后得到的字符串是 NOI除replace(pos, count, str)外std::string还提供迭代器版本replace(first, last, str)用[first, last)区间first含、last不含指定被替换范围上例第二句即使用了迭代器形式。迭代器版本配合begin() k可以精确表达删除前 k 个字符这类需求。3.7 删除eraseerase(pos, n)删除从pos开始的n个字符缺省n时删除pos位置及其之后的所有字符等价于截断。示例与输出string s OI Wiki, t Wiki; char u !; s.erase(2); printf(从字符串 s 的第三位开始删去所有字符后得到的字符串是 %s\n, s.c_str()); s.insert(2, t); printf(在字符串 s 的第三位处插入字符串 t 后得到的字符串是 %s\n, s.c_str()); s.insert(7, 3, u); printf(在字符串 s 的第八位处连续插入 3 次字符串 u 后得到的字符串是 %s, s.c_str());从字符串 s 的第三位开始删去所有字符后得到的字符串是 OI 在字符串 s 的第三位处插入字符串 t 后得到的字符串是 OI Wiki 在字符串 s 的第八位处连续插入 3 次字符串 u 后得到的字符串是 OI Wiki!!!3.8 插入insertinsert(pos, s)在pos位置插入字符串s。更通用的形式insert(index, count, ch)在index处连续插入count次字符ch如上例的s.insert(7, 3, u)即在第 8 位连续插入 3 个!。注意index从0计数插入位置即下标为 index 的字符之前。3.9 比较运算重载的关系运算符std::string重载了全部比较逻辑运算符、、、、、!按字典序比较复杂度为 $\Theta(N)$。这使得string可以直接交给std::sort对若干字符串按字典序排序也可以直接用于std::map/std::set的键。四、std::string 成员速查总表操作形式行为注意点拼接s1 s2连接后返回新string两侧可为string/char/char[]/const char*赋值s x右侧可为string/const char*/char*动态扩容下标访问s[i]返回i位置引用从0计数可读写转 C 指针s.c_str()/s.data()返回const char*C11 后两者一致长度s.size()/s.length()字符个数size_tC11 起常数复杂度查找s.find(x, start 0)从start起首次出现位置找不到返回npos即-1size_t反向查找s.rfind(x)从末尾起首次出现位置找不到返回npos截取s.substr(start, len)返回子串缺省len截到末尾超长自动截断追加s.append(t)/s.append(t, pos, n)末尾追加整串或片段返回*this可链式调用替换s.replace(pos, n, t)删除[pos, posn)再插入t结果长度可变删除s.erase(pos, n)删除[pos, posn)缺省n删到末尾插入s.insert(pos, t)/s.insert(pos, cnt, ch)在pos处插入串或重复字符从0计数比较等字典序比较复杂度 $\Theta(N)$可直接排序五、竞赛实战要点与常见误区5.1 字符数组 vs std::string 的选择追求极致速度、需要自行管理内存的底层场景用char[] C 库函数追求开发效率、字符串长度不确定或需要频繁拼接/查找的场景用std::string两者可以无缝衔接s.c_str()把string交给 C 函数string(str)把字符数组包装成string。仓库示例 hash_1.cpp 同时使用了两者——用char s[L]存原始串、strlen取长度同时把哈希值存进vectorint正是C 速度 C 便利的典型组合。5.2 六个最容易踩的坑strlen写进循环条件未开 O2 时每次迭代 $\Theta(N)$ 扫描整体退化为 $\Theta(N^2)$。务必int len strlen(s);先存变量。strcmp返回值当±1用标准只保证负/零/正三态不同平台返回的具体数值不同只与0比较符号。size_t与负数比较find、size返回size_t与-1比较或用%d打印都会出错。判空用npos打印需强转int。strcpy/strcat越界C 函数不检查容量目标数组需预先分配足够空间。strncpy不自动补\0当源串长度不小于cnt时结果串可能没有空字符结尾。scanf(%s)读不了空格遇空白即停止需要读整行时改用std::getline或逐字符读入。5.3 复杂度速记strlen、strcmp、strcat、strstr朴素实现均为线性或线性相关$O(N)$ 或 $O(NM)$随实现而异std::string的size()/length()C11 起常数复杂度find/rfind实现为线性扫描$O(N)$substr、replace、erase、insert需复制受影响区间线性复杂度频繁操作长串时注意常数开销std::string的关系运算符比较$\Theta(N)$。六、延伸阅读更完整的字符串术语体系字符集、子串、后缀、前缀、字典序、回文串、汉明距离见 docs/string/basic.mdstd::string的设计动机、动态扩容与data()/c_str()在 C11 前后的差异详解见 docs/lang/csl/string.md基于字符数组与哈希实现的完整竞赛示例见 docs/string/code/hash/hash_1.cpp后续进阶算法KMP、AC 自动机、后缀数组、后缀自动机等可继续阅读 docs/string 目录下的对应文档。【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表