Skip to content

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 常见竞赛题目类型

  1. 字符串匹配:KMP、Rabin-Karp、Z算法
  2. 回文串:Manacher算法、中心扩展
  3. 字典序:最小表示法、最大最小字符串
  4. 字符串哈希:快速比较子串、去重
  5. 动态规划:最长公共子序列、编辑距离
  6. 后缀结构:后缀数组、后缀自动机(进阶)

六、综合例题

例题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;
}

性能比较

  1. std::reverse():最简洁,性能好,推荐使用
  2. 手动交换:控制力强,性能与std::reverse()相当
  3. 反向迭代器:简洁但会创建新字符串
  4. 栈和递归:概念演示,实际应用较少

自定义函数模板

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(),因为它简洁、高效且可读性好。