【C++】深度实战:模拟 String 类全部核心操作

📌 相关专栏

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • [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;  // 总容量
};

🐾 关键要点:

  1. 引用返回 :返回 char& 而不是 char,使得 s1[0] = 'x' 可以修改原字符串

  2. 越界检查 :用 assert 或抛异常保证安全性

  3. 成员变量 :_str 指向动态分配的内存,_size 记录有效字符,_capacity 记录总容量

  4. 返回值生命周期 :_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;
}

🐶 🐾 ✨ 🐾 🐶


  1. 欢迎留言交流
  2. 期待你的评论与建议
  3. 留下你的想法吧

谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论