一、C++字符串简介
在C++中,字符串的处理方式主要有两种:字符数组(C风格字符串)和std::string类。虽然字符数组是C语言遗留的底层实现方式,但现代C++更推荐使用std::string
类,其封装了复杂的操作逻辑,提供了更高的安全性和便利性。本文将深入解析C++字符串的原理、常用操作,并结合力扣算法题进行实战演练。
二、C++字符串的原理
1. 字符数组(C风格字符串)
- 原理:字符数组是以
\0
结尾的字符序列,通过手动管理内存实现字符串操作。 - 特点:
- 需预分配固定大小,容易溢出。
- 操作依赖标准库函数(如
strcpy
、strlen
等),需手动处理边界问题。 - 适合对内存控制要求极高的底层场景。
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[] ) |
---|---|---|
内存管理 | 自动扩容/缩容 | 静态分配,需手动管理 |
操作便捷性 | 成员函数丰富(如find 、substr 等) | 依赖标准库函数(如strcpy 等) |
安全性 | 边界检查(at() )、异常处理 | 容易溢出,需手动添加\0 |
性能 | 高(现代编译器优化) | 更低(频繁内存拷贝) |
推荐使用场景 | 通用开发、STL兼容 | 嵌入式系统、协议解析等底层场景 |
2. 设计建议
- 优先使用
std::string
:现代C++开发中,std::string
是首选方式,其封装了复杂逻辑,减少内存泄漏风险。 - 避免字符数组:除非对性能或内存布局有严格要求,否则不建议使用字符数组。
- 利用移动语义(C++11):临时对象的资源转移通过
std::move
优化效率。 - 边界检查:使用
at()
代替[]
避免越界访问。