C++ 核心知识点汇总(字符串)
引言
字符串是C++中处理文本数据的核心工具。C++提供了两种字符串处理方式:C风格字符数组和C++标准库string类。其中string类以其便捷的操作、动态扩容和丰富的成员函数,成为现代C++开发的首选。
一、核心:C风格字符数组 vs C++11 std::string类
信奥赛中优先使用std::string类 ,其便捷的操作能大幅减少代码量、降低出错率,仅在极致性能优化(如大字符串频繁操作)场景下少量使用C风格字符数组。
| 类型 | 底层实现 | 竞赛核心特点 | 适用竞赛场景 | 竞赛注意事项 |
|---|---|---|---|---|
| C风格字符数组 | char[]/char*,以'\0'结尾 |
手动管理内存,依赖<cstring>库函数,执行效率略高 |
1. 题目明确要求使用字符数组;2. 大字符串频繁读写的极致优化 | 1. 必须预留'\0'空间;2. 谨防数组越界(竞赛高频RE原因); |
| C++11 std::string | 封装的动态字符数组,兼容C风格 | 自动内存管理、支持运算符重载、丰富的成员函数、C++11范围for/auto,无需维护'\0' |
99%的竞赛场景,包括模拟、枚举、动态规划、字符串匹配等 | 1. 下标pos == size()为合法访问,返回'\0';2. size()返回无符号整型,避免与负数比较; |
二、C++11 std::string :头文件与初始化
1. 必备头文件与命名空间
竞赛中固定写法,无需额外冗余代码:
cpp
#include <iostream>
#include <string> // 字符串核心头文件,必加
using namespace std; // 竞赛中直接使用,减少代码量
注意:无需额外包含
<cstring>,除非使用C风格字符数组的库函数(如strlen/strcat)。
2. C++11 推荐初始化方式
竞赛中优先选择简洁、高效的初始化方式,避免冗余,以下为考纲要求掌握的写法(按竞赛使用频率排序):
cpp
string s1; // 空字符串,竞赛中最常用(后续动态赋值)
string s2 = "GESP CSP"; // 直接初始化字符串常量,竞赛高频
string s3("123456"); // 构造函数初始化,与s2等价
string s4{7, 'a'}; // C++11列表初始化,生成7个'a':"aaaaaaa"(竞赛模拟题高频)
string s5 = s2; // 拷贝初始化,适用于字符串复制
// C++11移动初始化:竞赛中大字符串拷贝时使用,提升效率
string temp = "long string";
string s6 = move(temp); // 转移资源,无拷贝开销,竞赛性能优化点
避坑:禁止使用
string s = NULL;,C++11中NULL为指针,与字符串类型不匹配,直接编译错误。
三、C++11 std::string :输入与输出
字符串的输入输出是入门必考考点 ,也是高频错题点,需严格区分无空格输入 和带空格输入。
1. 无空格输入:cin >> s
竞赛最常用 ,自动跳过空格、回车、Tab等空白字符,遇到空白字符停止读取,适用于读取单个无空格字符串(如单词、数字串)。
cpp
string s;
cin >> s; // 输入:csp_j 2025
cout << s; // 输出:csp_j(仅读取第一个空白符前的内容)
竞赛真题场景:读取单个单词、数字字符串(如"123456")、竞赛指令码(如"U D L R")。
2. 带空格输入:getline(cin, s)
适用于读取整行带空格的字符串 (如竞赛模拟题中的整行指令、句子),必须处理输入缓冲区残留的换行符(高频错题点)。
正确写法(竞赛标准)
cpp
#include <iostream>
#include <string>
#include <limits> // 必须包含,用于numeric_limits
using namespace std;
int main() {
int n;
cin >> n; // 读取整数后,缓冲区残留换行符
// 清空缓冲区所有字符,直到遇到换行符(竞赛标准写法,避免getline读空串)
cin.ignore(numeric_limits<streamsize>::max(), '\n');
string s;
getline(cin, s); // 输入:CSP-S 2025 Round 1
cout << s; // 输出:CSP-S 2025 Round 1(完整读取整行)
return 0;
}
避坑:若未使用
cin.ignore,getline会直接读取缓冲区的换行符,得到空字符串,导致答案错误(WA)。
3. 进阶:逐个字符输入
适用于需要过滤字符的场景(如读取字符串并过滤非数字/非字母字符):
cpp
string s;
char c;
while (cin >> c) { // 逐个读取字符,自动跳过空白符
if (c >= '0' && c <= '9') { // 仅保留数字字符
s += c;
}
}
cout << s; // 输入:a1b2c3,输出:123
四、C++11 std::string 成员函数(高频)
信奥赛中对string类的考查集中在核心成员函数 ,要求选手熟记函数参数、返回值、使用场景,能快速实现字符串的长度获取、拼接、替换、查找、截取等操作。
1. 长度获取:size() / length()
- 说明 :两者功能完全一致,均返回字符串有效字符数 (不含内部维护的
'\0'),返回值类型为size_t(无符号整型,竞赛中注意与int的转换)。 - 推荐 :
size(),更贴合STL容器命名规范,竞赛中使用频率更高。 - 真题场景:遍历字符串、判断字符串长度是否符合条件。
cpp
string s = "csp";
cout << s.size(); // 输出:3
// 竞赛遍历写法(标准)
for (int i = 0; i < (int)s.size(); i++) { // 强转int,避免无符号整型负数比较坑
cout << s[i] << " ";
}
竞赛避坑:
size()返回size_t(无符号),若直接与负数比较(如i > s.size()-5),会因隐式转换导致逻辑错误,竞赛中统一强转为int。
2. 字符串拼接:+ 运算符 / append()
- 说明 :
+运算符更简洁,竞赛中首选 ;append()适用于批量追加字符,性能略高。 - 场景:字符串拼接、动态生成字符串。
cpp
string s1 = "CSP-", s2 = "S";
string s3 = s1 + s2 + " 2025"; // 首选,简洁
cout << s3; // 输出:CSP-S 2025
s3.append(3, '!'); // 追加3个'!'
cout << s3; // 输出:CSP-S 2025!!!
3. 字符串替换:replace(pos, len, str)
- 函数参数 :
pos(起始替换下标)、len(待替换字符数)、str(替换的新字符串)。 - 真题场景:字符串修改、模拟题中的字符替换(如将指定子串替换为其他字符)。
cpp
// 竞赛真题:将字符串中从下标1开始的5个字符替换为"12345"
string s = "1234@@chenadai";
s.replace(1, 5, "12345");
cout << s; // 输出:112345chenadai
4. 子串查找:find(sub, pos)
- 函数参数 :
sub(待查找的子串/字符)、pos(起始查找下标,默认0)。 - 返回值 :找到则返回子串起始下标 ,找不到返回
string::npos(竞赛必背,代表查找失败,值为-1)。 - 真题场景:字符串分割、判断子串是否存在、查找字符出现位置(竞赛高频)。
cpp
string s = "gesp.ccf.org.cn";
size_t pos = s.find('.'); // 查找第一个'.'的位置
if (pos != string::npos) { // 标准判断:先检查是否找到
cout << pos; // 输出:4
}
pos = s.find('.', pos + 1); // 从下一个位置继续查找
cout << pos; // 输出:8
竞赛必背:
string::npos是查找失败的标志,必须先判断是否找到,再使用返回的下标,否则会因下标越界导致RE。
5. 子串截取:substr(pos, len)
- 函数参数 :
pos(起始截取下标)、len(截取长度,缺省时默认截取至字符串末尾)。 - 真题场景:字符串分割、截取指定位置的子串(如从域名中截取主域名、从数字串中截取指定位数)。
cpp
string s = "gesp.ccf.org.cn";
size_t pos = s.find('.');
string sub = s.substr(0, pos); // 从0开始,截取pos个字符
cout << sub; // 输出:gesp
sub = s.substr(pos + 1); // 从pos+1开始,截取至末尾
cout << sub; // 输出:ccf.org.cn
竞赛注意:若
len超过字符串剩余长度,自动截取至末尾,不会报错,可放心使用。
6. 字符清空:clear() / erase()
clear():清空所有字符,size()变为0,竞赛中最常用。erase(pos, len):删除从pos开始的len个字符,适用于删除指定子串。- 真题场景:多次使用同一个字符串变量、过滤字符串中的指定子串。
cpp
string s = "csp_j_s";
s.erase(3, 2); // 删除从下标3开始的2个字符("_j")
cout << s; // 输出:csp_s
s.clear();
cout << s.size(); // 输出:0
五、核心操作:字符访问与遍历
字符串的遍历和字符访问是基础操作 ,要求选手掌握多种遍历方式,并能根据场景选择最优写法(如修改字符、只读遍历),同时严格规避下标越界问题(竞赛RE高频原因)。
1. 字符访问:operator[] 与 at()(竞赛核心区分)
根据C++11标准,结合竞赛考法,两者的核心区别为竞赛必背内容:
| 访问方式 | 边界检查 | pos == size() |
竞赛场景 | 竞赛优劣势 |
|---|---|---|---|---|
s[pos] |
无 | 合法,返回'\0' |
99%的竞赛场景,包括遍历、字符修改 | 优势:执行效率高、代码简洁;劣势:无边界检查,需手动保证下标合法 |
s.at(pos) |
有 | 抛出std::out_of_range异常 |
调试代码、需要严格边界检查的场景 | 优势:避免越界RE;劣势:执行效率低,竞赛中极少使用 |
结论:优先使用
s[pos],竞赛中通过逻辑保证下标合法(如i < (int)s.size()),无需使用at()(效率低且代码冗余)。
竞赛合法访问示例(贴合C++11标准)
cpp
string s = "hello";
cout << s[0]; // 合法,输出:h
cout << s[4]; // 合法,输出:o
cout << s[5]; // 合法(pos == size()),返回'\0',竞赛中可用于判断字符串结束
// cout << s[6]; // 非法(pos > size()),下标越界
2. 遍历方式
方式1:下标遍历(最常用)
适用于需要修改字符 或需要使用下标的场景(如字符替换、对称判断),标准写法:
cpp
string s = "gEsP";
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
s[i] -= 32; // 小写转大写,竞赛高频操作
}
}
cout << s; // 输出:GESP
方式2:C++11 范围for循环
适用于只读遍历 或简单修改字符的场景,代码简洁,不易出错,竞赛中高频使用:
cpp
string s = "csp2025";
// 只读遍历
for (char c : s) {
if (c >= '0' && c <= '9') {
cout << c;
}
}
// 修改遍历(加引用&),竞赛标准
for (char& c : s) {
c = toupper(c); // 转大写,需包含<cctype>
}
方式3:C++11 迭代器遍历(结合STL算法)
适用于使用STL算法 的场景(如sort/find):
cpp
#include <algorithm> // 必加STL头文件
string s = "bac";
sort(s.begin(), s.end()); // 排序字符串,竞赛高频
cout << s; // 输出:abc
// 迭代器遍历
for (auto it = s.begin(); it != s.end(); it++) {
cout << *it << " ";
}
竞赛必背:
s.begin()返回字符串首字符迭代器,s.end()返回字符串末尾后一个位置的迭代器(不指向有效字符)。
六、高频真题解析(字符串核心)
真题1:下标边界访问与空字符判断(GESP原题)
题目
执行以下代码,输出结果为?
cpp
string ch = "hello";
if (ch[5] == NULL) {
cout << "right" << endl;
} else if (ch[5] == '\0') {
cout << "wrong" << endl;
} else {
cout << "hello" << endl;
}
解析(C++11标准)
ch.size() = 5,ch[5]为pos == size(),合法访问 ,固定返回'\0'(ASCII码=0);NULL在C++11中为整数0,与'\0'隐式转换后相等,第一个条件成立;if-else if为互斥分支,第一个条件满足后,后续分支不执行。
标准答案
right
真题2:小写转大写
题目
将字符串"gEsP is Interesting"中的所有小写字母转为大写,输出结果。
标准写法
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "gEsP is Interesting";
for (char& c : str) { // 引用修改原字符
if (c >= 'a' && c <= 'z') {
c += 'A' - 'a'; // 竞赛核心:ASCII偏移转换,无需库函数
}
}
cout << str; // 输出:GESP IS INTERESTING
return 0;
}
竞赛得分点
- 使用范围for循环(C++11标准);
- 直接通过ASCII偏移转换,不使用
toupper(避免忘记包含<cctype>); - 字符修改加引用
&(否则仅修改临时变量,答案错误)。
真题3:字符串分割(按指定字符分割,CSP-J高频)
题目
将字符串"gesp.ccf.org.cn"按.分割,输出每个子串(竞赛模拟题)。
标准写法(find+substr)
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "gesp.ccf.org.cn";
string delimiter = ".";
size_t pos = 0;
while ((pos = s.find(delimiter)) != string::npos) { // 竞赛标准循环
string token = s.substr(0, pos);
cout << token << endl;
s = s.substr(pos + delimiter.size()); // 截取剩余字符串
}
cout << s; // 输出最后一个子串
return 0;
}
得分点
- 先判断
find的返回值是否为string::npos,再使用下标; - 截取剩余字符串时,跳过分隔符(
pos + delimiter.size()); - 循环结束后输出最后一个子串(无分隔符的部分)。