📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- [1. string 的多种构造方式](#1. string 的多种构造方式)
- [2. string 的三种遍历方法](#2. string 的三种遍历方法)
-
- [2.1 下标 + operator\[\] 遍历](#2.1 下标 + operator[] 遍历)
- [2.2 迭代器遍历](#2.2 迭代器遍历)
- [2.3 范围 for 遍历(C++11)](#2.3 范围 for 遍历(C++11))
- [3. auto 关键字详解](#3. auto 关键字详解)
- [4. 容量操作:size/capacity/resize/reserve](#4. 容量操作:size/capacity/resize/reserve)
-
- [4.1 size() / length() / capacity()](#4.1 size() / length() / capacity())
- [4.2 empty() 和 clear()](#4.2 empty() 和 clear())
- [4.3 resize():调整有效字符个数](#4.3 resize():调整有效字符个数)
- [4.4 reserve():预分配空间(避免频繁扩容)](#4.4 reserve():预分配空间(避免频繁扩容))
- [5. 字符串修改:追加/插入/删除/替换](#5. 字符串修改:追加/插入/删除/替换)
-
- [5.1 尾部追加:+= / push_back / append](#5.1 尾部追加:+= / push_back / append)
- [5.2 插入:insert](#5.2 插入:insert)
- [5.3 删除:erase](#5.3 删除:erase)
- [5.4 替换:replace](#5.4 替换:replace)
- [6. 查找与截取:find/substr](#6. 查找与截取:find/substr)
-
- [6.1 find():查找字符或子串](#6.1 find():查找字符或子串)
- [6.2 substr():截取子串](#6.2 substr():截取子串)
- [7. C风格转换:c_str()](#7. C风格转换:c_str())
- [8. 整行输入:getline()](#8. 整行输入:getline())
- [9. 模拟实现思路:operator\[\] 的设计](#9. 模拟实现思路:operator[] 的设计)
- 总结
- 本文所有代码
前言
C++ 标准库中的 std::string 是我们日常处理字符串的利器。但你真的了解它的全部构造方式吗?迭代器和指针到底是什么关系?resize 和 reserve 有什么区别?auto 关键字有哪些使用陷阱?
接下来我们一起来学习吧!!
🐶 🐾 ✨ 🐾 🐶
1. string 的多种构造方式
string 类提供了丰富的构造函数,满足不同场景的需求:
cpp
void Test_string1()
{
// 1. 默认构造:空字符串
string s1;
// 2. 用 C 风格字符串构造(最常用)
string s2("hello World");
// 3. 重复字符构造:n 个相同字符
string s3(5, 'a'); // "aaaaa"
// 4. 拷贝构造:用已有 string 创建新对象
string s4(s2); // "hello World"
// 5. 部分拷贝:从指定位置拷贝指定长度
string s5(s2, 6, 5); // 从下标 6 开始拷贝 5 个字符 → "World"
string s6(s2, 4, 100); // 长度超出则拷贝到末尾 → "o World"
string s7(s2, 4); // 不给长度,默认拷贝到末尾 → "o World"
// 6. 取 C 字符串前 n 个字符
string s8("hello World", 9); // 取前 9 个字符 → "hello Wor"
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
}
注意 :部分拷贝时,若指定的长度超过源字符串长度,会自动拷贝到末尾,不会越界。npos 是 string 类中定义的静态常量,代表"直到末尾"。
🐶 🐾 ✨ 🐾 🐶
2. string 的三种遍历方法
2.1 下标 + operator\[\] 遍历
cpp
string s1("Hello world");
s1[0] = 'h'; // 修改第一个字符
cout << s1 << endl; // "hello world"
for (size_t i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
🐾operator[] 会进行越界检查(断言),比 C 数组更安全。
2.2 迭代器遍历
迭代器是 STL 容器的通用访问方式,用法类似指针:
cpp
string s1("Hello world");
string::iterator it = s1.begin();
while (it != s1.end()) {
cout << *it << " "; // 解引用访问当前字符
it++;
}
cout << endl;
// 迭代器可以直接修改指向的对象
string::iterator it2 = s1.begin();
while (it2 != s1.end()) {
*it2 += 1; // 每个字符 ASCII +1
cout << *it2 << " ";
it2++;
}
cout << endl;
迭代器的通用性 :不仅适用于 string,也适用于 list、vector 等所有容器
cpp
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
list<int>::iterator lit = lt.begin();
while (lit != lt.end()) {
cout << *lit << " ";
lit++;
}
cout << endl;
🐾 反向迭代器:从尾到头遍历
cpp
auto rch = s1.rbegin(); // auto 简化类型书写
while (rch != s1.rend()) {
cout << *rch << " "; // 输出:d l r o w o l l e h
rch++; // 反向迭代器的 ++ 是向反方向移动
}
注意 :迭代器不一定是指针,但用法非常类似(解引用、自增)。标准库通过运算符重载实现了这一效果。
2.3 范围 for 遍历(C++11)
cpp
string s1("hello world");
for (auto it : s1) {
cout << it << " "; // 自动取容器数据赋值,自动迭代,自动判断结束
}
cout << endl;
// 范围 for 也适用于数组
int array[] = { 1, 2, 3, 4, 5 };
for (auto e : array) {
cout << e << " ";
}
🐶 🐾 ✨ 🐾 🐶
3. auto 关键字详解
auto 可以自动推导变量类型,但有一些使用限制和细节:
cpp
void autolearning()
{
int a = 10;
auto b = a; // b 是 int
auto c = 'a'; // c 是 char
auto d = func3(); // d 是 int(func3 返回 int)
// auto e; // 错误:auto 变量必须有初始值
// 范围 for 中修改原对象需要加引用
string s1("Hello world");
for (auto ch : s1) { // ch 是拷贝,修改不影响原字符串
ch += 1;
}
cout << s1 << endl; // 仍是 "Hello world"
for (auto& ch : s1) { // 加上引用,ch 是别名
ch += 1; // 修改原字符串
}
cout << s1 << endl; // "Ifmmp xpsme"
// 同一行声明多个变量,类型必须一致
auto aa = 1, bb = 2; // 正确
// auto cc = 3, dd = 4.0; // 错误:类型不同
// auto 不能声明数组
// auto array[] = { 1, 2, 3 }; // 错误
}
auto 的限制:
- auto变量必须初始化,不能只声明不赋值;
- 同一行的多个变量,类型也必须要一致;
- 不能直接定义数组,只能用auto接收数组指针;
🐶 🐾 ✨ 🐾 🐶
4. 容量操作:size/capacity/resize/reserve
4.1 size() / length() / capacity()
cpp
string s1("Hello world");
cout << s1.size() << endl; // 11(有效字符,不含 '\0')
cout << s1.length() << endl; // 11(与 size 完全等价)
cout << s1.capacity() << endl; // 通常 ≥ size(VS 会做对齐)
4.2 empty() 和 clear()
cpp
cout << s1.empty() << endl; // 0(不为空)
s1.clear(); // 清空有效字符
cout << s1.size() << endl; // 0
cout << s1.capacity() << endl; // 容量不变(不释放内存)
cout << s1.empty() << endl; // 1(现在为空)
🐾 clear() 只清空内容,不改变底层容量,避免频繁的内存释放和重新分配。
4.3 resize():调整有效字符个数
cpp
string s3("Hello world");
s3.resize(5); // 截断为前 5 个字符
cout << s3 << endl; // "Hello"
cout << s3.size() << endl; // 5
cout << s3.capacity() << endl; // 容量不变
s3.resize(20, 'a'); // 扩展到 20 个字符,多出的填 'a'
cout << s3.size() << endl; // 20
cout << s3.capacity() << endl; // 容量可能增大(如 31)
规则 :resize 减少元素时容量不变;增加元素时可能触发扩容。
4.4 reserve():预分配空间(避免频繁扩容)
cpp
// 场景:知道要存 1000 个字符
string s;
s.reserve(1000); // 提前预留空间
for (int i = 0; i < 1000; ++i) {
s += 'a'; // 不会频繁扩容
}
// reserve 也可以用来扩容
string s4("hello world");
cout << s4.capacity() << endl; // 初始容量(如 15)
s4.reserve(20); // 扩容到至少 20(实际可能对齐到 31)
cout << s4.capacity() << endl; // 31
// reserve 不会缩容(VS 行为,g++ 可以缩容)
s4.reserve(5); // n < 当前容量
cout << s4.capacity() << endl; // 仍是 31(VS 不缩容)
建议 :如果预先知道字符串最终长度,使用 reserve 可以避免多次内存分配和拷贝,显著提升效率。
🐶 🐾 ✨ 🐾 🐶
5. 字符串修改:追加/插入/删除/替换
5.1 尾部追加:+= / push_back / append
cpp
string s1("Hello world");
s1.push_back('a'); // 追加单字符
s1.append("bdc"); // 追加字符串
s1.append(10, 'x'); // 追加 10 个 'x'
string s2("Hello");
s2 += ' '; // += 最简洁,推荐使用
s2 += "world";
5.2 插入:insert
cpp
string s3("Hello world");
s3.insert(0, "abc "); // 头插字符串
s3.insert(4, "def "); // 指定位置前插入
// 插入单个字符需要指定个数
s3.insert(7, 1, 'g'); // 在下标 7 前插入 1 个 'g'
s3.insert(s3.begin(), ' '); // 迭代器版本头插
注意 :头插 insert 需要移动后面所有字符,时间复杂度 O(n²),谨慎使用。
5.3 删除:erase
cpp
// 头删
s3.erase(0, 1); // 删除下标 0 开始的 1 个字符
s3.erase(s3.begin()); // 迭代器版本头删
// 尾删
s3.erase(s3.size() - 1, 1);
s3.erase(--s3.end()); // 迭代器版本尾删
// 删除多个
s3.erase(3, 5); // 从下标 3 删 5 个
s3.erase(3); // 不给长度则删除到末尾
🐾 和 insert 一样,头删 erase 也需要移动后面所有字符,时间复杂度 O(n²)。
5.4 替换:replace
cpp
string s4("Hello world");
// 少换多:将下标 5 的 1 个字符替换成 "&&&"
s4.replace(5, 1, "&&&"); // "Hello&&&world"
// 多换少:将下标 5 开始的 3 个字符替换成 "*"
s4.replace(5, 3, "*"); // "Hello*world"
实战 :将空格替换为 &
cpp
// 方法一:find + replace(逐个替换)
string s5("Hello world a b c d");
size_t pos = s5.find(" ");
while (pos != string::npos) {
s5.replace(pos, 1, "&");
pos = s5.find(" ", pos + 1); // 从上次位置后继续找
}
cout << s5 << endl;
// 方法二:空间换时间(更高效)
string s6("Hello world a b c d");
string tmp;
tmp.reserve(s6.size()); // 预分配空间
for (auto ch : s6) {
if (ch == ' ')
tmp += '&';
else
tmp += ch;
}
s6.swap(tmp); // 交换,比赋值更高效
cout << s6 << endl;
建议 :大量替换时,用"遍历+追加到新串"的方式比多次 replace 更高效,配合 reserve 效果更佳。swap 比直接赋值效率高(只交换内部指针)。
🐶 🐾 ✨ 🐾 🐶
6. 查找与截取:find/substr
6.1 find():查找字符或子串
cpp
string s1 = "hello world";
// 查找字符
size_t pos1 = s1.find('w');
if (pos1 != string::npos) {
cout << "'w'在位置:" << pos1 << endl; // 6
}
// 查找子串
size_t pos2 = s1.find("world");
if (pos2 != string::npos) {
cout << "world在位置:" << pos2 << endl; // 6
}
// 从指定位置开始查找
size_t pos3 = s1.find('o', 5); // 从下标 5(空格)往后找
cout << "'o'在位置:" << pos3 << endl; // 7
🐾 string::npos 是 size_t 类型的最大值(通常为 -1 的无符号表示),作为"未找到"的返回值。
6.2 substr():截取子串
cpp
string s2 = "Hello world";
string sub1 = s2.substr(6, 5); // 从下标 6 取 5 个字符 → "world"
string sub2 = s2.substr(2); // 从下标 2 取到末尾 → "llo world"
🐾 substr 不修改原字符串,返回新字符串。第二个参数若超出长度,自动截取到末尾。
🐶 🐾 ✨ 🐾 🐶
7. C风格转换:c_str()
当需要与 C 语言库函数交互时,c_str() 必不可少:
cpp
#include <cstring>
string s3 = "Hello world";
// printf 输出(不直接支持 string)
printf("%s\n", s3.c_str()); // 正确
// 调用 C 库函数 strlen
size_t len = strlen(s3.c_str());
cout << len << endl; // 11
🐾 c_str() 返回的指针指向内部字符数组(以 '\0' 结尾),但不应修改其内容。
🐶 🐾 ✨ 🐾 🐶
8. 整行输入:getline()
cin >> string 遇到空格就会停止,而 getline 可以读取一整行:
cpp
string s4;
// 不传第三个参数则默认以换行符结束
getline(cin, s4); // 读取一行,包含空格
// 可以指定终止符
getline(cin, s4, '*'); // 遇到 '*' 才停止
🐶 🐾 ✨ 🐾 🐶
9. 模拟实现思路:operator\[\] 的设计
核心思路
cpp
class string
{
public:
char& operator[] (size_t pos)
{
assert(pos < _size); // 越界检查
return _str[pos]; // 引用返回,可修改原字符
}
private:
char* _str; // 指向堆空间
size_t _size; // 有效字符个数
size_t _capacity; // 总容量
};
🐾 关键要点:
-
引用返回 :
返回 char& 而不是 char,使得 s1[0] = 'x' 可以修改原字符串 -
越界检查 :
用 assert 或抛异常保证安全性 -
成员变量 :
_str 指向动态分配的内存,_size 记录有效字符,_capacity 记录总容量 -
返回值生命周期 :
_str[pos] 指向堆上的内存,函数返回后不会被销毁,所以引用返回有效
🐶 🐾 ✨ 🐾 🐶
总结
std::string 的核心使用场景:
| 分类 | 核心接口 |
|---|---|
| 构造 | string()、string(const char*)、string(size_t, char)、拷贝构造、部分拷贝 |
| 遍历 | operator[ ]、迭代器、范围 for |
| 容量 | size()、capacity()、empty()、clear()、resize()、reserve() |
| 修改 | +=、push_back()、append()、insert()、erase()、replace() |
| 查找截取 | find()、substr() |
| 工具 | c_str()、getline() |
🐶 🐾 ✨ 🐾 🐶
本文所有代码
🐾test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<list>
using namespace std;
//string 类的构造形式
void Test_string1()
{
//1.空字符串构造(默认构造)
//string()
string s1; //构造空的 string 类对象s1,底层已初始化,不用手动加'\0'
//2.用C格式字符串构造(最常用,把 char* 转成 string)
//string(const char* s)
string s2("hello World"); //s2 = "hello World"
//3.重复字符构造(创建n个相同字符的字符串)
//string(size_t n, char c)
string s3(5, 'a'); //s3 = "aaaaa"(5个'a')
//4.拷贝构造(用已有的 string 创建新对象)
//string(const string & s)
string s4(s2); // s4 = "hello World"(和s2内容一样)
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
//5.拷贝构造的特殊形式(拷贝 pos 位置的往后的 npos 个字符)
//string (const string& str, size_t pos, size_t len = npos);
//(1)pos 位置拷贝部分字符
string s5(s2, 6, 5);//从下标为6(w)的位置往后拷贝5个字符
cout << s5 << endl;
//(2)pos位置一直拷贝到结尾
//第一种情况:写一个超过原字符串长度的
string s6(s2, 4, 100);
cout << s6 << endl;
//第二种情况:直接不写第三个实参(默认使用缺省值npos)
string s7(s2, 4);
cout << s7 << endl;
//6.取C语言字符串前n个字符
//string(const char* s, size_t n);
string s8("hello World", 9);//取前5个字符
cout << s8 << endl;
}
//class string
//{
//public:
// char& operator[] (size_t pos) //模拟实现 operator[]
// {
// assert(pos < _size);
// return _str[pos];
// //这里之所以能够引用返回是因为_str的生成是在堆上,出了函数不会被销毁
// }
//
//private:
// char* _str;
// size_t _size;
// size_t _capacity;
//};
void Test_string2()
{
//字符串遍历
//1. operator [] 下标访问
string s1("Hello world");
cout << s1 << endl;
s1[0] = 'h';
cout << s1 << endl;
//相比于数组这个越界有严格的检查
//s1[100];//断言
//遍历整个字符串
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
//2.迭代器遍历
string s1("Hello world");
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " "; //和指针的使用非常类似
//但是it不一定是指针,因为 * 是 operator * 进行了重载
it++;
}
cout << endl;
//虽然迭代器不能和指针划等于,但我们能理解为指针,因为用法非常类似
//所以迭代器也可以直接对指向对象本身进行修改(而后面要讲的关键字 auto 需要加上引用才能修改):
string::iterator it2 = s1.begin();
while (it2 != s1.end())
{
*it2 += 1;
cout << *it2 << " ";
it2++;
}
cout << endl;
cout << s1 << endl;
cout << endl;
//相对于 下标+[] 来说,迭代器更加通用,我们这里再来看看在链表中的使用
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
lit++;
}
cout << endl;
//反向迭代器
//string::reverse_iterator rch = s1.rbegin();
auto rch = s1.rbegin(); //这里auto的作用就能体现出来了,上面的类型比较长就可以用auto进行简化
while (rch != s1.rend())
{
cout << *rch << " ";
rch++; //此时反向时 ++ 已经被重载了,是反方向移动
}
//3.范围 for 遍历(C++11)
//自动取容器数据赋值,自动迭代++,自动判断结束
string s1("hello world");
for (auto it : s1)
{
cout << it << " ";
}
cout << endl;
//范围for也可以作用到数组上进行遍历
int array[] = { 1, 2, 3, 4, 5 };
for (auto e : array)
{
cout << e << " ";
}
}
//关键字 auto 的知识点讲解
// 不能做参数
//void func2(auto a) // error C3533: 参数不能为包含"auto"的类型
//{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
void autolearning()
{
int a = 10;
auto b = a; //关键字auto自动推断类型
auto c = 'a';
auto d = func3();
//auto e; // error C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
//需要注意的是:对于上面范围for遍历中,如果想对对象本身进行修改,必须加上引用
string s1("Hello world");
for (auto ch : s1) //这个可以理解成相当于把s1的所有字符挨个拷贝到ch中
//此时ch为局部变量,对其修改并不会影响s1的改变
{
ch += 1;
cout << ch << " ";
}
//之所以迭代器能直接修改,是因为我们可以把迭代器看成是指针,也就类似于自带引用功能
cout << endl;
cout << s1 << " ";
cout << endl;
for (auto& ch : s1)//加上引用后,ch的每个字符也就是s1里面字符的别名
{
ch += 1; //修改ch也就相当于是修改了s1
cout << ch << " ";
}
cout << endl;
cout << s1 << " ";
//当在同一行声明多个变量时,这些变量必须是相同的类型
auto aa = 1, bb = 2;
//auto cc = 3, dd = 4.0; //error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
//auto 不能直接用来声明数组
//auto array[] = { 4, 5, 6 }; //error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
}
void Test_string3()
{
// string 类对象的容量操作
// size 和 length:返回字符串有效字符长度
string s1("Hello world");
cout << s1.size() << endl; //不包含结尾的\0
cout << s1.length() << endl << endl;; //两者用法是完全一样的,但基本是用size来求长度
// capacity:返回空间总大小
string s2; //空串
cout << s1.capacity() << endl;
cout << s2.capacity() << endl << endl;; //在vs中大多数情况容量都是比字符串本身长度要大的,因为会进行对齐
// empty:检测字符串释放为空串,是返回true,否则返回false
cout << s1.empty() << endl;
cout << s2.empty() << endl << endl;;
// clear:清空有效字符
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;// clear()只是将 string 中有效字符清空,但不改变底层空间大小
// resize:将有效字符的个数该成n个,多出的空间用字符c填充
//注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,
//如果是将元素个数减少,底层空间总大小不变
string s3("Hello world");
s3.resize(5);
string::iterator ch = s3.begin();
while (ch != s3.end())
{
cout << *ch;
ch++;
}
cout << endl;
cout << s3.size() << endl;
cout << s3.capacity() << endl;
s3.resize(20, 'a');
for (size_t i = 0; i < s3.size(); i++)
{
cout << s3[i];
}
cout << endl;
cout << s3.size() << endl;
cout << s3.capacity() << endl << endl; //此时底层容量的大小就发生了改变
//reserve:为字符串预留空间,不改变有效元素个数(预分配空间)
//作用:如果我们已经知道一个字符串大概有多长,
//就可以先用 reserve() 预分配空间,避免后续扩容次数过多导致效率降低
string s;
// 已知要存1000个字符,提前预分配
s.reserve(1000);
// 后续拼接1000个字符,不会频繁扩容
for (int i = 0; i < 1000; ++i) {
s += 'a';
}
//reserve也可以用来增容
string s4("hello world");
cout << s4.size() << endl;
cout << s4.capacity() << endl;
s4.reserve(20);//会开的比20大,因为会进行对齐
cout << s4.size() << endl;
cout << s4.capacity() << endl;
//当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小
s4.reserve(5);//在 vs 上 reserve 不会缩容,但其他平台如 g++ 是可以缩容的
cout << s4.size() << endl;
cout << s4.capacity() << endl; //由于上面预留总空间到31,reserve的参数小于31,仍为31
}
void Test_string4()
{
//字符串修改
//尾部追加(operator+=/push_back/append)
string s1("Hello world");
s1.push_back('a'); //仅尾插一个字符
cout << s1 << endl;
s1.append("bdc"); //仅尾插一个字符串
cout << s1 << endl;
s1.append(10, 'x'); //尾插10个x
cout << s1 << endl << endl;
string s2("Hello"); //使用 += 更加方便
s2 += ' ';
s2 += "world";
cout << s2 << endl << endl;
//插入和删除:insert 和 erase
string s3("Hello world");
//头插数据:
s3.insert(0, "abc ");
cout << s3 << endl;
//指定位置之前插入数据:
s3.insert(4, "def "); //注意的是 pos 是下标
cout << s3 << endl;
//插入单个字符(insert设计比较有缺陷)
//string& insert(size_t pos, size_t n, char c);
//n > 1时插入n个c字符,n = 1时相当于就是插入单个字符,但不能没有n
//s3.insert(7, 'g'); //error:没有重载函数可以转换所有参数类型
s3.insert(7, 1, 'g');
cout << s3 << endl;
//iterator insert (iterator p, char c);
s3.insert(s3.begin(), ' ');
cout << s3 << endl << endl;
//但是头插insert要谨慎使用,因为insert需要将后面的字符挨个往后移动,时间复杂度为O(n^2)
//删除数据
//string& erase (size_t pos = 0, size_t len = npos);
//iterator erase(iterator p); //迭代器用法
//头删数据
s3.erase(0, 1);
cout << s3 << endl;
s3.erase(s3.begin()); //迭代器用法
cout << s3 << endl;
//尾删数据
s3.erase(s3.size() - 1, 1);
cout << s3 << endl;
s3.erase(--s3.end()); //迭代器用法
cout << s3 << endl;
//删除多个数据
s3.erase(3, 5);
cout << s3 << endl;
s3.erase(3); //若不给第二个实参,则len默认给缺省值npos,即删除对应位置后面的全部数据
cout << s3 << endl << endl;
//和insert一样,头删erase也要谨慎使用,因为需要将后面所有数据挨个往前移动,时间复杂度为O(n^2)
//字符串替换:replace() 修改指定位置内容
string s4("Hello world");
s4.replace(5, 1, "&&&");//把下标为5这个位置的1个字符替换成&&&,由于是少的位置替换多的字符
//这就说明了需要利用前面insert的逻辑将后面的字符往后移动
cout << s4 << endl;
s4.replace(5, 3, "*");//把下标为5这个位置开始的三个字符替换成*,由于是多的位置替换少的字符
//这也就说明了需要利用前面erase的逻辑将后面的字符往前移动
//练习:将所有空格全部替换为&
//第一种方法:find()查找
string s5("Hello world a b c d");
size_t pos = s5.find(" ");
while (pos != string::npos) //当find没有找到对应匹配项,则返回npos,但是npos是类中成员,需要用类访问
{
s5.replace(pos, 1, "&");
//pos = s5.find(" "); //pos改为下一个空格的下标
pos = s5.find(" ", pos + 1); //优化:从 pos + 1 的位置开始找,不需要再从头开始找
}
cout << s5 << endl;
//但是我们会发现上面这种方法会比较麻烦,每次只能找一个空格替换成&
//所以我们考虑用另一个字符串来存放替换后的结果
//第二种方法:空间换时间
string s6("Hello world a b c d");
string tmp;
tmp.reserve(s6.size()); //预分配空间,防止后续多次扩容
for (auto ch : s6)
{
if (ch == ' ')
{
tmp += '&';
}
else
{
tmp += ch;
}
}
//s6 = tmp; //最后赋值给s5
//也可以使用string中的swap()
s6.swap(tmp);
cout << s6 << endl;
}
//#include<cstring>
void Test_string5()
{
//字符串查找:find() 找字符/子串
//找字符
string s1 = "hello world";
// 1. 找字符'w'
size_t pos1 = s1.find('w');
if (pos1 != string::npos)
{
cout << "'w'在位置:" << pos1 << endl;// 输出6
}
// 2. 找子串"world"
size_t pos2 = s1.find("world");
if (pos2 != string::npos)
{
cout << "world在位置:" << pos2 << endl;// 输出6
}
// 3. 从下标5开始找字符'o'
size_t pos3 = s1.find('o', 5); // 从下标为5的位置 ' ' 往后找
cout << "'o'在位置:" << pos3 << endl << endl;// 输出7(s[7]是'o')
//子串截取:substr()从指定位置取指定长度
string s2 = "Hello world";
// 1. 从位置6开始,取5个字符
string sub1 = s2.substr(6, 5); // sub1 = "world"
cout << sub1 << endl;
// 2. 从位置2开始,取到末尾
string sub2 = s2.substr(2); // sub2 = "llo world"
cout << sub2 << endl << endl;
//C字符转换:c_str() 适配C语言库函数
string s3 = "Hello world";
//1. printf输出(printf不直接支持string)
//printf("%s\n", s3);//虽然是警告不是报错,但由于不支持string打印的结果也不是我们预期的
//warning C4477: "printf": 格式字符串"%s"需要类型"char *"的参数,但可变参数 1 拥有了类型"std::string"
printf("%s\n", s3.c_str());
// 2. 调用C库函数strlen(需要包含<cstring>)
size_t len = strlen(s3.c_str());
cout << len << endl << endl;
//整行输入:getline() 读取带空格的字符串
//istream& getline (istream& is, string& str, char delim);
//不传第三个实参则默认回车结束,可以指定终止符
string s4;
//cin >> s4; //cin读取字符串时,当遇到空格就会停止
//如果空格后面仍有字符串也不会被读取到,而是存到缓冲区里
getline(cin, s4, '*');
}
void test()
{
string s1("hello world");
s1[5] = 'a';
cout << s1 << endl;
}
int main()
{
//Test_string1();
//Test_string2();
//autolearning();
//Test_string3();
//Test_string4();
//Test_string5();
test();
return 0;
}
🐶 🐾 ✨ 🐾 🐶
- 欢迎留言交流
- 期待你的评论与建议
- 留下你的想法吧

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论
