- admin 的博客
信息学竞赛:字符数组 vs string 类 完全指南
- @ 2026-8-31 16:44:37
信息学竞赛:字符数组 vs string 类 完全指南
在信息学竞赛(CSP-J/S, NOIP, NOI)中,字符串处理是必考内容。C++ 提供了两种主要的字符串处理方式:C 风格字符数组 和 C++ std::string 类。
一、字符数组 (C-style String)
字符数组是 C 语言遗留下来的处理方式,本质是 char 类型的数组,以空字符 \0 (ASCII 码为 0) 作为结束标志。
1. 定义与初始化
char s1[105]; // 定义长度为 105 的字符数组
char s2[] = "Hello"; // 自动推断长度 (实际为 6,包含 '\0')
char s3[105] = "Hello"; // 初始化
2. 输入与输出
- 不含空格:
cin >> s1; // 遇到空格、换行、Tab 停止 cout << s1 << endl; scanf("%s", s1); // 注意:不需要取地址符 & printf("%s", s1); - 含空格(读取整行):
注意:cin.getline(s1, 105); // 读取一行,最多读 104 个字符,自动补 '\0' // 或者 C 风格 fgets(s1, 105, stdin); // 读取一行,会保留末尾的 '\n'gets(s1)因不安全已在 C++11 中被废弃,竞赛中严禁使用。
3. 常用函数 (<cstring> 头文件)
| 函数 | 功能 | 示例 | 注意事项 |
|---|---|---|---|
strlen(s) |
求长度 | int len = strlen(s1); |
不包含 \0 |
strcpy(s1, s2) |
复制 | strcpy(s1, s2); |
将 s2 复制到 s1,s1 空间需足够 |
strcat(s1, s2) |
连接 | strcat(s1, s2); |
将 s2 拼接到 s1 后,s1 空间需足够 |
strcmp(s1, s2) |
比较 | if(strcmp(s1, s2)==0) |
相等返回 0,s1>s2 返回正数 |
sprintf |
格式化写入 | sprintf(s1, "%d", 123); |
将数字转为字符串存入 s1 |
sscanf |
格式化读取 | sscanf(s1, "%d", &n); |
从字符串读取数字 |
二、string 类 (C++ String)
std::string 是 C++ 标准库提供的字符串类,封装了动态内存管理,使用更安全、方便。需包含 <string> 头文件。
1. 定义与初始化
string s1; // 空字符串
string s2 = "Hello"; // 直接赋值
string s3(5, 'a'); // "aaaaa"
string s4 = s2; // 拷贝构造
2. 输入与输出
- 不含空格:
cin >> s1; cout << s1 << endl; - 含空格(读取整行):
注意:若前面有getline(cin, s1); // 读取整行,遇到换行符停止,不保留换行符cin >> n,需先用getchar()吃掉换行符,再getline。
3. 常用成员函数
| 函数 | 功能 | 示例 | 注意事项 |
|---|---|---|---|
s.length() / s.size() |
求长度 | int len = s1.length(); |
返回 size_t (无符号整型) |
s1 + s2 |
连接 | string s3 = s1 + s2; |
支持 string + char / string + string |
s1 == s2 |
比较 | if(s1 == s2) |
支持 >, <, >=, <= |
s.substr(pos, len) |
截取子串 | s1.substr(1, 3); |
从下标 1 开始,长度 3 |
s.find(str) |
查找 | s1.find("abc"); |
找不到返回 string::npos |
s.insert(pos, str) |
插入 | s1.insert(1, "X"); |
在下标 1 处插入 |
s.erase(pos, len) |
删除 | s1.erase(1, 2); |
从下标 1 开始删 2 个 |
s.replace(...) |
替换 | s1.replace(1, 2, "X"); |
从下标 1 开始,长度 2 替换为 "X" |
stoi(s) / to_string(n) |
数值互转 | int n = stoi(s1); |
C++11 特性,需 <string> |
三、核心对比总结
| 特性 | 字符数组 (char[]) |
string 类 |
|---|---|---|
| 所属 | C 语言风格 | C++ 标准库类 |
| 内存管理 | 静态/手动,需预判最大长度 | 动态自动管理 |
| 结束标志 | 必须有 \0 |
内部维护长度,无需 \0 |
| 赋值 | strcpy (不能直接用 =) |
可以直接 = |
| 连接 | strcat (需确保空间) |
+ 或 += |
| 比较 | strcmp (不能直接用 >) |
可直接用 ==, > 等 |
| 效率 | 略高 (无动态分配开销) | 略低 (但现代编译器优化后差距很小) |
| 安全性 | 低 (易溢出、忘加 \0) |
高 (自动扩容、边界检查) |
| 适用场景 | 极端性能要求、卡内存、C 接口交互 | 绝大多数竞赛题目 |
四、信奥赛实战技巧与避坑
1. 什么时候用哪个?
- 首选
string:95% 的题目推荐使用string,因为写起来快,不易出错(如赋值、比较、拼接)。 - 使用字符数组:
- 题目内存限制极其严格(如 2MB)。
- 需要与 C 风格 API 交互(如
fopen,scanf大量读入)。 - 涉及极其频繁的字符修改且
string超时(string的insert/erase是 的)。
2. 常见坑点
getline的换行符残留:int n; string s; cin >> n; // getchar(); // 必须吃掉 cin 留下的换行符,否则 getline 会读入空串 getline(cin, s);string的下标越界:s[i]不会自动检查越界,访问s[s.length()]是未定义行为(虽然通常是\0),访问更大下标会 RTE。find的返回值:string::npos是一个很大的无符号数(通常是 -1 的补码)。if (s.find("a") != string::npos) { ... } // 正确 if (s.find("a") != -1) { ... } // 警告:有符号与无符号比较- 字符数组的空间:
定义
char s[100]最多只能存 99 个可见字符,第 100 位必须是\0。若题目说字符串长度 100,数组要开 105。
3. 高效互转技巧
string转char[]:string s = "hello"; const char* c = s.c_str(); // 获取 C 风格字符串指针char[]转string:char c[] = "hello"; string s = c; // 直接赋值 string s2(c); // 构造函数- 数字转字符串 (C++11):
int n = 123; string s = to_string(n); // "123" - 字符串转数字 (C++11):
string s = "123"; int n = stoi(s); long long ln = stoll(s);
4. 性能优化建议
如果题目数据量达到 级别,且涉及大量字符串拼接:
- 避免:
s = s + "a"(每次可能重新分配内存) - 推荐:
s += "a"(通常有预留容量,效率更高) - 推荐:
string的reserve()预分配空间。string s; s.reserve(100000); // 预分配 10 万字符空间,避免多次扩容
总结:在 CSP-J/S 及 NOIP 比赛中,无脑用 string 通常是最佳策略,除非你明确遇到了 TLE 或 MLE 且确定是字符串操作引起的。