C++字符串
一、前言:C++字符串相较于C
二、C++字符串基础
2.1 string的本质
string实际上是一个类模版的特化
cpp
typedef basic_string<char> string;内部结构(简化):
- 动态分配的字符数组
- 当前长度(size)
- 容量(capacity)
- 短字符串优化(SSO):短字符串直接存储在对象内部,避免堆分配
2.2 五种创建方式
cpp
#include <iostream>
#include <string>
using namespace std;
int main(){
//1.默认构造 - 空字符串
string s1;
//2.c风格字符串构造
string s2="Hello, World";
string s3("Hello World");
//3.重复字符串构造(听说竞赛常用)
string s4(10,'A'); //"AAAAAAAAAA"
string s5(5,'0'); //"00000",初始化数组常用
//4.拷贝构造
string s6=s2;
//5.字串构造
string s7(s2,6,4); //从s2[6]开始,取四个字符
//6.迭代器范围构造
string s8(s2.begin(),s2.begin()+5); //"Hello"
return 0;
}输入输出
基础输入
cpp
string s;
//方法1:cin(遇到空格/换行停止)
cin>>s; //输入"Hello World"只会读取"Hello"
//方法2:getline(读取整行,包括空格)
getline(cin,s);
//方法3:读取固定量字符
char buffer[100];
cin.read(buffer,10); //读取10个字符(包括空格)
s = string(buffer,10);其他输入技巧
cpp
//技巧1:混合输入数字和字符串
int n;
string line;
cin>>n;
cin.ignore(); //跳过数字后面的换行符
getline(cin,line); //正确读取下一行
//技巧2:读取不确定数量的字符串
vector<string> words;
string word;
while (cin>>word){ //Ctrl+Z/D结束
words.push_back(word);
}输出优化
cpp
//使用'\n'换行会比endl快,因为endl会刷新缓冲区
//大量输出时使用字符串拼接
string result;
for (int i=0;i<10000;i++){
result+=to_string(i)+'\n'; //内存中拼接
}
cout<<result;三、核心操作
3.1 访问与修改
| 方法 | 示例 | 说明 | 时间复杂度 |
|---|---|---|---|
[] | s[i] | 不检查越界 | O(1) |
.at(i) | s.at(i) | 检查越界,越界抛异常 | O(1) |
.front() | s.front() | 首字符 | O(1) |
.back() | s.back() | 尾字符 | O(1) |
cpp
string s = "Hello";
// 遍历(三种方式)
// 1. 下标遍历
for (int i = 0; i < s.length(); i++) {
cout << s[i];
}
// 2. 迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it) {
cout << *it;
}
// 3. 范围for循环(C++11推荐)
for (char c : s) {
cout << c;
}
// 修改字符串
s[0] = 'h'; // 变为"hello"
s.front() = 'H'; // 变回"Hello"3.2 大小与容量
cpp
string s = "Hello";
cout << s.size() << endl; // 5,同length()
cout << s.capacity() << endl; // 当前分配的存储空间
cout << s.empty() << endl; // 是否为空
// 调整大小
s.resize(10, '!'); // "Hello!!!!!",扩展部分用'!'填充
s.resize(3); // "Hel",截断
// 预留空间(性能关键!)
string large;
large.reserve(1000000); // 预分配100万字符空间,避免频繁扩容
for (int i = 0; i < 1000000; i++) {
large.push_back('a'); // 不会触发重新分配
}3.3 修改操作
追加
cpp
string s = "Hello";
// 追加字符串
s += " World"; // "Hello World"
s.append("!!!"); // "Hello World!!!"
// 追加字符
s.push_back('!'); // "Hello World!!!!"
// 追加部分字符串
s.append("ICPC Contest", 4); // 追加前4个字符:"Hello World!!!!ICPC"插入
cpp
string s = "Hello World";
s.insert(6, "Beautiful "); // "Hello Beautiful World"
// 在位置6插入
s.insert(s.begin() + 5, ','); // "Hello, Beautiful World"
// 在迭代器位置插入删除
cpp
string s = "Hello Beautiful World";
s.erase(6, 10); // 从位置6删除10个字符:"Hello World"
s.pop_back(); // 删除最后一个字符:"Hello Worl"
s.erase(s.begin() + 5, s.end() - 3); // 删除[5, end-3)区间:"Hello ld"替换
cpp
string s = "Hello World";
s.replace(6, 5, "ICPC"); // 从位置6开始,替换5个字符:"Hello ICPC"
// 原:"World",新:"ICPC"
s.replace(s.find("ICPC"), 4, "Competition"); // "Hello Competition"3.4 查找操作大全
cpp
string s = "Hello World, Hello ICPC";
// 正向查找
size_t pos1 = s.find("Hello"); // 0
size_t pos2 = s.find("Hello", 1); // 13(从位置1开始找)
// 反向查找
size_t pos3 = s.rfind("Hello"); // 13
// 查找字符集中任意字符
size_t pos4 = s.find_first_of("aeiou"); // 1(第一个元音e的位置)
size_t pos5 = s.find_last_of("aeiou"); // 19(最后一个元音o的位置)
// 查找不在字符集中的字符
size_t pos6 = s.find_first_not_of("Helo "); // 6(W的位置)
size_t pos7 = s.find_last_not_of("CPIC"); // 21(空格的位置)
// 检查是否包含子串
if (s.find("ICPC") != string::npos) {
cout << "包含ICPC" << endl;
}3.5 子串操作
cpp
string s = "Hello ICPC World";
// 提取子串
string sub1 = s.substr(6); // "ICPC World"(从6到结尾)
string sub2 = s.substr(6, 4); // "ICPC"(从6开始取4个字符)
// 分割字符串(竞赛常用)
vector<string> split(const string& str, char delimiter) {
vector<string> tokens;
string token;
stringstream ss(str);
while (getline(ss, token, delimiter)) {
if (!token.empty()) {
tokens.push_back(token);
}
}
return tokens;
}
// 使用:split("apple,banana,orange", ',')3.6 比较操作
cpp
string s1 = "apple";
string s2 = "banana";
// 比较操作符
if (s1 < s2) cout << "apple < banana" << endl;
if (s1 == s2) cout << "equal" << endl;
// compare方法(更多控制)
int result = s1.compare(s2);
// result < 0: s1 < s2
// result == 0: s1 == s2
// result > 0: s1 > s2
// 比较子串
if (s1.compare(0, 3, "app") == 0) {
cout << "以app开头" << endl;
}四、高级技巧与算法
4.1 字符串匹配算法
KMP算法(竞赛必备)
cpp
// 计算前缀函数(部分匹配表)
vector<int> computePrefix(const string& pattern) {
int m = pattern.length();
vector<int> pi(m, 0);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && pattern[i] != pattern[j]) {
j = pi[j-1];
}
if (pattern[i] == pattern[j]) {
j++;
}
pi[i] = j;
}
return pi;
}
// KMP搜索
vector<int> kmpSearch(const string& text, const string& pattern) {
vector<int> positions;
vector<int> pi = computePrefix(pattern);
int n = text.length(), m = pattern.length();
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && text[i] != pattern[j]) {
j = pi[j-1];
}
if (text[i] == pattern[j]) {
j++;
}
if (j == m) {
positions.push_back(i - m + 1);
j = pi[j-1];
}
}
return positions;
}Z算法
cpp
vector<int> zAlgorithm(const string& s) {
int n = s.length();
vector<int> z(n, 0);
for (int i = 1, l = 0, r = 0; i < n; i++) {
if (i <= r) {
z[i] = min(r - i + 1, z[i - l]);
}
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
z[i]++;
}
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
return z;
}4.2 字符串哈希
cpp
// 双哈希避免冲突
class StringHash {
private:
const int MOD1 = 1e9 + 7;
const int MOD2 = 1e9 + 9;
const int BASE = 131;
vector<long long> pow1, pow2;
vector<long long> hash1, hash2;
public:
StringHash(const string& s) {
int n = s.length();
pow1.resize(n + 1);
pow2.resize(n + 1);
hash1.resize(n + 1);
hash2.resize(n + 1);
pow1[0] = pow2[0] = 1;
for (int i = 0; i < n; i++) {
pow1[i+1] = (pow1[i] * BASE) % MOD1;
pow2[i+1] = (pow2[i] * BASE) % MOD2;
hash1[i+1] = (hash1[i] * BASE + s[i]) % MOD1;
hash2[i+1] = (hash2[i] * BASE + s[i]) % MOD2;
}
}
// 获取子串[l, r]的哈希值对
pair<long long, long long> getHash(int l, int r) {
long long h1 = (hash1[r+1] - hash1[l] * pow1[r-l+1] % MOD1 + MOD1) % MOD1;
long long h2 = (hash2[r+1] - hash2[l] * pow2[r-l+1] % MOD2 + MOD2) % MOD2;
return {h1, h2};
}
};4.3 回文串处理
cpp
// 判断回文串
bool isPalindrome(const string& s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
// 最长回文子串 - 中心扩展法
string longestPalindrome(const string& s) {
if (s.empty()) return "";
int start = 0, maxLen = 1;
int n = s.length();
for (int i = 0; i < n; i++) {
// 奇数长度
int l = i, r = i;
while (l >= 0 && r < n && s[l] == s[r]) {
if (r - l + 1 > maxLen) {
start = l;
maxLen = r - l + 1;
}
l--; r++;
}
// 偶数长度
l = i; r = i + 1;
while (l >= 0 && r < n && s[l] == s[r]) {
if (r - l + 1 > maxLen) {
start = l;
maxLen = r - l + 1;
}
l--; r++;
}
}
return s.substr(start, maxLen);
}4.4 字典序问题
cpp
// 最小表示法(字符串循环同构最小表示)
int minRepresentation(const string& s) {
int n = s.length();
int i = 0, j = 1, k = 0;
while (i < n && j < n && k < n) {
int diff = s[(i+k)%n] - s[(j+k)%n];
if (diff == 0) {
k++;
} else {
if (diff > 0) {
i = i + k + 1;
} else {
j = j + k + 1;
}
if (i == j) j++;
k = 0;
}
}
return min(i, j);
}五、竞赛实战技巧
5.1 常见输入格式处理
cpp
// 1. 多组数据直到文件结束
string s;
while (getline(cin, s)) {
// 处理每组数据
}
// 2. 第一行是数据组数T
int T;
cin >> T;
cin.ignore(); // 重要!
while (T--) {
string line;
getline(cin, line);
// 处理
}
// 3. 不定长数字字符串
string num;
cin >> num;
vector<int> digits;
for (char c : num) {
digits.push_back(c - '0'); // 字符转数字
}
// 4. 按空格分割的单词
string line;
getline(cin, line);
stringstream ss(line);
vector<string> words;
string word;
while (ss >> word) {
words.push_back(word);
}5.2 性能优化技巧
cpp
// 1. 使用reserve预分配
string processBigData() {
string result;
result.reserve(1000000); // 预分配空间
// ... 拼接操作
return result;
}
// 2. 使用移动语义避免拷贝
string generateString() {
string s(1000, 'a');
// ... 处理
return s; // 编译器会进行返回值优化(RVO)
}
// 3. 使用string_view(C++17)避免子串拷贝
void processSubstrings(const string& s) {
// 传统方法:创建子串副本
string sub = s.substr(10, 5); // 拷贝!
// C++17 string_view:不拷贝
string_view sv = string_view(s).substr(10, 5); // 不拷贝!
}
// 4. 批量操作优于单个操作
// 慢:
string s;
for (int i = 0; i < 10000; i++) {
s += 'a'; // 可能多次重新分配
}
// 快:
string s(10000, 'a'); // 一次性构造
// 或者
string s;
s.reserve(10000);
for (int i = 0; i < 10000; i++) {
s += 'a'; // 不会重新分配
}5.3 字符串与数值转换
cpp
// 1. 标准库方法
string s1 = to_string(123); // "123"
string s2 = to_string(3.14159); // "3.141590"
int i = stoi("456"); // 456
double d = stod("3.14"); // 3.14
// 2. 竞赛快速转换(避免异常)
int fastStoi(const string& s) {
int result = 0;
for (char c : s) {
if (c < '0' || c > '9') break; // 遇到非数字停止
result = result * 10 + (c - '0');
}
return result;
}
// 3. 大数运算:字符串表示
string addStrings(const string& num1, const string& num2) {
string result;
int carry = 0;
int i = num1.length() - 1, j = num2.length() - 1;
while (i >= 0 || j >= 0 || carry) {
int sum = carry;
if (i >= 0) sum += num1[i--] - '0';
if (j >= 0) sum += num2[j--] - '0';
result.push_back(sum % 10 + '0');
carry = sum / 10;
}
reverse(result.begin(), result.end());
return result;
}5.4 常见竞赛题目类型
- 字符串匹配:KMP、Rabin-Karp、Z算法
- 回文串:Manacher算法、中心扩展
- 字典序:最小表示法、最大最小字符串
- 字符串哈希:快速比较子串、去重
- 动态规划:最长公共子序列、编辑距离
- 后缀结构:后缀数组、后缀自动机(进阶)
六、综合例题
例题1:统计单词频率
cpp
// 输入一段文本,统计每个单词出现的频率
#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <cctype>
using namespace std;
int main() {
string text;
getline(cin, text);
// 转换为小写
for (char& c : text) {
c = tolower(c);
}
// 分割单词
map<string, int> freq;
stringstream ss(text);
string word;
while (ss >> word) {
// 去除标点
while (!word.empty() && ispunct(word.back())) {
word.pop_back();
}
if (!word.empty()) {
freq[word]++;
}
}
// 输出结果
for (const auto& p : freq) {
cout << p.first << ": " << p.second << endl;
}
return 0;
}例题2:字符串编辑距离
cpp
// Levenshtein距离
int editDistance(const string& s1, const string& s2) {
int m = s1.length(), n = s2.length();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for (int i = 0; i <= m; i++) dp[i][0] = i;
for (int j = 0; j <= n; j++) dp[0][j] = j;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = 1 + min({dp[i-1][j], // 删除
dp[i][j-1], // 插入
dp[i-1][j-1]}); // 替换
}
}
}
return dp[m][n];
}七、其他补充
7.1如何反转字符串
补充于2026-02-10,在做到CF12A时想到
在C++中有多种方法可以反转字符串,以下是几种常见的方法:
方法1:使用 std::reverse()(推荐)
cpp
#include <iostream>
#include <algorithm>
#include <string>
int main() {
std::string str = "Hello, World!";
std::reverse(str.begin(), str.end());
std::cout << str << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}方法2:使用反向迭代器
cpp
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
std::string reversed(str.rbegin(), str.rend());
std::cout << reversed << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}方法3:手动交换字符
cpp
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
int n = str.length();
for (int i = 0; i < n / 2; i++) {
std::swap(str[i], str[n - i - 1]);
}
std::cout << str << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}方法4:使用栈(演示概念)
cpp
#include <iostream>
#include <string>
#include <stack>
int main() {
std::string str = "Hello, World!";
std::stack<char> charStack;
// 压入栈
for (char c : str) {
charStack.push(c);
}
// 弹出栈(反转顺序)
std::string reversed;
while (!charStack.empty()) {
reversed += charStack.top();
charStack.pop();
}
std::cout << reversed << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}方法5:递归反转
cpp
#include <iostream>
#include <string>
void reverseString(std::string& str, int left, int right) {
if (left >= right) return;
std::swap(str[left], str[right]);
reverseString(str, left + 1, right - 1);
}
int main() {
std::string str = "Hello, World!";
reverseString(str, 0, str.length() - 1);
std::cout << str << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}针对C风格字符串(char数组)
cpp
#include <iostream>
#include <cstring>
void reverseCString(char* str) {
if (!str) return;
char* start = str;
char* end = str + strlen(str) - 1;
while (start < end) {
std::swap(*start, *end);
start++;
end--;
}
}
int main() {
char str[] = "Hello, World!";
reverseCString(str);
std::cout << str << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}性能比较
std::reverse():最简洁,性能好,推荐使用- 手动交换:控制力强,性能与
std::reverse()相当 - 反向迭代器:简洁但会创建新字符串
- 栈和递归:概念演示,实际应用较少
自定义函数模板
cpp
#include <iostream>
#include <string>
template<typename T>
void reverseAny(T& container) {
int left = 0;
int right = container.size() - 1;
while (left < right) {
std::swap(container[left], container[right]);
left++;
right--;
}
}
int main() {
std::string str = "Hello, World!";
reverseAny(str);
std::cout << str << std::endl; // 输出: "!dlroW ,olleH"
return 0;
}建议:对于日常使用,推荐使用 std::reverse(),因为它简洁、高效且可读性好。