如果你在阅读过程中有任何疑问或想要进一步探讨的内容欢迎在评论区畅所欲言我们一起学习、共同成长~ 如果你觉得这篇文章还不错不妨顺手点个赞、加入收藏并分享给更多的朋友噢~1. 为什么必须掌握 string 类1.1 C 语言字符串的局限性C 语言中字符串被定义为以\0结尾的字符数组依赖 str 系列库函数如 strcpy。存在显著缺陷不符合面向对象编程OOP思想C 标准库提供的str系列函数是独立于字符串数据结构之外的全局函数无法直接通过对象调用与 OOP 中 “数据与操作绑定” 的核心思想相悖。内存管理风险高字符串底层空间需用户手动管理若操作不当极易引发内存泄漏等安全问题。无法直接支持字符串拼接、查找等常用操作需手动实现。1.2 面试笔试中的核心地位OJ 题目中字符串类题目 90% 以上以string类形式出现极少使用 C 库函数面试高频要求模拟实现string类构造、拷贝构造、赋值运算符重载、析构考查深拷贝能力常规工作中string类是字符串操作的首选是基础工具类的核心。2.string类的核心接口必须掌握使用 string 类需包含头文件string声明 using namespace std; 或 std::string2.1 构造函数构造函数原型功能描述string()重点默认构造函数创建一个空的string对象。string(const char* s)重点用 C 风格字符串s初始化string对象。string(const string str)重点拷贝构造函数用已存在的string对象str初始化新的string对象。string(size_t n, char c)创建一个包含n个字符c的string对象。#include iostream #include string using namespace std; int main() { string s1; cout s1: s1 endl; string s2(Hello); // 等价于 const char* cstr Hello; string s2(cstr); cout s2: s2 endl; string s3(s2); cout s3: s3 endl; string s4(5, A); cout s4: s4 endl; return 0; }2.2 容量操作分类函数名称功能说明核心特性与注意事项长度查询size()重点返回字符串有效字符长度与容器接口统一- 与length()底层实现完全一致-优先使用size()兼容其他容器length()返回字符串有效字符长度C 风格命名- 功能与size()完全相同- 保留用于兼容旧代码习惯容量查询capacity()返回底层存储空间的总大小单位字符数- 表示当前已分配的内存空间上限- 与有效字符数无关状态检测empty()重点判断字符串是否为空返回 true 为空返回 false 非空内容清空clear()重点清空有效字符size()置于0不会释放空间仅清空内容不改变底层capacity()空间管理reserve(size_t n)重点为字符串预留n个字符的存储空间不改变有效字符数- 若n capacity()不执行- 若n capacity()执行- 仅扩容不缩容提前预留可避免频繁扩容效率损耗resize(size_t n)将有效字符数调整为n多出空间用\0ASCII 码 0填充- n 原长用 \0 填充n 原长截断resize(size_t n,char c)重点将有效字符数调整为n多出空间用字符 c 填充n 原长用字符填充n 原长截断。可能改变容量#include iostream #include string using namespace std; int main() { string str Hello; cout 字符串 str 长度: str.size() endl; cout 字符串 str 容量: str.capacity() endl; cout 字符串 str 是否为空: (str.empty() ? 是 : 否) endl; // clear() string strToClear Clear me; cout strToClear 清空前长度: strToClear.size() endl; cout 清空前容量: strToClear.capacity() endl; strToClear.clear(); cout 清空后长度: strToClear.size() endl; cout 清空后容量: strToClear.capacity() endl; // reserve() string strToReserve Reserve; cout strToReserve 预留前容量: strToReserve.capacity() endl; cout 预留前长度: strToReserve.size() endl; strToReserve.reserve(20); cout 预留 20 字符后容量: strToReserve.capacity() endl; cout 预留后长度: strToReserve.size() endl; // resize(n) string s1 Resize; cout s1 调整前长度: s1.size() endl; s1.resize(10); cout 调整为 10 后长度: s1.size() endl; cout 调整后内容: s1 endl; // resize(n, c) string s2 Resize; cout s2 调整前长度: s2.size() endl; s2.resize(10, !); cout 调整为 10 并用 ! 填充后长度: s2.size() endl; cout 调整后内容: s2 endl; return 0; }2.3 访问及遍历函数名称功能说明operator[]重点随机访问单个字符直接通过下标访问字符不进行越界检查需确保pos size()begin() end()begin()返回指向首字符的迭代器end()返回指向最后一个字符下一个位置last 1的迭代器用于正向遍历rbegin() rend()rbegin()返回指向最后一个字符的反向迭代器rend()返回指向首字符前一个位置first - 1的反向迭代器用于反向遍历范围 for 循环C 11遍历所有字符支持容器、数组或任何支持begin()end()迭代器的对象2.3.1operator[]重点#include iostream #include string using namespace std; int main() { string str Hello; cout 首字符: str[0] endl; str[1] E; cout 修改后字符串: str endl; return 0; }2.3.2begin() end()#include iostream #include string using namespace std; int main() { string str World; for (auto it str.begin(); it ! str.end(); it) { cout *it ; } // 迭代器的类型较复杂auto自动推导类型就无需手动写明类型 cout endl; return 0; }2.3.3rbegin() rend()#include iostream #include string using namespace std; int main() { string str World; for (auto rit str.rbegin(); rit ! str.rend(); rit) { cout *rit ; } cout endl; return 0; }2.3.4 范围 forfor (声明变量 : 范围) {}#include iostream #include string using namespace std; int main() { string str C11; for (char c : str) { cout c ; } cout endl; return 0; }2.4 修改操作分类函数名称功能说明重点说明尾部追加push_back(char c)在字符串尾部插入单个字符c。直接操作字符等价于append(1, c)。append(const string str)在字符串尾部追加另一个字符串str支持str、const char*、字符序列等多类型。可指定追加位置和长度灵活性高。operator重点在字符串尾部追加字符或字符串返回当前对象引用。最常用尾部追加优先用支持 a或 bcd。转换c_str()重点返回以\0结尾的 C 风格字符串指针const char*。用于兼容 C 库函数如strlen、strcmp如 printf(%s, s.c_str())查找find(char c/string s, size_t pos 0)重点从索引pos开始向后查找字符c或字符串s返回首次出现的索引未找到返回string::npos值为-1rfind(char c/string s, size_t pos npos)从索引pos开始向前查找适用于从后往前搜索的场景如查找后缀。截取substr(size_t pos 0, size_t n)重点从索引pos开始截取长度为n的子字符串并返回pos 越界则抛出异常n 省略则截至末尾int main() { string s Hello World; s.push_back(!); s ???; size_t idx1 s.find(Wor); // 从下标0开始查找 size_t idx2 s.find(!, 3); // 从下标3开始找 size_t idx3 s.find(z); // 查找不存在的字符 if (idx3 string::npos) { cout 未找到 endl; } string sub1 s.substr(2, 3); // 起始下标截取长度 string sub2 s.substr(3); // 起始下标截到末尾 string sub3 s.substr(); // 截全部 return 0; }2.5 非成员函数了解函数名称功能说明operator拼接字符串返回新字符串传值返回深拷贝可能导致效率问题建议尽量少用用替代。operator重点输入字符串遇空格 / 换行符停止。operator重点输出字符串支持连续输出。getline(cin, str)重点读取一整行字符串包含空格遇换行符停止换行符不保留。比较运算符重点比较字符串大小按字符字典序ASCII 码逐个比较直到出现差异或到达末尾支持、!、、、、2.5.1 #include iostream #include string using namespace std; int main() { string inputStr; cout 请输入一个字符串遇空格或换行停止: ; cin inputStr; cout inputStr endl; string str1 Hello; string str2 World; cout str1 str2 endl; return 0; }2.5.2getline#include iostream #include string using namespace std; int main() { string line; cout 请输入一行字符串可含空格: ; getline(cin, line); cout 你输入的是: line endl; cout 字符串长度: line.size() endl; return 0; }2.5.3 关系运算符#include iostream #include string using namespace std; int main() { string str1 apple; string str2 banana; string str3 apple; // if (str1 str3) { cout str1 等于 str3 endl; } else { cout str1 不等于 str3 endl; } // 大小比较按字典序 if (str1 str2) { cout str1 str1 小于 str2 str2 endl; } else if (str1 str2) { cout str1 str1 大于 str2 str2 endl; } else { cout str1 等于 str2 endl; } // ! if (str2 ! str3) { cout str2 不等于 str3 endl; } return 0; }3. string类的模拟实现面试常考面试常考模拟实现string类主要是实现string类的构造、拷贝构造、赋值运算符重载及析构函数。3.1错误示例引入class String { public: String(const char* s ) { if (nullptr s) { assert(false); return; } _s new char[strlen(s) 1]; strcpy(_s, s); } ~String() { if (_s) { delete[] _s; _s nullptr; } } private: char* _s; }; void TestString() { String s1(hello bit!!!); String s2(s1); }上述String类没有显式定义其拷贝构造函数与赋值运算符重载编译器会合成默认拷贝构造函数默认拷贝构造函数是浅拷贝只复制对象中的值不复制内容。用s1构造s2时编译器调用默认拷贝构造只复制了对象中的char* _s没有复制该指针指向的堆内存。于是两个对象s1、s2的_s指向同一块内存析构时这块内存被释放两次引起程序崩溃。3.2 浅拷贝危害浅拷贝值拷贝指编译器仅复制对象中的值。若对象涉及资源管理会导致多个对象共享同一资源。当某一对象销毁并释放资源后其他对象仍认为资源有效继续操作该资源时将引发访问违规。采用深拷贝可解决以上问题即每个对象拥有独立的内存空间避免共享资源。3.3 深拷贝如果一个类中涉及资源管理其拷贝构造函数、赋值运算符重载及析构函数必须显式定义从而实现深拷贝。现代版深拷贝模拟实现String类面试必问#include iostream #include cassert #include cstring // strlen、strcpy using namespace std; class String { public: String(const char* str ) // 默认值为空字符串 { if (nullptr str) { assert(false); // 直接终止程序并提示错误位置 return; } _str new char[strlen(str) 1]; // strlen(str)计算字符串str有效字符个数 // 多分配 1 字节空间用于存 \0 strcpy(_str, str); // 将源字符串str完整含\0拷贝到_str指向的空间 } // 拷贝构造 String(const String s) { String temp s._str; // 用 s._str 创建并初始化临时对象temp // 若此时_str未初始化那它就是野指针swap后析构 delete[] _str 就是释放野指针使程序崩溃而 delete[] nullptr 在C是安全 swap(_str, temp._str); // 交换当前对象和temp的字符串 _strnullptr; // 避免swap后临时对象持有野指针 } // 赋值运算符重载 String operator(String s) // 传值调用触发拷贝构造生成一个s的副本独立内存 { swap(_str, s._str); // 交换前s._str指向副本内存交换后s._str指向原内存——析构时释放原内存 return *this; } ~String() { if (_str) // 判断_str非nullptr而是有效指针——虽然 delete[] nullptr 在C中是安全的但这样增强代码可读性 { delete[] _str; _str nullptr; // 释放资源后置空避免野指针 } } private: char* _str; };传统版模拟实现String类现代版现代版优势拷贝构造String(const String s): _str(new char[strlen(s._str) 1]){strcpy(_str, s._str);}String(const String s){String strTmp(s._str);swap(_str, strTmp._str);_strnullptr;}无需手动判断自赋值不用手动释放旧内存依赖局部参数析构自动释放降低内存泄漏风险代码更简洁。赋值运算符重载重点对比String operator(const String s){if (this ! s)// 要素①自赋值检测{// 要素②深拷贝char* pStr new char[strlen(s._str) 1];strcpy(pStr, s._str);// 要素③释放旧资源delete[] _str;_str pStr;}return *this;// 要素④}String operator(String s){swap(_str, s._str);return *this;}4. 笔试高频OJ题必掌握以下题目均为字符串模块高频笔试真题代码可直接复用需理解核心思路。4.1 仅仅反转字母给定一个字符串如 a-bC-dEf-ghIj 仅反转字符串中字母的位置非字母的字符位置不变。核心思路首尾向中间靠拢跳过非字母字符后交换。#include iostream #include string using namespace std; class Solution { public: bool isLetter(char c) { if ((c a c z) || (c A c Z)) { return true; } return false; } string reverseOnLetter(string s) { if (s.empty()) return s; // 字符串判空返回避免无效操作及潜在问题 size_t begin 0, end s.size() - 1; // size_t取值非负适合存储索引 while (begin end) // 左右指针未相遇即还有未处理的字符 { // “左指针”跳过非字母 while (!isLetter(s[begin])) begin; // “右指针”跳过非字母 while (!isLetter(s[end])) --end; swap(s[begin], s[end]); begin; --end; } return s; } }; int main() { string str a-bC-dEf-ghIj; Solution sol; cout 原字符串 str endl; cout 反转字母后的字符串 sol.reverseOnLetter(str) endl; return 0; }4.2 第一个只出现一次的字符给定一个字符串若能找到第一个只出现一次的字符返回其索引若不存在则返回 -1 。核心思路用数组统计每个ASCII字符的出现字数。#include iostream #include string using namespace std; class Solution {public: int firstUniqChar(string s) { int count[256] { 0 }; // 计算机中字符本质是用ASCII码表示的整数而扩展ASCII码共256个可能取值 // 所有字符的次数初始化为 0 for (int i 0; i s.size(); i) { count[s[i]]; // C中字符可隐式转换为对应ASCII码 } for (int i 0; i s.size(); i) { if (count[s[i]] 1) return i; } return -1; } }; int main() { string str leetcode; Solution sol; cout 原字符串 str endl; cout 第一个只出现一次的字符索引 sol.firstUniqChar(str) endl; return 0; }4.3 最后一个单词的长度给定一个由若干单词组成的句子单词由大小写字母混合构成单词间用单个空格分隔求最后一个单词的长度。核心思路从末尾反向查找最后一个空格计算长度。#include iostream #include string using namespace std; int main() { string s You Are Beautiful; while (getline(cin, s)) // 读取整行字符串含空格 { size_t pos s.rfind( ); // 最后一个空格的索引 cout s.size() - 1 - pos endl; } return 0; }4.4 验证一个字符串是否为回文串给定一个字符串如 A man, a plan, a canal: Panama 如果它是回文串返回true否则返回false。回文串不区分大小写所有大写字符转为小写字符或所有小写字符转为大写字符忽略非字母数字字符如标点符号、空格正读和反读相同的字符串。核心思路双指针过滤非字母数字字符并统一大小写后比较。#include iostream #include string using namespace std; class Solution { public: bool isLetterOrNumber(char c) { return (c 0 c 9) || (c a c z) || (c A c Z); } bool isPalindrome(string s) { // 先统一大小写 for (auto c : s) { if (c A c Z) { c 32; // 小写ASCII码 大写ASCII码 32 } } int begin 0, end s.size() - 1; while (begin end) { while (!isLetterOrNumber(s[begin])) begin; while (!isLetterOrNumber(s[end])) --end; if (s[begin] ! s[end]) // 左右不等 return false; // 左右相等 begin; --end; } return true; } }; int main() { string str A man,a plan,a canal:Panama; Solution sol; cout str (sol.isPalindrome(str)? 是: 不是) 回文串 endl; return 0; }4.5 字符串相加给定两个非负整数字符串 n1如123和 n2如456计算它们的和并同样以字符串形式返回。不能使用任何內建的用于处理大整数的库 也不能直接将输入的字符串转换为整数形式。核心思路从“个位”开始逐位相加处理进位最后反转结果。#include iostream #include string using namespace std; class Solution { public: string addStrings(string n1, string n2) { int end1 n1.size() - 1; // 最后一个字符个位索引 int end2 n2.size() - 1; int value1 0, value2 0, carry 0; string result; while (end1 0 || end2 0) // 未遍历完 { // 字符0~9的ASCII码连续如3-03即字符转为数字 // 若end10即遍历完那该位取0 value1 (end1 0) ? (n1[end1--] - 0) : 0; value2 (end2 0) ? (n2[end2--] - 0) : 0; int sum value1 value2 carry; // 两个数字位 更低一位的进位 carry sum / 10; // 当前位的进位如5712进位就是1 result (sum % 10) 0; // 当前位结果去进位后转字符加进结果 } // 此时逐位相加的循环已止carry表示最高位的进位 if (carry 1) { result 1; } // 从右向左逐位相加但result字符串是从左向右加字符 reverse(result.begin(), result.end()); return result; } }; int main() { Solution sol; string num1 123; string num2 456; string sum sol.addStrings(num1, num2); cout 非负整数字符串之和: sum endl; return 0; }