C++ string类
int main()// 不要使用cin>>line,因为它遇到空格就结束了return 0;string s;;return 0;
1. 为什么学习string类?
1.1 C语言中的字符串
• C语言中,字符串是以'\0'结尾的字符数组。
• 标准库提供了strlen、strcpy、strcmp等一系列str函数,但这些函数与字符串本身是分离的,不符合OOP(面向对象编程)的思想。
• 底层的内存空间需要用户自己管理,稍不留意就可能导致越界访问。
1.2 两个面试题(暂不做讲解)
• 字符串转整形数字
• 字符串相加
在OJ(在线判题)中,有关字符串的题目基本都以string类的形式出现。在常规工作中,为了简单、方便、快捷,也基本都使用string类,很少有人去使用C库中的字符串操作函数。
2. 标准库中的string类
2.1 string类(了解)
• string类的文档介绍:在使用string类时,必须包含<string>头文件,并且使用using namespace std;(或std::string)。
2.2 auto和范围for
auto关键字
在C++11中,auto被赋予了全新的含义:它不再是一个存储类型指示符,而是作为一个类型指示符,由编译器在编译时期推导得出。
• 指针与引用:用auto声明指针类型时,auto和auto*没有任何区别;但用auto声明引用类型时,则必须加&。
• 同一行声明多个变量:这些变量必须推导为相同的类型,否则编译器会报错,因为编译器只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
• 函数参数与返回值:auto不能作为函数的参数,但可以做返回值,不过建议谨慎使用。
• 数组:auto不能直接用来声明数组。
#include<iostream>
using namespace std;
int func1()
{
return 10;
}
// 不能做参数
// void func2(auto a)
// {}
// 可以做返回值,但建议谨慎使用
auto func3()
{
return 3;
}
int main()
{
int a = 10;
auto b = a; // 推导为 int
auto c = 'a'; // 推导为 char
auto d = func1();// 推导为 int
// 编译报错: error C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
// auto e;
cout << typeid(b).name() << endl; // 输出 int
cout << typeid(c).name() << endl; // 输出 char
cout << typeid(d).name() << endl; // 输出 int
int x = 10;
auto y = &x; // 推导为 int*
auto* z = &x; // 推导为 int*
auto& m = x; // 推导为 int&
cout << typeid(y).name() << endl; // 输出 int *
cout << typeid(z).name() << endl; // 输出 int *
cout << typeid(m).name() << endl; // 输出 int &
auto aa = 1, bb = 2; // 正确,均为 int
// 编译报错: error C3538: 在声明符列表中, "auto"必须始终推导为同一类型
// auto cc = 3, dd = 4.0;
// 编译报错: error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
// auto array[] = { 4, 5, 6 };
// auto的用武之地:简化迭代器声明
std::map<std::string, std::string> dict = { {"apple", "苹果"},{"orange", "橙子"}, {"pear","梨"} };
// std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
return 0;
}
范围for
• 对于一个有范围的集合,由程序员来说明循环的范围是多余的,有时还会容易犯错误。因此,C++11中引入了基于范围的for循环。
• for循环后的括号由冒号:分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
• 范围for会自动迭代、自动取数据、自动判断结束。
• 其底层实现就是替换为迭代器。
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历方式
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2; // 将每个元素乘以2
}
// 遍历并打印数组
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << " ";
}
cout << endl; // 输出:2 4 6 8 10
// C++11的范围for遍历
for (auto& e : array) // 使用引用,直接修改原数组元素
e *= 2; // 每个元素再乘以2
// 遍历并打印数组
for (auto e : array) // 直接遍历打印
cout << e << " ";
cout << endl; // 输出:4 8 12 16 20
// 遍历字符串
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl; // 输出:h e l l o w o r l d
return 0;
}
◦ for (auto& e : array):使用auto&可以直接修改数组中的元素,因为e是数组元素的引用。
◦ for (auto e : array):这里的e是数组元素的拷贝,修改e不会影响原数组,适合只读遍历。
◦ 范围for循环自动处理迭代的开始和结束,代码更简洁、安全。
◦ string是一个容器,范围for循环可以直接遍历其每个字符。
◦ auto ch会被推导为char类型,逐个取出字符串中的字符。
string的三种遍历方式
1. 下标 [] —— 像数组一样用
2. 迭代器 iterator —— 通用遍历器
3. 范围for for(auto...) —— 最简单写法
创建字符串 + 重载 << 输出 & [] 修改
string s1; // 空字符串
string s2("hello world"); // 构造字符串
cout << s1 << s2 << endl;
s2[0] = 'x'; // 把第 0 个字符改成 'x'
cout << s1 << s2 << endl; // 改成 x 后,s2 变成:xello world
重点来了:三种遍历
① 下标遍历(最简单)
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
// 像数组一样,i 从 0 开始,用 [] 访问每个字符
② 迭代器 iterator(最通用)
// string::iterator it = s2.begin();
auto it = s2.begin();
while (it != s2.end())
{
*it += 2; // 把字符 ASCII +2
cout << *it << " ";
++it;
}
• begin() → 指向第一个字符;end() → 指向最后一个字符的下一个位置;*it → 取出当前字符
• 迭代器就是像指针一样的东西;所有容器都能用迭代器:vector / list / map…
这里 *it += 2 会让字符变大,比如:x → z,e→g 等。
③ 范围for(最简洁)
for (auto& ch : s2)
{
ch -= 2; // 改回原来的字符
cout << ch << " ";
}
• auto& ch:引用,能修改原字符串 ; auto ch:值拷贝,不能修改原字符串
• 底层原理:就是迭代器
总结:[]:像数组,方便;iterator:通用,所有容器都能用;范围for:写法最简单,底层是迭代器。
迭代器
1. 普通迭代器:iterator
2. 反向迭代器:reverse_iterator
3. 只读迭代器:const_iterator
4. 只读反向迭代器:const_reverse_iterator
1. 普通迭代器 iterator
string s2("hello world");
string::iterator it = s2.begin();
while (it != s2.end())
{
*it += 2; // ✅ 可以修改
cout << *it << " ";
++it;
}
// 输出:j g n n q " y q t n f
• 可读、可修改; begin() → 开头; end() → 末尾下一个位置; ++it 往后走
2. 反向迭代器 reverse_iterator
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;
}
// 输出:f n t q y # q n n g j
• 倒着遍历; rbegin() → 最后一个字符; rend() → 第一个字符前面; 依然用 ++rit,它自动往前走
3. const 迭代器 const_iterator
const string s3("hello world");
auto cit = s3.begin();
while (cit != s3.end())
{
// *cit += 2; ❌ 不能改
cout << *cit << " ";
++cit;
}
// 输出:h e l l o w o r l d
• 只能读,不能改; 给 const string 用的; 想修改会直接报错
4. const 反向迭代器 const_reverse_iterator
auto rcit = s3.rbegin();
while (rcit != s3.rend())
{
// *rcit += 2; ❌ 不能改
cout << *rcit << " ";
++rcit;
}
// 输出:d l r o w o l l e h
• 反向 + 只读; 倒着遍历,但不能修改内容
总结:
| 迭代器类型 | 能否修改 | 遍历方向 | 适用对象 |
| iterator | 可改 | 正向 | 普通 string |
| reverse_iterator | 可改 | 反向 | 普通 string |
| const_iterator | 不可改 | 正向 | const string |
| const_reverse_iterator | 不可改 | 反向 | const string |
• 带 reverse → 倒着走 ; 带 const → 只能看,不能改
rbegin() 和 rend()
rbegin = reverse begin → 反向开头;rend = reverse end → 反向结尾;它们是给反向迭代器用的。
1. 正常迭代器(begin / end)
字符串: h e l l o
下标: 0 1 2 3 4
begin() → 指向 h
end() → 指向 o 后面
++it → 从左往右走
2. 反向迭代器(rbegin / rend)
字符串:h e l l o
rbegin() → 指向 **o**(最后一个字符)
rend() → 指向 **h 前面**(第一个字符前面)
++rit → **从右往左走**
重点:++ 反向迭代器,是往左边走!
3. 画图秒懂
字符: h e l l o
↑ ↑
rend() rbegin()
• rbegin() = 最后一个元素; rend() = 第一个元素前面的位置; ++rit → 向左移动
4. 代码里的效果
string s = "hello";
auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
输出:o l l e h
超级总结(背这三句)
rbegin() → 指向最后一个字符; rend() → 指向第一个字符前面; ++rit → 从右往左遍历
一句话:rbegin / rend 就是专门用来倒着遍历的。
2.3 string类的常用接口说明
1. string类对象的常见构造
| (constructor)函数名称 | 功能说明 |
| string() (重点) | 构造空的string类对象,即空字符串 |
| string(const char* s) (重点) | 用C-string来构造string类对象 |
| string(size_t n, char c) | string类对象中包含n个字符c |
| string(const string& s) (重点) | 拷贝构造函数 |
#include <string>
#include <iostream>
using namespace std;
void Teststring()
{
string s1; // 1. 默认构造(空字符串)
string s2("hello bit"); // 2. 从C风格字符串构造
string s3(s2); // 3. 拷贝构造
}
int main() {
Teststring();
return 0;
}
1. string s1; —— 默认构造
作用:创建一个空的 string 对象 s1。
内部状态: 有效字符长度 size() 为 0;容量 capacity() 可能为 0 或一个实现定义的初始值(例如在VS下为15);不包含任何字符,等价于 ""。
2. string s2("hello bit"); —— 从C风格字符串构造
作用:接收一个以 '\0' 结尾的C风格字符串(const char*),并创建一个内容相同的 string 对象 s2。
内部状态:s2 的内容为 "hello bit";size() 返回 9(h e l l o b i t 共9个有效字符)。
注意:构造时会自动忽略C字符串末尾的 '\0',string 内部不依赖 '\0' 来判断长度。
3. string s3(s2); —— 拷贝构造
作用:创建一个新的 string 对象 s3,其内容是 s2 的一个深拷贝。
内部状态:s3 的内容与 s2 完全相同,为 "hello bit";s3 拥有独立的内存空间,对 s3 的修改不会影响 s2,反之亦然。
2. string类对象的容量操作
| 函数名称 | 功能说明 |
| size(重点) | 返回字符串有效字符长度 |
| length | 返回字符串有效字符长度 |
| capacity | 返回空间总大小 |
| empty(重点) | 检测字符串是否为空串,是返回true,否则返回false |
| clear(重点) | 清空有效字符 |
| reserve(重点) | 为字符串预留空间** |
| resize(重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
注意:
1. size()与length()方法底层实现完全相同,引入size()是为了与其他容器的接口保持一致,一般情况下基本都使用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n)与resize(size_t n, char c)都是将字符串中有效字符个数改变到n个。不同的是当字符个数增多时,resize(n)用'\0'来填充多出的元素空间,resize(n, char c)用字符c来填充。resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小;如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0)为string预留空间,不改变有效元素个数。当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
// 先造一个字符串
string s("hello");
// ==============================
// 1. size() / length()
// ==============================
cout << "1. size: " << s.size() << endl;
cout << " length: " << s.length() << endl;
// 功能完全一样,都是返回有效字符个数
// 输出:5
// ==============================
// 2. capacity()
// ==============================
cout << "2. capacity: " << s.capacity() << endl;
// 输出:平台不同不一样,比如 15 / 22 等
// 意思:当前 string 能存多少字符不用扩容
// ==============================
// 3. empty()
// ==============================
cout << "3. empty? " << boolalpha << s.empty() << endl;
// 输出:false(不是空串)
// ==============================
// 4. clear()
// ==============================
s.clear();
cout << "4. clear 后 empty? " << s.empty() << endl;
// 输出:true
// 注意:clear 只清内容,不释放空间,capacity 不变
// 恢复字符串
s = "hello";
// ==============================
// 5. reserve(n) —— 预留空间,不改变内容
// ==============================
cout << "5. reserve 前 capacity: " << s.capacity() << endl;
s.reserve(20); // 至少能存20个字符
cout << " reserve(20) 后 capacity: " << s.capacity() << endl;
cout << " 内容不变: " << s << endl;
// 内容还是 hello,size 还是 5,只是容量变大
// ==============================
// 6. resize(n) / resize(n, ch)
// 改变有效字符个数
// ==============================
s.resize(8); // 长度改成8,多出来的填 '\0'
cout << "6. resize(8) 后 size: " << s.size() << endl;
s.resize(10, 'x'); // 长度改成10,多出来的填 'x'
cout << " resize(10,'x') 后: " << s << endl;
s.resize(3); // 长度改成3,截断
cout << " resize(3) 后: " << s << endl;
return 0;
}
• size / length:字符串实际有多少字符
• capacity:现在能装多少字符不用扩容
• empty:是不是空串
• clear:清空内容,容量不变
• reserve(n):只扩容空间,不改内容
• resize(n):改字符个数,变长补字符,变短截断
默认:true=1,false=0;用 boolalpha:true=true,false=false
size / length / capacity / reserve / resize / clear
• size() / length():字符串有效字符个数,两个完全一样。
• capacity():底层已经开辟的空间大小(能存多少字符)。
• reserve(n):只开空间,不改变有效字符;作用:提前扩容,避免频繁扩容。注意:只扩不缩(不会缩小容量)。
• clear():清空数据,size 变成 0,capacity 不变。
① void TestPushBack(),功能:测试 reserve 提前开空间 + 观察自动扩容
作用:reserve(100) 直接开 100 空间
void TestPushBack()
{
string s;
// 提前开 100 字符空间,避免自动扩容
s.reserve(100);
// 记录当前容量
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
// 尾插 100 个 'c'
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
// 容量变了就打印
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
// 输出:capacity changed: 100
// making s grow:
• 后面插 100 个字符 不会扩容;只会打印一次容量
② void test_string(),功能:测试容量相关函数
void test_string()
{
string s2("hello world");
cout << s2.length() << endl; // 有效字符:11
cout << s2.size() << endl; // 和 length 一样
cout << s2.max_size() << endl; // 字符串最大理论长度(很大的数)
cout << s2.capacity() << endl; // 当前底层空间大小
TestPushBack(); // 测试push_back、reserve、扩容
}
输出:
11
11
1073741820
15
capacity changed: 100
making s grow:
重点: size = length; capacity ≥ size
③ void test_string4(),功能:重点测试 reserve 各种情况 + clear
void test_string4()
{
string s2("hello worldxxxxxxxxxxxxx");
cout << s2.size() << endl; // 有效字符
cout << s2.capacity() << endl; // 容量
cout << endl;
// size() = 24;编译器默认给的 capacity() 一般是 31(VS 下常见)
// 1. reserve(20) 比当前容量小
// reserve 只扩不缩!无效,容量不变
s2.reserve(20);
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
// 2. reserve(28) 比当前容量小
// 依然无效,容量不变
s2.reserve(28);
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
// 3. reserve(40) 比当前容量大
// 会扩容,capacity 变成≥40
s2.reserve(40);
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
// 4. clear 清空
// size = 0,capacity 不变!
s2.clear();
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
// 查看迭代器类型名称
cout << typeid(string::iterator).name() << endl;
cout << typeid(string::reverse_iterator).name() << endl;
}
1. 初始: size:24 ; capacity:31
2. reserve(20)栈: 比 31 小 → 不缩容, size:24, capacity:31
3. reserve(28), 还是比 31 小 → 不变, size:24, capacity:31
4. reserve(40): 比 31 大 → 扩容到 40, size:24, capacity:40
5. clear(): size 清 0, capacity 保持 40 不变
6. 最后两行:是 typeid 打印出的迭代器真实类型。
超级重点结论:
1. reserve(n): n > capacity → 扩容; n < capacity → 不缩容,什么都不做
2. clear(): size 变 0; capacity 不变(空间不释放)
大小相关:
• size() = length():有效字符个数; capacity():底层实际空间大小; max_size():理论最大值(不用记)
空间操作:
• reserve(n): 只改 capacity,只扩不缩, 用来提前开空间,提高效率。
• clear():清空内容,size=0, capacity 不动。
erase 删除、replace 替换、swap、find / rfind、substr、文件读取、路径分割
1)test_string6
① erase 删除(超级常用)
string s("hello world");
s.erase(6, 1); // 从下标6开始,删1个字符 → 删掉'w'
cout << s << endl;// hello orld
s.erase(0, 1); // 从0开始删1个 → 删掉'h'
cout << s << endl;// ello orld
s.erase(s.begin());// 删迭代器位置(第一个字符)
cout << s << endl; // llo orld
s.erase(--s.end());// 删最后一个字符
cout << s << endl; // llo orld → llo orl
s.erase(s.size()-1, 1);// 再删最后一个
cout << s << endl; // llo or
② erase 只给起始位置,删到末尾
string ss("hello world");
ss.erase(6); // 从下标6开始,全删完
cout << ss << endl;// hello
③ 把空格替换成 %%(两种方法)
string sss("hello world hello bit");
// 方法1:replace 循环替换(注释掉的)
/*size_t pos = sss.find(' ');
while (pos != string::npos)
{
sss.replace(pos, 1, "%%");
pos = sss.find(' ', pos+2);
}
cout << sss << endl;*/
//sss.replace(5, 1, "%%");
//cout << sss << endl;
// 方法2:遍历一遍,遇到空格就加%%,效率更高
string tmp;
tmp.reserve(sss.size()); // 提前开空间,避免扩容
for (auto ch : sss)
{
if (ch == ' ')
tmp += "%%"; // 空格 → %%
else
tmp += ch;
}
cout << tmp << endl;
④ swap 交换两个 string
sss.swap(tmp);
cout << sss << endl;
swap 只交换内部指针,极快。
⑤ c_str() 配合 C 文件函数
string file;
cin >> file; // 定义一个字符串 file,从键盘输入一个文件名,比如:test.txt
FILE* fout = fopen(file.c_str(), "r");// string → const char*
// fopen 不认识 C++ 的 string,只认识 C 语言的 const char*
// 所以必须用 .c_str() 把 string 转成 C 字符串
char ch = fgetc(fout);
while (ch != EOF)
{
cout << ch;
ch = fgetc(fout);
}
fclose(fout);
• fopen 必须用 c_str(),因为 C 语言文件函数不认识 C++ string, .c_str() = 把 C++ string 转成 C 语言const char*
2)test_string7
① rfind + substr 取文件后缀
string s("test.cpp.zip");
size_t pos = s.rfind('.');// 从后往前找最后一个.
string suffix = s.substr(pos);
cout << suffix << endl; // .zip
② find_first_not_of:找第一个不在指定集合里的字符
find_first_not_of( "要排除的字符", 从哪里开始找 );
string str("Please, replace the vowels in this sentence by asterisks.");
size_t found = str.find_first_not_of("abcdef");
// 从字符串开头开始找,找到第一个不属于 {a,b,c,d,e,f} 的字符,返回它的下标。
while (found != string::npos) // 只要找到了(没找到就是 npos)
{
str[found] = '*'; // 把这个字符改成 *
// 从下一个位置继续找,不再回头
found = str.find_first_not_of("abcdef", found + 1);
}
cout << str << endl;
// 原字符串:Please, replace the vowels in this sentence by asterisks.
// 运行后:**e*e****************e****************e******************
把不是 a/b/c/d/e/f 的字符全变成 *。
③ find_last_of / substr 拆分路径
size_t found = str.find_last_of("/\\");// 找最后一个 / 或 \
// 作用:从后往前找,找到最后一个 / 或者 \ 的位置。
// / 是 Linux/mac 路径分隔符:/usr/bin/xxx, \是 Windows 路径分隔符:D:\folder\a.txt
// 写 \\ 是因为 \ 在字符串里要转义
str.substr(0, found); // 路径部分
// substr(起始位置, 长度),这里是:从 0 开始,到 found 位置之前,得到:路径部分
str.substr(found + 1); // 文件名部分
// 从 found + 1 开始,一直取到字符串末尾,得到:文件名
"/usr/bin/man"
路径:/usr/bin
文件:man
总结:
1. erase 删除
s.erase(pos, n):从pos删n个 s.erase(pos):从pos删到尾 s.erase(it):删迭代器位置
2. substr 截取
s.substr(pos, n):从pos取n个 s.substr(pos):从pos取到尾
3. find 查找
find:从前找 rfind:从后找 find_first_not_of:找不在集合里的第一个字符
4. 替换
循环遍历替换最稳、最好懂
5. 小知识点
swap:快速交换两个 string
c_str():转C语言字符串,给文件/系统函数用
reserve:提前开空间,提高效率
3. string类对象的访问及遍历操作
| 函数名称 | 功能说明 |
| operator[](重点) | 返回pos位置的字符,const string类对象调用 |
| begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
| rbegin + rend | rbegin获取最后一个字符的迭代器 + rend获取第一个字符前一个位置的迭代器 |
| 范围for | C++11支持更简洁的范围for的新遍历方式 |
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "hello";
// ==============================
// 1. operator[] 下标访问
// ==============================
cout << "1. operator[]:" << endl;
cout << s[0] << endl; // h
cout << s[1] << endl; // e
s[0] = 'H'; // 可以修改
cout << s << endl; // Hello
// ==============================
// 2. begin + end 迭代器
// ==============================
cout << "\n2. begin + end:" << endl;
string::iterator it = s.begin();
while (it != s.end()) {
cout << *it << " "; // 逐个输出
++it;
}
cout << endl;
// ==============================
// 3. rbegin + rend 反向迭代器
// ==============================
cout << "\n3. rbegin + rend:" << endl;
string::reverse_iterator rit;
for (rit = s.rbegin(); rit != s.rend(); ++rit) {
cout << *rit << " "; // 倒着输出:o l l e H
}
cout << endl;
// ==============================
// 4. 范围for C++11 最简单
// ==============================
cout << "\n4. 范围for:" << endl;
for (auto ch : s) {
cout << ch << " ";
}
cout << endl;
return 0;
}
• []:像数组一样用下标访问
• begin/end:从前往后遍历
• rbegin/rend:从后往前遍历
• 范围for:最简洁,C++11 首选
4. string类对象的修改操作
| 函数名称 | 功能说明 |
| push_back | 在字符串后尾插字符c |
| append | 在字符串后追加一个字符串 |
| operator+=(重点) | 在字符串后追加字符串str |
| c_str(重点) | 返回C格式字符串 |
| find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
|
rfind |
从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
| substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
注意:
1. 在string尾部追加字符时,s.push_back(c)、s.append(1, c)、s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
// 1. push_back:尾部插一个字符
string s1 = "hello";
s1.push_back('!'); // 尾插 '!'
cout << "push_back: " << s1 << endl; // hello!
// 2. append:尾部追加字符串
string s2 = "hello ";
s2.append("world"); // 追加 "world"
cout << "append: " << s2 << endl; // hello world
// 3. operator+=:最常用的追加
string s3 = "abc";
s3 += "def"; // 追加字符串
s3 += 'g'; // 追加字符
cout << "operator+=: " << s3 << endl; // abcdefg
// 4. c_str():转C风格字符串 const char*
string s4 = "test";
const char* cstr = s4.c_str();
cout << "c_str: " << cstr << endl; // test
// 5. find + npos:从前找字符/字符串
string s5 = "apple banana";
int pos = s5.find('b'); // 找 'b'
if (pos != string::npos)
cout << "find 'b' at: " << pos << endl; // 6
// 6. rfind:从后往前找
string s6 = "abacaba";
int rpos = s6.rfind('a');
cout << "rfind 'a' at: " << rpos << endl; // 6
// 7. substr:截取子串
string s7 = "hello world";
// 从位置 6 开始,取 5 个字符
string sub = s7.substr(6, 5);
cout << "substr: " << sub << endl; // world
return 0;
}
输出结果:
push_back: hello!
append: hello world
operator+=: abcdefg
c_str: test
find 'b' at: 6
rfind 'a' at: 6
substr: world
c_str() 详解:
c = C 语言 str = string 字符串
合起来:把 C++ 的 string → 转成 C 语言能用的字符串
为什么要转? C++ 里:string s = "hello"; C 语言里:只能用 const char*(字符指针)
很多老函数、系统函数、C 库函数只认识 C 语言的字符串,不认识 C++ string。所以必须用 .c_str() 转一下。
最简单例子
string s = "hello";
// 转成 C 语言字符串
const char* p = s.c_str();
内存结构图:
① string 对象 s(在栈上)
s: [ 指针 ] → 指向堆里的字符数据
[ 长度 ]
[ 容量 ]
② 堆里的真实字符数据
堆: 'h' 'e' 'l' 'l' 'o' '\0'
↑
这个地址就是 c_str() 返回的
③ p 指针(栈上)
p: 存储的就是上面那个 'h' 的地址
连起来看(终极版)
【栈内存】
+----------------+ +----------------------------+
| string s | | const char* p |
| | | |
| ┌──────────┐ | | ┌──────────────────────┐ |
| │ 指针 ├──┼--------┼─→│ 地址:指向 'h' │ |
| ├──────────┤ | | └──────────────────────┘ |
| │ 长度=5 | | | |
| ├──────────┤ | +----------------------------+
| │ 容量≥5 | |
| └──────────┘ |
+----------------+
│
▼
【堆内存】
+-----------------------------+
| 'h' 'e' 'l' 'l' 'o' '\0' |
+-----------------------------+
↑
└── 这个地址 = s.c_str()
• s 是 C++ string ;p 是 C 风格字符串(const char*)
• string s:是个对象,里面包着指针、长度、容量。
• s.c_str():返回的是对象里面那个指针,指向真实的字符数组。
• const char* p:只是接住了这个指针。
• c_str() 返回的是 const char*,不能修改内容。
• 一旦 string 发生扩容、修改,c_str() 的地址可能失效。
最常见用途:比如输出文件、打开文件、传给 C 函数:
string filename = "test.txt";
FILE* fp = fopen(filename.c_str(), "r");
这里必须用 .c_str(),否则编译报错。
一句话总结:c_str() 就是 C++ string 转 C 字符串的接口。
5. string类非成员函数
| 函数 | 功能说明 |
| operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
| operator>>(重点) | 输入运算符重载 |
| operator<<(重点) | 输出运算符重载 |
| getline(重点) | 获取一行字符串 |
| relational operators(重点) | 大小比较 |
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
// ==============================
// 1. operator+ 字符串拼接
// ==============================
string s1 = "hello";
string s2 = " world";
string s3 = s1 + s2; // 产生临时对象,效率低,少用
cout << "operator+: " << s3 << endl; // hello world
// ==============================
// 2. operator>> 输入字符串(空格/回车截止)
// ==============================
string s4;
cout << "请输入字符串(不含空格):";
cin >> s4;
cout << "operator>>: " << s4 << endl;
// ==============================
// 3. operator<< 输出字符串
// ==============================
string s5 = "test output";
cout << "operator<<: " << s5 << endl;
// ==============================
// 4. getline 读取一整行(包括空格)
// ==============================
string s6;
cin.ignore(); // 忽略上一次 cin 留下的换行
cout << "请输入一行字符串(可含空格):";
getline(cin, s6);
cout << "getline: " << s6 << endl;
// ==============================
// 5. relational operators 大小比较(按字典序)
// ==============================
string a = "apple";
string b = "banana";
string c = "apple";
cout << "a < b ? " << (a < b) << endl; // 1
cout << "a > b ? " << (a > b) << endl; // 0
cout << "a == c ? " << (a == c) << endl; // 1
cout << "a != b ? " << (a != b) << endl; // 1
return 0;
}
重点一句话总结
1. operator+:拼接字符串,但效率低,推荐用 += 代替
2. >>:读字符串,遇到空格/回车就停
3. <<:正常输出 string
4. getline:读一整行,包括空格,最常用在读取带空格输入
5. == != < > <= >=:按字典序比较,直接用就行
string 字符串拼接问题
#include <iostream>
#include <string>
using namespace std;
void test_string8()
{
string s1("hello");
// 合法:string + const char*
string s2 = s1 + "world";
cout << s2 << endl; // 输出:helloworld
// 非法!const char* + string 不支持
// "world" 是 const char*,没有重载 operator+(string)
// string s3 = "world" + s1;
// cout << s3 << endl;
}
关键点:
• s1 + "world" ✔:string 重载了 operator+(const char*)
• "world" + s1 ❌:字符串常量是const char*,没有重载+,不能和string相加
getline + rfind 求最后一个单词长度
int main()
{
string str;
// 以 * 作为结束符读取一行
getline(cin, str, '*');
// 从后往前找最后一个空格
size_t pos = str.rfind(' ');
// 最后一个单词长度 = 总长度 - (空格位置+1)
cout << str.size() - (pos + 1) << endl;
return 0;
}
作用:输入一串带空格的字符串,以*结束,输出最后一个单词的长度。
auto 关键字(C++11)
int func1()
{
return 10;
}
// 错误:C++ 中函数形参不能用 auto
// void func0(auto a = 0)
// {}
// auto 做返回值:由返回值推导类型
auto func2()
{
return func1(); // 推导为 int
}
auto func3()
{
return func2(); // 推导为 int
}
int main()
{
int a = 10;
auto b = a; // int
auto c = 'a'; // char
auto d = func1(); // int
// 错误:auto 必须初始化才能推导
// auto e;
// 查看类型
cout << typeid(b).name() << endl; // int
cout << typeid(c).name() << endl; // char
cout << typeid(d).name() << endl; // int
// 错误:auto 不能用于数组定义
// auto array[] = { 4,5,6 };
auto ret = func3(); // int
auto 规则:
1. 必须初始化,编译器从初始化表达式推导类型
2. 不能做函数形参类型(C++20 概念/模板除外)
3. 不能定义auto 数组
4. 可做函数返回值,支持链式推导
范围 for(C++11)
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98 遍历
for (int i = 0; i < sizeof(array)/sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array)/sizeof(array[0]); ++i)
{
cout << array[i] << endl;
}
// C++11 范围 for
// & 引用:才能修改原数组
for (auto& e : array)
e *= 2;
// 值传递:只读打印
for (auto e : array)
cout << e << " ";
return 0;
}
范围 for 要点:
• for(元素 : 数组/容器)
• 要修改元素:用 auto&
• 只读取:用 auto 或 const auto&
• 适用于:数组、string、vector、list 等所有容器
cin.ignore()
1. 为什么要用 ignore?
先看这个坑:你先用了 cin >>,再用 getline,getline 会直接跳过,不等待你输入。
原因:cin >> 读到空格 / 回车就停,但它只拿走了前面的字符,把回车留在了输入缓冲区里,后面 getline 一进来,看到剩下的回车,以为你输入完了,就直接空着结束了。
2. cin.ignore() 是干嘛的? 把缓冲区里多余的换行/字符“吃掉”,不让它干扰后面的 getline。
作用:扔掉缓冲区里的 1 个字符(通常就是那个碍事的回车)
3. 最经典场景(必背)
string s;
cin >> s; // 输入后会留下一个回车
cin.ignore(); // 把回车扔掉!
getline(cin, s); // 现在就能正常输入一整行了
4. 更安全、考试常用写法(清空一整行)
cin.ignore(numeric_limits<streamsize>::max(), '\n');
意思:一直扔字符,直到遇到换行,把整行垃圾清空,用这个基本不会出错。
5. 一句话总结
• cin >> 会留回车 getline 会被回车坑 cin.ignore() = 把回车吃掉,清空路障
getline
getline 就是:读取一整行输入,包括空格。
1. 它和 cin >> 的区别:cin >> 字符串,读到 空格 / 回车 就停,只能读一个单词。
getline(cin, 字符串):读到 回车 才停,能读一整句话,带空格。
2. 最简单例子
string str;
getline(cin, str);
cout << str << endl;
你输入:我 爱 编 程 ,它会全部读进去,输出:我 爱 编 程
如果用 cin >>,只会读到:我
3. 最常用场景:读带空格的名字,读一句话,读地址。只要内容里有空格,就用 getline
4. ignore 为什么存在?
因为: cin >> 读完会留下一个回车,下一条 getline 看到回车,以为你直接回车了,就读到空字符串
所以:先用 cin → 再用 getline,中间必须加 cin.ignore()
超级总结
• cin >>:读单词,遇到空格停
• getline:读一整行,包括空格
• cin.ignore():清掉前面残留的回车,让 getline 正常工作
relational operators
就是用来比较两个字符串大小的符号:==、!=、<、>、<=、>=
2. 怎么比?(按什么规则?):按字典序(字母顺序)比较
从第一个字符开始,逐个比 ASCII 码:a < b,A < a(大写 < 小写),前面都一样,短的更小。
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "apple";
string s2 = "banana";
string s3 = "apple";
string s4 = "app";
// == 等于
cout << (s1 == s2) << endl; // 0
cout << (s1 == s3) << endl; // 1
// != 不等于
cout << (s1 != s2) << endl; // 1
// < 字典序更小
cout << (s1 < s2) << endl; // 1(apple < banana)
cout << (s4 < s1) << endl; // 1(app < apple)
// > 字典序更大
cout << (s2 > s1) << endl; // 1
return 0;
}
string 的关系运算符,是按内容比较,不是比地址!
string a = "abc";
string b = "abc";
if (a == b) // 成立!因为内容一样
这和 C 语言的 char* 完全不一样,C 语言比的是指针地址,C++ string 比的是字符串内容。
总结:relational operators = 字符串比较符号,按字典序比内容,直接用就行。
2.4 vs和g++下string结构的说明
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
vs下string的结构
• string总共占28个字节。
• 内部有一个联合体,用来定义字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放。
当字符串长度大于等于16时,从堆上开辟空间。
• 这种设计的合理性:大多数情况下字符串的长度都小于16,string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
• 还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间的总容量,以及一个指针做其他事情。故总共占16+4+4+4=28个字节。
union _Bxty
{
// storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
g++下string的结构
• G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间。
• 堆空间内部包含了如下字段:空间总大小,字符串有效长度,引用计数
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
• 指向堆空间的指针,用来存储字符串。
3. string类的模拟实现
3.1 经典的string类问题
在面试中,面试官喜欢让学生自己模拟实现string类,主要是实现其构造、拷贝构造、赋值运算符重载以及析构函数。
// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非法
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello world!!!");
String s2(s1); // 这里会出现问题!
}
问题分析:
上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的。当用s1构造s2时,编译器会调用默认的拷贝构造,最终导致s1、s2共用同一块内存空间。在释放时,同一块空间被释放多次,引起程序崩溃,这种拷贝方式称为浅拷贝。
3.2 浅拷贝
• 浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源。当一个对象销毁时,就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时,就会发生访问违规。
• 可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。
3.3 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须显式给出,一般情况都是按照深拷贝方式提供。
3.3.1 传统版写法的String类
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
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()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
3.3.2 现代版写法的String类
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值哪个实现比较好?
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
3.3 写时拷贝(了解)
• 写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
• 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1;每增加一个对象使用该资源,就给计数增加1;当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源。如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
4. 经典例题与练习
4.1 仅仅反转字母
class Solution {
public:
bool isLetter(char ch)
{
if(ch >= 'a' && ch <= 'z')
return true;
if(ch >= 'A' && ch <= 'Z')
return true;
return false;
}
string reverseOnlyLetters(string S) {
if(S.empty())
return S;
size_t begin = 0, end = S.size()-1;
while(begin < end)
{
while(begin < end && !isLetter(S[begin]))
++begin;
while(begin < end && !isLetter(S[end]))
--end;
swap(S[begin], S[end]);
++begin;
--end;
}
return S;
}
};
4.2 找字符串中第一个只出现一次的字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
输入: s = "loveleetcode" 输出: 2
class Solution {
public:
int firstUniqChar(string s) {
int count[26] = {0};
// 统计次数
for (auto ch : s) {
count[ch - 'a']++;
}
for (int i = 0; i < s.size(); ++i) {
if (count[s[i] - 'a'] == 1)
return i;
}
return -1;
}
};
4.3 字符串里最后一个单词的长度
#include<iostream>
#include<string>
using namespace std;
int main()
{
string line;
// 不要使用cin>>line,因为它遇到空格就结束了
// while(cin>>line)
while(getline(cin, line))
{
size_t pos = line.rfind(' ');
cout<<line.size()-pos-1<<endl;
}
return 0;
}
#include <iostream>
using namespace std;
int main() {
string s;
while (cin >> s) {
;
}
cout << s.size();
return 0;
}
4.4 验证一个字符串是否是回文
class Solution {
public:
// 判断是否字母或数字
bool isLetterOrNumber(char ch)
{
return (ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z');
}
bool isPalindrome(string s) {
// 先小写字母转换成大写,再进行判断
for(auto& ch : s)
{
if(ch >= 'a' && ch <= 'z')
ch -= 32;
}
int begin = 0, end = s.size()-1;
while(begin < end)
{
while(begin < end && !isLetterOrNumber(s[begin])) // 左指针跳过非字母数字
++begin;
while(begin < end && !isLetterOrNumber(s[end])) // 右指针跳过非字母数字
--end;
if(s[begin] != s[end])
{
return false;
}
else
{
++begin;
--end;
}
}
return true;
}
};
class Solution {
public:
bool isPalindrome(string s) {
string str;
for (char ch : s) {
if (isalnum(ch))
str += tolower(ch);
}
// 遍历原字符串 s,只保留字母和数字(isalnum 判断)
// 并统一转为小写(tolower),存入新字符串 str。
int left = 0, right = str.size() - 1;
while (left < right) {
if (str[left] != str[right])
return false;
++left;
--right;
}
return true;
}
};
4.5 字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
方法一:
class Solution {
public:
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
int next = 0; // 进位
while (end1 >= 0 || end2 >= 0) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10; // 新进位(0或1)
ret = ret % 10; // 当前位结果
str.insert(str.begin(), '0' + ret);
}
// 最后如果还有进位,要多加一个 1
if (next == 1)
str.insert(str.begin(), '1');
return str;
}
};
从后往前加,模拟手算, 用 next 存进位, insert 插在开头,保证数字顺序正确, 一个数字读完了就补 0,继续加, 能处理无限大数字,不会溢出
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
int next = 0;
// 两个数都没走完就继续加
while (end1 >= 0 || end2 >= 0) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next; // 加 + 进位
next = ret / 10; // 新进位
ret %= 10; // 当前位
str += ('0' + ret); // 🔥尾插(直接往后加,超快)
}
if (next == 1)
str += '1'; // 最后还有进位,再补1
reverse(str.begin(), str.end()); // 🔥反转一次就正了
return str;
}
str += ... 是尾插,O(1) 快;reverse 只做一次,O(n) 可以接受
上一段写法:str.insert(str.begin(), ...) 是头插,每插一次都要把整个字符串往后挪,总复杂度 O(n²),慢
• 从后往前加 → 得到的是倒序数字;用 += 尾插 + reverse → 最标准、最高效、面试最爱写
4.6 反转字符串
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于
k个,则将剩余字符全部反转。 - 如果剩余字符小于
2k但大于或等于k个,则反转前k个字符,其余字符保持原样。
输入:s = "abcdefg", k = 2 输出:"bacdfeg"
输入:s = "abcd", k = 2 输出:"bacd"
class Solution {
public:
string reverseStr(string s, int k) {
int n=s.size();
// 每次跳 2k 步,对应题目:每2k个字符处理一组
for(int i=0;i<n;i+=k*2)
{
reverse(s.begin()+i,s.begin()+min(i+k,n));
// 核心:反转从 i 开始的 k 个字符
// min(i+k, n)是为了处理最后一段不足k个的情况,剩下 <k 个就反转到末尾
}
return s;
}
};
// 步长 2k,每次只反前 k 个,最后不够 k 就全反
4.7 反转字符串中的单词
给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入:s = "Let's take LeetCode contest" 输出:"s'teL ekat edoCteeL tsetnoc"
class Solution {
public:
string reverseWords(string s) {
int n = s.size();
int i = 0;
while (i < n) {
// 找单词开头
while (i < n && s[i] == ' ') i++;
int left = i;
// 找单词结尾
while (i < n && s[i] != ' ') i++;
int right = i - 1; // 单词最后一个字符下标
// 反转这个单词
reverse(s.begin() + left, s.begin() + right + 1);
// reverse 是左闭右开,反转从 left 到 right 的所有字符,单词就被反转了
}
return s;
}
};
举例:
字符串:"hello world"
1. i 从 0 开始,跳过空格(这里没有)
2. left = 0
3. 走到空格下标 5 停下
4. right = 4
5. reverse(0,5) → "olleh"
6. i 继续走,跳过空格
7. left = 6
8. 走到结尾停下
9. right = 10
10. reverse(6,11) → "world" → "dlrow"
最终:"olleh dlrow"
4.8 字符串相乘
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
整体思路(最关键)
1. 两个数相乘,结果最多有 m + n 位
2. 先开一个 res 字符串,长度 m+n,全部填 '0'
3. 从后往前,让 num1 的每一位 × num2 的每一位
4. 乘出来的结果,放在 res[i+j+1] 这个位置
5. 每次都算上原来的值 + 进位
6. 最后去掉前面多余的 0
class Solution {
public:
string multiply(string num1, string num2) {
// 两个数相乘,结果长度最多 = 长度之和
int m = num1.size(), n = num2.size();
string res(m + n, '0'); // 先全初始化为 '0'
// 从后往前,逐位相乘,遍历 num1 从最后一位开始
for (int i = m - 1; i >= 0; i--) {
int carry = 0; // 每处理完 num1 的一位,进位要清零
// 遍历 num2 从最后一位开始
for (int j = n - 1; j >= 0; j--) {
// 当前乘积 + 原来res里的值 + 进位
int mul = (num1[i] - '0') * (num2[j] - '0');
int sum = mul + (res[i + j + 1] - '0') + carry;
res[i + j + 1] = sum % 10 + '0'; // 当前位,保留个位,放到 i+j+1 位置
carry = sum / 10; // 进位,十位及以上作为新的进位
}
res[i] += carry; // 剩下的进位,内层循环结束后,还有进位,直接加到 res[i] 上
}
// 去掉前面的 0
int start = 0;
while (start < res.size() && res[start] == '0') {
start++;
}
// 全是 0 的情况
if (start == res.size()) return "0";
return res.substr(start); // 从 start 截取到末尾,就是最终结果。
}
};
这段代码就是模拟竖式乘法:
• 每一位 × 每一位
• 结果放在正确位置
• 加上原来的值和进位
• 最后去 0
更多推荐

所有评论(0)