一、C++字符串简介

在C++中,字符串的处理方式主要有两种:字符数组(C风格字符串)和std::string类。虽然字符数组是C语言遗留的底层实现方式,但现代C++更推荐使用std::string类,其封装了复杂的操作逻辑,提供了更高的安全性和便利性。本文将深入解析C++字符串的原理、常用操作,并结合力扣算法题进行实战演练。


二、C++字符串的原理

1. 字符数组(C风格字符串)

  • 原理:字符数组是以\0结尾的字符序列,通过手动管理内存实现字符串操作。
  • 特点
    • 需预分配固定大小,容易溢出。
    • 操作依赖标准库函数(如strcpystrlen等),需手动处理边界问题。
    • 适合对内存控制要求极高的底层场景。
char str[10] = "hello"; // 存储"hello\0"

2. std::string类

  • 原理std::string是C++标准库提供的动态字符串类,内部通过动态内存管理实现自动扩容和缩容。
  • 特点
    • 自动管理内存,无需手动分配/释放。
    • 提供丰富的成员函数(如拼接、查找、替换等)。
    • 支持STL容器和算法,兼容性高。
    • 通过移动语义(C++11)优化临时对象的资源转移效率。
#include <string>
std::string s = "hello";
s += " world"; // 自动扩容

三、C++字符串的常用操作

1. 赋值与初始化

std::string s1 = "hello";       // 直接赋值
std::string s2(5, 'a');          // 构造5个'a'组成的字符串
std::string s3(s1);              // 拷贝构造
std::string s4 = s1 + s2;        // 拼接赋值

2. 字符串拼接

std::string s = "Hello";
s += " C++";                     // 运算符重载
s.append(" world");              // 成员函数

3. 查找与替换

std::string s = "Hello World";
size_t pos = s.find("World");    // 查找子串位置
if (pos != std::string::npos) {
    s.replace(pos, 5, "C++");     // 替换为"C++"
}

4. 子串提取

std::string s = "Hello C++";
std::string sub = s.substr(6, 4); // 提取从索引6开始的4个字符:"C++"

5. 字符串比较

std::string a = "apple", b = "banana";
if (a < b) {                     // 字典序比较
    std::cout << "a is smaller";
}

6. 长度与空判断

std::string s = "hello";
std::cout << s.size();           // 输出长度(5)
std::cout << s.empty();          // 判断是否为空(0)

7. 边界安全访问

std::string s = "example";
char ch1 = s[2];                 // 不检查边界
char ch2 = s.at(2);              // 检查边界,越界抛出异常

四、力扣算法实战:字符串应用场景

1. 回文串验证(LeetCode 125)

题目:验证字符串是否为回文串(忽略非字母数字字符,不区分大小写)。

思路:双指针法,从两端向中心遍历,逐字符比较。

#include <string>
#include <cctype>
using namespace std;

bool isPalindrome(string s) {
    int left = 0, right = s.size() - 1;
    while (left < right) {
        while (left < right && !isalnum(s[left])) left++;
        while (left < right && !isalnum(s[right])) right--;
        if (tolower(s[left]) != tolower(s[right])) return false;
        left++, right--;
    }
    return true;
}

2. 最长公共前缀(LeetCode 14)

题目:找出多个字符串的最长公共前缀。

思路:纵向扫描,逐字符比较所有字符串的相同位置。

#include <string>
#include <vector>
using namespace std;

string longestCommonPrefix(vector<string>& strs) {
    if (strs.empty()) return "";
    for (int i = 0; i < strs[0].size(); ++i) {
        char c = strs[0][i];
        for (int j = 1; j < strs.size(); ++j) {
            if (i == strs[j].size() || strs[j][i] != c) {
                return strs[0].substr(0, i);
            }
        }
    }
    return strs[0];
}

3. 字符串转换整数(atoi)(LeetCode 8)

题目:将字符串转换为整数,处理空格、符号和溢出。

思路:状态机处理输入,逐字符解析。

#include <string>
#include <climits>
using namespace std;

int myAtoi(string s) {
    int index = 0, sign = 1, result = 0;
    while (s[index] == ' ') index++;
    if (s[index] == '-' || s[index] == '+') {
        sign = (s[index++] == '-') ? -1 : 1;
    }
    while (index < s.size() && isdigit(s[index])) {
        int digit = s[index++] - '0';
        if (result > INT_MAX / 10 || 
            (result == INT_MAX / 10 && digit > INT_MAX % 10)) {
            return (sign == 1) ? INT_MAX : INT_MIN;
        }
        result = result * 10 + digit;
    }
    return sign * result;
}

五、知识点总结

1. std::string vs 字符数组

特性 std::string 字符数组(char[]
内存管理 自动扩容/缩容 静态分配,需手动管理
操作便捷性 成员函数丰富(如findsubstr等) 依赖标准库函数(如strcpy等)
安全性 边界检查(at())、异常处理 容易溢出,需手动添加\0
性能 高(现代编译器优化) 更低(频繁内存拷贝)
推荐使用场景 通用开发、STL兼容 嵌入式系统、协议解析等底层场景

2. 设计建议

  • 优先使用std::string:现代C++开发中,std::string是首选方式,其封装了复杂逻辑,减少内存泄漏风险。
  • 避免字符数组:除非对性能或内存布局有严格要求,否则不建议使用字符数组。
  • 利用移动语义(C++11):临时对象的资源转移通过std::move优化效率。
  • 边界检查:使用at()代替[]避免越界访问。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐