news 2026/9/14 22:01:19

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 / sscanf

  • printf("%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 / strcmp

  • strlen(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更大则返回正值。常见误区:不要认为返回值只有01-1三种——不同平台下的实现返回值都遵循"正负零"语义,但具体数值未必是1-1。因此正确用法是只判断< 0== 0> 0,而不是与1-1直接比较。

字典序的定义参见 basic.md:以第 $i$ 个字符作为第 $i$ 关键字进行大小比较,且空字符小于字符集内任何字符(即a < aa)。strcmp正是按此规则比较的。

2.3 复制:strcpy / strncpy

  • strcpy(char *str, const char *src):把src中的字符(含结尾的空终止符'\0')复制到str中,strsrc均为字符数组头指针,返回值为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 / strrchr

  • strcat(char *str1, const char *str2):把str2接到str1的结尾,用str2的首字符替换掉str1末尾的'\0',返回str1。同样存在缓冲区溢出风险,且要求str1有足够剩余空间。
  • strstr(char *str1, const char *str2):若str2str1的子串,返回str2str1首次出现位置的地址;否则返回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)复制(含'\0'dst目标数组需足够大
strncpy(dst, src, cnt)至多复制cntdstsrc不够长时补'\0';够长时不自动补
strcat(a, b)b接到aa空间需足够
strstr(a, b)子串首次出现地址地址或NULL判断子串包含关系
strchr(s, c)字符首次出现地址地址或NULL按单字符查找
strrchr(s, c)字符最后出现地址地址或NULL从尾部向前找

三、C++ 标准库:std::string 对象操作详解

C++ 标准库操作字符串对象std::string(定义于<string>头文件,本质是std::basic_string<char>的别名),同时也提供对字符数组的兼容。本节结合 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*指针。C++11 之前c_str()保证末尾有空字符而data()不保证;C++11 起两者行为一致。典型用法是交给printf输出:
printf("%s", s); // 不能保证编译通过,行为未定义 printf("%s", s.data()); // C++11 前行为未定义,C++11 后正确输出 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()的复杂度在 C++98 标准中未指定,在C++11 中被指定为常数复杂度,且在常见编译器上即使是 C++98 也是常数。
  • 类型警告:这三个函数(以及后文find)的返回值类型都是size_tunsigned long)。size_t无符号类型,不支持直接与负数比较或运算,需要时请显式强制转换(如(int)s.find('a'))。

3.3 查找:find / rfind

  • find(ch, start = 0):查找并返回start开始(含start)第一次出现字符/字符串ch的位置(从0计数);找不到时返回string::npos(定义为-1,但类型仍是size_t)。注意原文档指出"返回-1"指的是npos的取值,直接以size_t打印会得到巨大的无符号数。
  • rfind(ch):从末尾向前查找,返回第一个找到的位置(从0计数);找不到同样返回string::npos

示例与输出(来自 string.md):

string 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 截取子串:substr

  • substr(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 的第二位开始的最多三个字符构成的子串是 I

3.5 追加:append

  • append(s):将字符串s整体添加到当前字符串末尾。
  • append(s, pos, n):将字符串s中从pos开始的n个字符连接到当前字符串末尾。适用于只取源串一部分追加的场景。

3.6 替换:replace

  • replace(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 删除:erase

  • erase(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 插入:insert

  • insert(pos, s):在pos位置插入字符串s
  • 更通用的形式insert(index, count, ch):在index处连续插入count次字符ch(如上例的s.insert(7, 3, u)即在第 8 位连续插入 3 个'!')。注意index0计数,插入位置即"下标为 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*C++11 后两者一致
长度s.size()/s.length()字符个数,size_tC++11 起常数复杂度
查找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, pos+n)再插入t结果长度可变
删除s.erase(pos, n)删除[pos, pos+n)缺省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取长度,同时把哈希值存进vector<int>,正是"C 速度 + C++ 便利"的典型组合。

5.2 六个最容易踩的坑

  1. strlen写进循环条件:未开 O2 时每次迭代 $\Theta(N)$ 扫描,整体退化为 $\Theta(N^2)$。务必int len = strlen(s);先存变量。
  2. strcmp返回值当±1:标准只保证"负/零/正"三态,不同平台返回的具体数值不同,只与0比较符号。
  3. size_t与负数比较findsize返回size_t,与-1比较或用%d打印都会出错。判空用npos,打印需强转int
  4. strcpy/strcat越界:C 函数不检查容量,目标数组需预先分配足够空间。
  5. strncpy不自动补'\0':当源串长度不小于cnt时,结果串可能没有空字符结尾。
  6. scanf("%s")读不了空格:遇空白即停止;需要读整行时改用std::getline或逐字符读入。

5.3 复杂度速记

  • strlenstrcmpstrcatstrstr朴素实现均为线性或线性相关($O(N)$ 或 $O(NM)$,随实现而异);
  • std::stringsize()/length():C++11 起常数复杂度;
  • find/rfind:实现为线性扫描,$O(N)$;
  • substrreplaceeraseinsert:需复制受影响区间,线性复杂度,频繁操作长串时注意常数开销;
  • std::string的关系运算符比较:$\Theta(N)$。

六、延伸阅读

  • 更完整的字符串术语体系(字符集、子串、后缀、前缀、字典序、回文串、汉明距离)见 docs/string/basic.md;
  • std::string的设计动机、动态扩容与data()/c_str()在 C++11 前后的差异详解见 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 19:50:22

Archon Quick Start:5 分钟跑通你的第一个 AI 编码工作流

Archon Quick Start&#xff1a;5 分钟跑通你的第一个 AI 编码工作流 【免费下载链接】Archon The first open-source harness builder for AI coding. Make AI coding deterministic and repeatable. 项目地址: https://gitcode.com/GitHub_Trending/archon3/Archon 本…

作者头像 李华
网站建设 2026/9/13 19:49:46

Linux内核设备驱动开发核心原理与实战

1. 项目概述&#xff1a;这不是写个hello world就能交差的底层工程 “Linux设备驱动开发”这七个字&#xff0c;听起来像教科书目录里一个不起眼的章节&#xff0c;但在我带过的二十多届嵌入式团队新人里&#xff0c;超过七成的人在真正动手写第一个字符设备驱动前&#xff0c;…

作者头像 李华
网站建设 2026/9/13 19:48:29

方框不再来:用PDF补丁丁完成PDF字体嵌入

方框不再来&#xff1a;用PDF补丁丁完成PDF字体嵌入 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址: https://gitcode.com/Git…

作者头像 李华