首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【c++】STL-string容器的使用

【c++】STL-string容器的使用

作者头像
mosheng
发布2026-01-14 18:26:53
发布2026-01-14 18:26:53
1470
举报
文章被收录于专栏:c++c++

hello~ 很高兴见到大家! 这次带来的是C++中关于STL-string容器的使用这部分的一些知识点,如果对你有所帮助的话,可否留下你的三连呢? 个 人 主 页: 默|笙

一、STL

1.1 什么是STL

STL(Standard Template Library,标准模板库) 是 C++ 标准库的重要组成部分,提供了通用的模板类和函数,用于实现数据结构和算法。它的核心思想是泛型编程,通过模板(Template)让代码具备高度复用性。

1.2 STL的版本

  1. HP版本:STL首个实现版本,开源,其他版本通常都是基于此版本来实现的。
  2. P.J.版本:继承自HP版本,不开源,可读性低,符号命名比较怪异,被Windows Visual C++采用。
  3. RW版本:继承自HP版本,不开源,可读性一般,被C+ + Builder 采用。
  4. SGI版本:继承自HP版本,开源,可读性高,被GCC(Linux)采用。

1.3 STL的六大组件

在这里插入图片描述
在这里插入图片描述
  1. 容器:用于存储和管理数据的类模板
  2. 算法:一系列用于处理容器中元素的函数模板
  3. 迭代器与指针类似,用于遍历容器中的元素,是容器与算法之间沟通的桥梁。
  4. 仿函数:通过重载函数调用运算符 () 实现,可像函数一样调用的类或结构体(不是真函数,它是对象,但行为像函数),能用于自定义算法的行为。
  5. 配接器:也叫适配器,用于修改容器、迭代器或仿函数的接口,使其符合特定需求 。
  6. 空间配置器:负责容器的内存分配和释放,管理容器的内存资源。

二、string类及其使用

string

定义: std :: string是c++标准库提供的动态字符串类(位于头文件 < string> 中),它封装了对字符序列的底层操作,以成员函数和运算符重载为用户提供了一套安全、便捷的接口。

在这里插入图片描述
在这里插入图片描述

  1. 可以将其理解为一个管理字符数组的顺序表
  2. string 实际上是 basic_string< char>此类型的别名,是basic_string模板char类型的实例化版本。
  • 使用的时候包含头文件< iostream>,后进行命名空间的展开 using namespace std; 。
  • 有很多接口都不常用,不需要我们去记忆,在需要使用的时候动手去查一查就行,目录里标注星号的一般没那么重要。string<—

2.1 成员常量

在这里插入图片描述
在这里插入图片描述

  1. 它是一个静态成员常量值,npos 的值被设置为无符号整数(size_t)的最大值
  2. 当此值用作 string 成员函数中的 len(长度参数) 时,表示直至这个字符串的末尾
  3. 作为返回值,它通常用于表示未找到匹配项
  4. 这个常量被定义为数值 -1,但由于 size_t 是无符号整数类型,因此该值实际上是该类型所能表示的最大可能值。
  • -1 的补码被无符号整数类型解析为最大值

2.2 可以默认生成的特殊成员函数

在这里插入图片描述
在这里插入图片描述
1. 构造函数
在这里插入图片描述
在这里插入图片描述
  1. 默认构造函数:构造一个长度为0个字符的空字符串。
  2. 拷贝构造函数:以 str 为蓝本,拷贝构造一个字符串。
  3. *子串构造函数:以 str 为蓝本,从索引为 pos 的字符起,拷贝 len 个字符,如果没有传实参给 len ,将会使用默认值 npos。

  1. 若 len 的值超过剩余长度(比如默认值 npos),则截断至末尾
  2. 若 pos 越界时,构造函数会抛出异常,不会静默处理。
  3. c风格字符串构造:复制指针 s 指向的以’\0’ 结尾的字符串序列(遇到 ‘\0’ 停止)。
  4. *缓冲区构造:复制指针 s 指向的字符串里的前 n 个字符(无论是否有 ‘\0’。
  5. *填充构造函数:用字符 c 的连续 n 个副本填充字符串。
  6. *范围构造函数:按顺序复制迭代器区间 [first, last)(左闭右开)内的所有字符。关于迭代器下文将会讲到,它类似指针但不是指针。
代码语言:javascript
复制
//默认构造
string s1;
cout << "s1:" << s1 << endl;
s1 = "abcdefg";
//拷贝构造
string s2 = s1;//string s2(s1);
//子串构造
string s3(s1, 2);
//c风格
string s4("abc\0defg");
//从缓冲区
string s5("abc\0defg", 6);
//填充
string s6(5, 'a');
//范围
string s7(s2.begin(), s2.end());
cout << "s2:"<< s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;

执行结果:

在这里插入图片描述
在这里插入图片描述
  • 在 std :: string 中,一般情况下不会显式存储’\0’,但在与c风格函数交互时会根据需要进行存储。
2. 析构函数
在这里插入图片描述
在这里插入图片描述

析构函数会在 std::string 对象声明周期结束时(如离开作用域,被显式删除),会自动调用析构函数,无需手动干预,仅作了解即可。

3. 赋值重载函数
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
string s1("abcdefg");
string s2;
cout << "s2:" << s2 << endl;
//string,自赋值是安全的(s2 = s2)
s2 = s1;
cout << "string:"<< "s2:" << s2 << endl;
//c_string
s2 = "abc\0defg";
cout << "string_c:" << "s2:" << s2 << endl;
//character
s2 = 'a';
cout << "character:" << "s2:" << s2 << endl;

执行结果:

在这里插入图片描述
在这里插入图片描述
  • s2 首先得存在,若 s2 不存在就进行赋值操作,编译器会强制调用构造函数,赋值操作将变为初始化操作:string s2 = s1。

2.3 迭代器成员函数

迭代器:在 C++ 中,迭代器(Iterator) 是一种行为类似于指针的对象,它提供了一种统一的方式来遍历和操作容器(如 std::vector、std::list、std::map 等)中的元素,而无需暴露容器的底层实现细节。 意义:

  1. 统一且类似的方式修改容器。在之后的遍历操作中会详细讲解。
  2. 算法脱离具体底层结构,和底层结构解耦。(降低耦合,降低关联性,不用担心底层如何存储,实现)
在这里插入图片描述
在这里插入图片描述
1.正向迭代器(begin从前往后遍历):
在这里插入图片描述
在这里插入图片描述
  1. begin():返回一个指向字符串首字符(第一个元素) 的非常量迭代器。
  2. end():返回一个指向字符串最后一个字符的下一个位置的非常量迭代器。
  3. *cbegin()和*cend():用于返回正向常量迭代器,相比于begin()/end() 加 const 修饰返回正向常量迭代器,它的语义更明确(好区分),避免意外修改。
代码语言:javascript
复制
string s1 = "abcdefg";
string::iterator it1 = s1.begin();
string::const_iterator it1 = s1.begin();
string::const_iterator it1 = s1.cbegin();
2.反向(reverse)迭代器(rbegin从后往前遍历):
在这里插入图片描述
在这里插入图片描述
  1. rbegin():返回一个指向字符串最后一个字符的非常量迭代器(反向开头)。
  2. rend():返回一个指向字符串首字符的前一个位置的非常量迭代器。
  3. *crbegin()和 *crend():用于返回反向常量迭代器,比 rbegin()/rend() 加 const 修饰返回反向常量迭代器更清晰(好区分)安全。
代码语言:javascript
复制
string::reverse_iterator it3 = s1.rbegin();
string::const_reverse_iterator it3 = s1.rbegin();
string::const_reverse_iterator it3 = s1.crbegin();

关于常量迭代器:关于新的类型const_iterator与const_reverse_iterator,它们限制只能对“指向”的元素进行只读访问,而不是指它本身不可修改(const iterator 是本身不可修改)。它们能够很好的保护数据。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
//正向
cout << "正向:" << endl;
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
	cout << *it1 << " ";
	it1++;
}
cout << endl;
//反向
cout << "反向" << endl;
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{
	cout << *it2 << " ";
	it2++;
}

执行结果:

在这里插入图片描述
在这里插入图片描述

2.4 遍历

2.4.1 下标 + []
代码语言:javascript
复制
string s1 = "abcdefg";
//下标 + []
for (size_t i = 0; i < s1.size(); i++)
{
	cout << s1[i] << endl;
}
2.4.2 迭代器
代码语言:javascript
复制
	string s1 = "abcdefg";
	//迭代器
	//[begin(), end())
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		(*it1)++;
		++it1;
	}
	cout << s1 << endl;

对于 string 而言,下标 + [] 的确更加直白简单,但是对于其他容器如链表(空间一般不连续),下标 + [] 不再适用,而迭代器适用于一切容器,是容器的主流遍历方式

2.4.3 范围 for(c++11)
1. auto关键字

在c/c++早期,auto的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量。后来在 c++11中,它被赋予新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量的类型必须由编译器在编译时期经过推导而得

  1. auto能够自动推导类型。
在这里插入图片描述
在这里插入图片描述
  1. auto 和 auto*声明指针类型没有什么不同,但是在声明引用类型时,必须添加 ‘&’
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int x = 10;
int& y = x;
auto z1 = y;//auto推演的是y本身的int类型,而非int&类型
z1++;
cout << y << endl;
auto& z2 = y;//在声明引用类型时,要加上&
z2++;
cout << y << endl;

执行结果:

在这里插入图片描述
在这里插入图片描述
  1. 推导过程顺序独立进行:同一行申明多个变量时,这些变量类型必须相同,否则编译器会报错,因为编译器实际只会对第一个变量进行推导,然后用推导出来的类型定义其他变量。
代码语言:javascript
复制
auto x = 5, y = 10;      // 合法:x 和 y 均为 int
auto a = 1, b = 3.14;    // 错误:int 和 double 类型不匹配,编译报错
  1. auto不能作为函数的参数,编译报错,但是可以作返回值。谨慎使用。
  2. auto不能直接用来声明数组,编译报错。
在这里插入图片描述
在这里插入图片描述
2.遍历
代码语言:javascript
复制
//范围 for
for (auto e : s1)//e是容器元素的别名,可以随便换,但一般用e
{
	e++;
	cout << e;
}
  1. 范围for循环的核心机制:

  1. 通过容器的begin()/end()获得迭代器范围。
  2. 自动迭代(递增迭代器)。
  3. 自动解引用:自动取容器数据赋值给e。
  4. 自动判断是否结束。
  5. 跟迭代器一样,所有容器都可以用它来进行遍历,非常方便,但它底层是迭代器。

2.5 容量相关成员函数

在这里插入图片描述
在这里插入图片描述
  1. size():返回字符串的大小。
  2. length():返回字符串的长度。
  3. *max_size():返回字符串可以达到的最大理论长度。实际往往比理论要小,意义不大。
  4. capacity():返回已分配存储空间的大小。

vs里面,除了第一次,后续都是 1.5 倍扩容。 gcc里面,2倍扩容。

  1. clear():清空字符串里的所有字符。
  2. empty():判断是否还有字符,是否为空。是为 true,不是为 false。
7. *shrink_to_fit():

请求字符串减少其容量以适应其实际大小。

缩容代价很大,需重新分配内存:它无法直接缩小原有空间,只能去申请一块适应字符串大小的新的空间,然后将原内容拷贝到新的空间中,再释放原有空间,最后让string的内部指针指向新的空间。是一种用时间换空间的做法,一般不会这样做。

8. resize(n, c):

将字符串调整为n个字符的长度

在这里插入图片描述
在这里插入图片描述

  1. 如果 n 小于字符串的长度,字符串会被截断为前 n 个字符,后面的字符将被删除
  2. 如果 n 大于字符串的长度,那么会在字符串末尾插入所需数量的字符来达到 n 的大小。如果指定了 c,将会以 c 的副本作为新元素填充,未指定时,则用值初始化字符(‘\0’)进行填充。
  3. 容量够用时不会改变,不够用时会自动扩容。
代码语言:javascript
复制
string s1 = "abcdefg";
//初始值:
cout << "初始值:" << endl;
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;

//改变大小(增)且不指定:
cout << "改变大小(增)且不指定:" << endl;
s1.resize(100);
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;

//改变大小(减):
cout << "改变大小(减):" << endl;
s1.resize(4);
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;

//补充:改变大小(增)且指定:
cout << "补充:改变大小(增)且指定:" << endl;
s1.resize(10, '#');
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;

执行结果:

在这里插入图片描述
在这里插入图片描述
9. reserve(n):

请求调整字符串容量,用于预先分配内存,以避免后续操作频繁分配内存,从而提高性能。扩容靠谱,缩容不靠谱

在这里插入图片描述
在这里插入图片描述

它会将容量调整为至少 n 个字符,但不会改变字符串的长度

  1. 若 n 大于当前容量,则必须扩容到 n 个字符或者更大的容量(考虑内存对齐等问题)。
  2. 若 n 小于等于当前容量,则不保证(非约束性)执行任何操作(容量可能保持不变,或按实现策略优化)。

2.6 元素访问相关成员函数

在这里插入图片描述
在这里插入图片描述
1. operator[]:

返回字符串 pos 索引处字符的引用(非 const 限定的字符串可对其进行修改)。

在这里插入图片描述
在这里插入图片描述

  1. 若 pos == 字符串长度,则函数会返回对空字符’\0’的引用。
  2. operator[]不进行任何边界检查,但在某些编译器如 vs 下面,一旦越界,运行时会断言报错。
2. *at(pos):

功能与 operator[] 一样,不过它在处理越界时会抛异常

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
void stirng_test06()
{
	string s1 = "abcdefg";
	s1.at(25);
}
int main()
{
	try
	{
		stirng_test06();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

结果:

在这里插入图片描述
在这里插入图片描述
  1. *back():返回字符串结束位置的字符。
  2. *front():返回字符串首字符。
在这里插入图片描述
在这里插入图片描述

2.7 修改相关成员函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1. operator+=:

通过在当前字符串的末尾添加其他字符来拓展字符串。

代码语言:javascript
复制
//string
string s0 = "abcdefg";
string s1 = "###";
s0 += s1;
cout << s0 << endl;

//c-string
string s2 = "abcdefg";
const char* str = "###";
s2 += str;
cout << s2 << endl;

//character
string s3 = "abcdefg";
s3 += '#';
cout << s3 << endl;

结果:

在这里插入图片描述
在这里插入图片描述
2. *append():

追加内容,相比于operator +=,功能更加丰富,但其实平常用得不多。

在这里插入图片描述
在这里插入图片描述
3. *push_back():

在尾部插入一个字符。

在这里插入图片描述
在这里插入图片描述
4.*assign():

整体替换内容,相比于operator =,功能更加丰富,但其实平常用得不多。

在这里插入图片描述
在这里插入图片描述
5.*insert():

在索引为 pos 的值前插入字符或字符串。一般记住一个插入字符一个插入字符串的就行了。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
string s1 = "abcdefg";
//字符串
s1.insert(0, "###");
cout << s1 << endl;
//字符
s1.insert(0, 1, '!');
cout << s1 << endl;
s1.insert(s1.begin(), '$');
cout << s1 << endl;

结果:

在这里插入图片描述
在这里插入图片描述

insert谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。

6.*erase():

删除部分字符串,减少字符串长度。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
string s1 = "abcdefg";
//从索引为1的字符开始删,删一个
s1.erase(1, 1);
cout << s1 << endl;
//从索引为1的字符开始删,删完为止
s1.erase(1);
cout << s1 << endl;

erase:谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。

7.*replace:

用来替换字符串的一部分

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
string s1 = "abcdefg";
s1.replace(1, 3, "#");
cout << s1 << endl;

结果:

在这里插入图片描述
在这里插入图片描述

replace:谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。

8.swap():

交换,要注意 str 类型是 string& 而不是 const string&。

在这里插入图片描述
在这里插入图片描述

底层实现类似于:

在这里插入图片描述
在这里插入图片描述

算法库里面也有一个swap函数,但是不要去使用它,因为使用它的代价很大。它要实例化三次。

在这里插入图片描述
在这里插入图片描述
9.pop_back():

删掉字符串最后一个字符,使长度减 1。

2.8 字符串操作相关成员函数

在这里插入图片描述
在这里插入图片描述
1. c_str():

返回一个指向数组的指针,该数组包含一个以’\0’结尾的字符序列,即c字符串。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. c_str()可以拿到底层的_str(内部指针),即指向c风格字符串的指针。
  2. 有些函数只提供c的接口(比如 fopen()等),而没有c++的接口,这种情况下,c_str()可以很好的解决这个问题。
代码语言:javascript
复制
string filename("Test.cpp");
FILE* fout = fopen(filename.c_str(), "r");
if (fout == nullptr)
{
	cout << "fopen fail" << endl;
	return;
}
2.*data():

c++11以后,功能与c_str()完全相同。

3.get_allocator():

用于获取容器当前使用的内存分配器。

在这里插入图片描述
在这里插入图片描述
4.*copy(s, len, pos):

拷贝当前对象的一部分到 s 所指向的数组中,从 pos(起始位置) 开始拷贝,最多拷贝 len 个字符,返回值是实际拷贝的字符数。

在这里插入图片描述
在这里插入图片描述

一般使用之后substr()来进行拷贝,这个用得很少。

5.substr(pos, len):

从当前字符串对象中提取子串,并通过拷贝构造返回一个新的 string 对象

在这里插入图片描述
在这里插入图片描述

  1. 从 pos(当前位置)开始拷贝,最多拷贝 len 个字符,若 len > 剩余字符数量,则拷贝到字符串末尾。
  2. pos 默认值是0,显式传参时不能越界,否则运行时会抛异常,len 默认值是 npos,无符号整数最大值,表示“直到字符串末尾”。
在这里插入图片描述
在这里插入图片描述
6.find():

查找当前字符串对象的字符或者是子字符串。

在这里插入图片描述
在这里插入图片描述

  1. buffer(3):s 是指向c风格字符串的指针,size_t n 中的n指的是从 s 中取前 n 个字符进行查找。
  2. 返回值:找到则返回第一次找到位置的索引;未找到则返回 npos
在这里插入图片描述
在这里插入图片描述
7.*rfind():

查找当前字符串对象的字符或者是子字符串。相比于find(),用法差不多,不过它是倒着从后往前找

在这里插入图片描述
在这里插入图片描述
8.*find_first_of(s/c, pos):

给定一个字符串或单个字符参考集合 s 或 c,在当前字符串对象里查找第一个在参考集合里面的字符,并返回它的索引。

在这里插入图片描述
在这里插入图片描述
9.*find_last_of(s/c, pos):

功能和 find_first_of(s/c, pos)差不多,不过它是倒着从后往前查找。

10.*find_first_not_of(s/c, pos):

给定一个字符串或单个字符参考集合 s 或 c,在当前字符串对象里面查找第一个不在参考集合里面的字符,并返回它的索引。

在这里插入图片描述
在这里插入图片描述
11.*find_last_not_of(s/c, pos):

功能和 find_first_not_of(s/c, pos)差不多,不过与之相比,它是倒着从后往前查找。

12.*compare():

用来比较字符串,不过很少用到它,因为非成员函数里重载了用来比较的运算符,了解一下。

在这里插入图片描述
在这里插入图片描述

2.9 非成员函数

在这里插入图片描述
在这里插入图片描述
1.*operator+:

连接字符串并返回新的字符串,并且不会改变字符串彼此的值。

在这里插入图片描述
在这里插入图片描述
  1. 尽量少用,传值返回,深拷贝效率低下。
  2. 其不跟 operator+= 一同重载为成员函数的原因:

成员函数的都有一个隐藏的参数:const string& this,为了支持第一个参数为非string类型(如const char*),operator+需要被重载为非成员函数。之后的关系运算符重载函数重载为非成员函数也是这个原因。

2.relational operators:

其重载了所有的关系运算符。

在这里插入图片描述
在这里插入图片描述
3.swap(x, y):

交换两个字符串的值。

在这里插入图片描述
在这里插入图片描述

它存在的原因就是可以避免调用算法库里代价大的swap函数,已经准备好的函数的优先级是大于没有实例化的模板函数的。

4.operator>> 与 operator<<:

让 string类对象可以像内置类型一样输入输出。

5.getline(string)

从输入流中读取一行文本(遇到’\n’停止),并将其存储到字符串对象 str 中,类似cin。使用依赖<string>头文件。

在这里插入图片描述
在这里插入图片描述

它与 cin 的区别:

  1. cin 读取字符串的时候,如果字符串中间有空格,它便会停止读取。cin以空白字符作为分隔符(空格、制表符、换行符)
  2. getline() 则会无视空格,一直读下去,直到遇到’\n’。默认getline以换行符’\n’作为分隔符,它也可以自己定义分隔符(delim)。
代码语言:javascript
复制
string s1;
getline(cin, s1);
cout << "s1:" << s1 << endl;
string s2;
cin >> s2;
cout << "s2:" << s2 << endl;
在这里插入图片描述
在这里插入图片描述

三、部分函数底层实现

3.1 string.h

代码语言:javascript
复制
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
#include<vector>
using namespace std;

//解决命名冲突-->命名空间
namespace mosheng
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//begin()
		iterator begin();
		const_iterator begin()const;

		//end()
		iterator  end();
		const_iterator end()const;

		////默认构造函数
		//string();
		////c风格字符串构造(带参构造)
		//string(const char* str);

		//两个构造函数合并成全缺省构造函数
		string(const char* str = "");

		//拷贝构造
		string(const string& s);

		//swap
		void swap(string& s);

		//赋值重载函数
		string& operator=(string tmp);

		//析构函数
		~string();
		//c_str
		const char* c_str()const;

		//size函数
		size_t size()const;
		//operator[]重载
		//可读也可写
		char& operator[](size_t i);
		//只可读
		const char& operator[](size_t i)const;

		//扩容
		void reserve(size_t x);
		//尾插
		void push_back(char ch);
		void append(const char* str);

		//>>与<<
		string& operator+=(char ch);
		string& operator+=(const char* str);

		//在指定位置上插入
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);

		//删除指定位置
		//void erase(size_t pos);
		string& erase(size_t pos = 0, size_t len = npos);

		//尾删
		void pop_back();

		//查找
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;

		//获取子串
		string substr(size_t pos, size_t len = npos)const;

		//关系运算符重载
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

		//清理
		void clear();

	private:
		//类似于顺序表
		//不能用 const char*,我们是要修改字符串数组上的内容的
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	public:
		static const size_t npos;
	};

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
	istream& getline(istream& in, string& s, char delim = '\n');

}

3.2 string.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS

#include"string.h"
//如何进行具体的实现?
//法一:mosheng::string::string()
//		mosheng::string::string(const char* str)
//法二:定义命名空间:
//		多个文件可以定义同一个命名空间,不会冲突,多个文件的名称相同
//		命名空间编译器会认为是同一个命名空间
namespace mosheng
{
	const size_t string::npos = -1;
	//begin()
	//iterator前也要加string,突破类域使用
	string::iterator string::begin()
	{
		return _str;
	}
	string::const_iterator string::begin()const
	{
		return _str;//返回值自动转换为 const char*类型
	}
	//end()
	string::iterator  string::end()
	{
		return _str + _size;
	}
	string::const_iterator string::end()const
	{
		return _str + _size;
	}
	////默认构造函数
	//string::string()
	//	//通过字符串指针打印字符,首先是对指针解引用
	//	//空指针不能被解引用,程序会崩溃
	//	//:_str(nullptr)
	//	
	//	//待会儿可能会开多个,用 new[]对应
	//	:_str(new char[1]{'\0'})
	//	,_capacity(0)
	//	,_size(0)
	//{}


	////带参构造函数
	//string::string(const char* str)
	//	//涉及到权限放大问题,而且怎么能是单纯的指向str字符串
		// 需要申请一个独立的空间
	//	//:_str(str)

	//	//注意要给'\0'留个空间
	//	//调用了三次 strlen 不好,代价大
	//	:_str(new char[strlen(str) + 1])
	//	,_size(strlen(str))
	//	,_capacity(strlen(str))
	//{
	//	//strcpy会把'\0'一起给拷贝过来
	//	strcpy(_str, str);
	//}


	//带参构造函数
	string::string(const char* str)
		//涉及到权限放大问题,而且怎么能是单纯的指向str字符串
		// 需要申请一个独立的空间
		//:_str(str)

		//注意要给'\0'留个空间
		: _size(strlen(str))

	{
		//放到里面去,既不会因为初始化列表初始顺序出现问题代价也小
		_capacity = _size;
		_str = new char[_size + 1];
		//strcpy会把'\0'一起给拷贝过来
		//strcpy(_str, str);
		memcpy(_str, str, _size + 1);
	}
	//拷贝构造(深拷贝)
	//string::string(const string& s)
	//{
	//	//记得要多开一个
	//	_str = new char[s._capacity + 1];
	//	memcpy(_str, s._str, s._size + 1);
	//	_size = s._size;
	//	_capacity = s._capacity;
	//}
	void string::swap(string& s)
	{
		//为了正常交换,需要给成员变量一个缺省值,编译器可能不会处理
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	//拷贝构造(现代写法)
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	////赋值重载函数(传统写法)
	//string& string::operator=(const string& s)
	//{
	//	if (this != &s)
	//	{
	//		char* tmp = new char[s._capacity + 1];
	//		memcpy(tmp, s._str, s._size + 1);
	//		delete[] _str;
	//		_str = tmp;
	//		_size = s._size;
	//		_capacity = s._capacity;
	//	}
	//	return *this;

	//}
	
	//赋值重载函数(现代写法)
	string& string::operator=(string tmp)
	{

		swap(tmp);

		return *this;
	}
	//析构函数
	string::~string()
	{
		//与new[]对应
		delete[] _str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}

	//c_str
	const char* string::c_str()const
	{
		return _str;
	}

	//size函数
	size_t string::size()const
	{
		return _size;
	}
	//operator[]重载
	// 可读也可写
	char& string::operator[](size_t i)
	{
		assert(i < _size);

		return _str[i];
	}
	//只可读
	const char& string::operator[](size_t i)const
	{
		assert(i < _size);

		return _str[i];
	}

	//扩容
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			//cout << "void string::reserve(size_t n)" << endl;
			//是希望存储 n 个有效字符,还要为'\0'预留一个
			char* str = new char[n + 1];
			//strcpy只copy到第一个'\0',会出现问题
			//strcpy(str, _str);
			memcpy(str, _str, _size + 1);
			//释放旧空间
			delete[] _str;
			//_str指向新空间
			_str = str;
			_capacity = n;
		}
	}
	//尾插
	void string::push_back(char ch)
	{
		if (_size >= _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newcapacity = 2 * _capacity > (_capacity + len) ? 2 * _capacity : _capacity + len;
			reserve(newcapacity);
		}

		//strcpy(_str + _size, str);
		memcpy(_str + _size, str, len + 1);
		_size = _size + len;
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}

	//<< 与 >>
	ostream& operator<<(ostream& out, const string& s)
	{
		//有坑,关于'\0'
		//out << s.c_str();

		for (int i = 0; i < s.size(); i++)
		{
			if (s[i] != '\0')
				out << s[i];
		}
		return out;
	}
	//在指定位置上插入
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size >= _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		//int end = size;
		//还是用无符号整数类型
		size_t end = _size + 1;
		while (end >= pos + 1)
		{
			_str[end] = _str[end - 1];
			end--;
		}

		_str[pos] = ch;
		_size++;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newcapacity = 2 * _capacity > (_capacity + len) ? 2 * _capacity : _capacity + len;
			reserve(newcapacity);
		}

		size_t end = _size + len;
		while (end >= pos + len)
		{
			_str[end] = _str[end - len];
			end--;
		}
		memcpy(_str + pos, str, len);
		_size += len;
		return *this;
	}
	//删除指定位置
	/*void string::erase(size_t pos)
	{
		size_t begin = pos;
		while (pos <= _size)
		{
			_str[pos] = _str[pos + 1];
			pos++;
		}
		_size--;
	}*/
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos <= _size);
		if (len == npos || len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			/*size_t begin = pos;
			while (pos + len <= _size)
			{
				_str[pos] = _str[pos + len];
				pos++;
			}*/
			size_t i = pos + len;
			memmove(_str + pos, _str + i, _size - i + 1);
			_size -= len;
		}
		return *this;
	}

	//尾删
	void string::pop_back()
	{
		assert(_size > 0);
		_size--;
		_str[_size] = '\0';
	}

	//查找
	size_t string::find(char ch, size_t pos)const
	{
		assert(pos <= _size);

		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos)const
	{
		assert(pos <= _size);

		const char* find = strstr(_str + pos, str);
		if (find != nullptr)
		{
			//指针相减可以得到中间的字符数量
			return find - _str;
		}
		return npos;
	}

	//获取子串
	string string::substr(size_t pos, size_t len)const
	{
		if (len == npos || _size - pos < len)
		{
			len = _size - pos;
		}
		string ret;
		ret.reserve(len);
		//memcpy(ret._str, _str + pos, len);
		for (int i = 0; i < len; i++)
		{
			ret += _str[pos + i];
		}
		//初始要调用两次拷贝构造
		//和二为一,不生成临时对象,ret直接拷贝构造到后面的对象上去
		//vs2022和三为一,ret跟后面的对象的地址相同,ret是后面对象的别名,不需要调用拷贝构造
		return ret;
	}
	bool string::operator<(const string& s)const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] < s._str[i2])
			{
				return true;
			}
			else if (_str[i1] > s._str[i2])
			{
				return false;
			}
			i1++;
			i2++;
		}
		return _size < s._size;
	}
	bool string::operator==(const string& s)const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] != s._str[i2])
			{
				return false;
			}
			i1++;
			i2++;
		}
		if (_size == s._size)
		{
			return true;
		}
		return false;
	}
	bool string::operator<=(const string& s)const
	{
		if (*this < s || *this == s)
			return true;
		return false;
	}
	bool string::operator>(const string& s)const
	{
		if (!(*this <= s))
			return true;
		return false;
	}
	bool string::operator>=(const string& s)const
	{
		if (!(*this < s))
			return true;
		return false;
	}
	bool string::operator!=(const string& s)const
	{
		if (!(*this == s))
			return true;
		return false;
	}
	//清理
	void string::clear()
	{
		_size = 0;
		_str[0] = '\0';
	}

	//往往需要扩容多次,还有空间的浪费
	//istream& operator>>(istream& in, string& s)
	//{
	//	s.clear();
	//	char ch = in.get();
	//	//cin 与 scanf会自动忽略掉空格与换行,读不到
	//	//in >> ch;
	//	while (ch != ' '&& ch != '\n')
	//	{
	//		s += ch;
	//		//in >> ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();

		char buff[128];
		int i = 0;

		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			//存储的有效字符为127个,还剩一个用来存储'\0'
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	istream& getline(istream& in, string& s, char delim)
	{
		s.clear();

		char buff[128];
		int i = 0;

		char ch = in.get();
		while (ch != delim)
		{
			buff[i++] = ch;
			//存储的有效字符为127个,还剩一个用来存储'\0'
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

3.3 test.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS


#include"string.h"

//这里的命名空间是为了封装,若在命名空间之外也有一个
//test_string,它们之间不会冲突
namespace mosheng
{
	void test_string1()
	{
		string s1;
		//cout << s1 << endl;
		cout << s1.c_str() << endl;

		const string s2("hello world!");
		cout << s2.c_str() << endl;

		/*for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i]++;
			cout << s2[i] << " ";
		}*/
		cout << endl;
		for (auto ch : s2)
		{
			cout << ch << " ";
		}
		cout << endl;
		string::const_iterator it1 = s2.begin();
		/*while (it1 != s2.end())
		{
			cout << *it1 << " ";
			it1++;
		}*/
		cout << endl;
	}
	void test_string02()
	{
		string s1("hello world!");
		s1.push_back('#');
		cout << s1.c_str() << endl;
		s1.append("abc");
		cout << s1.c_str() << endl;
		s1 += "###";
		cout << s1.c_str() << endl;
		cout << s1 << endl;
		//关于'\0'的坑
		s1 += '\0';
		s1 += '\0';
		s1 += '#';
		s1 += "yyyyyyyyyyyyyyyyyyyyyyyyy";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}
	void test_string03()
	{
		string s1("hello world!");
		s1.insert(0, '#');
		cout << s1 << endl;
		s1.insert(0, "!!!");
		cout << s1 << endl;
		s1.erase(0, 1);
		cout << s1 << endl;
		s1.erase(5, 2);
		cout << s1 << endl;
		s1.pop_back();
		cout << s1 << endl;
	}
	void test_string04()
	{
		string s1("abcdefg");
		size_t find = s1.find('c');
		cout << find << endl;
		cout << s1.find("efg") << endl;
	}

	void split_url(const string& url)
	{
		size_t i1 = url.find(':');
		if (i1 != string::npos)
		{
			cout << url.substr(0, i1) << endl;
		}
		size_t i2 = i1 + 3;
		size_t i3= url.find('/', i2);
		if (i3 != string::npos)
		{
			cout << url.substr(i2, i3) << endl;
			cout << url.substr(i3 + 1) << endl;
		}
	}
	void test_string05()
	{
		string url1 = "http://legacy.cplusplus.com/reference/string/string/";
		string url2 = "https://yuanbao.tencent.com/chat/naQivTmsDa/43735652-b5e3-11ef-bcaa-c6162ee89a56?yb_channel=3003";
		string url3 = "https://legacy.cplusplus.com/reference/vector/vector/";

		split_url(url1);
		split_url(url2);
		split_url(url3);
	}
	void test_string06()
	{
		string s1("hello"), s2("hello");
		string s3("hellox"), s4("hello");
		string s5("hello"), s6("hellox");

		cout << (s1 < s2) << endl;
		cout << (s3 < s4) << endl;
		cout << (s5 < s6) << endl;
	}
	void test_string07()
	{
		string s1;
		string s2("hello");
		//cin >> s1 >> s2;
		getline(cin, s1);
		s2 = s1;
		cout << s1 << " " << s2;
	}
}
int main()
{
	//mosheng::test_string02();
//	mosheng::test_string1();
	/*cout << typeid(mosheng::string::iterator).name() << endl;
	cout << typeid(std::string::iterator).name() << endl;*/
	//mosheng::test_string03();
	//mosheng::test_string04();
	//mosheng::test_string06();
	mosheng::test_string07();
	return 0;
}

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~ 如果可以, 那就让我们共同努力, 一起走下去!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、STL
    • 1.1 什么是STL
    • 1.2 STL的版本
    • 1.3 STL的六大组件
  • 二、string类及其使用
    • 2.1 成员常量
    • 2.2 可以默认生成的特殊成员函数
      • 1. 构造函数
      • 2. 析构函数
      • 3. 赋值重载函数
    • 2.3 迭代器成员函数
      • 1.正向迭代器(begin从前往后遍历):
      • 2.反向(reverse)迭代器(rbegin从后往前遍历):
    • 2.4 遍历
      • 2.4.1 下标 + []
      • 2.4.2 迭代器
      • 2.4.3 范围 for(c++11)
    • 2.5 容量相关成员函数
      • 7. *shrink_to_fit():
      • 8. resize(n, c):
      • 9. reserve(n):
    • 2.6 元素访问相关成员函数
      • 1. operator[]:
      • 2. *at(pos):
    • 2.7 修改相关成员函数
      • 1. operator+=:
      • 2. *append():
      • 3. *push_back():
      • 4.*assign():
      • 5.*insert():
      • 6.*erase():
      • 7.*replace:
      • 8.swap():
      • 9.pop_back():
    • 2.8 字符串操作相关成员函数
      • 1. c_str():
      • 2.*data():
      • 3.get_allocator():
      • 4.*copy(s, len, pos):
      • 5.substr(pos, len):
      • 6.find():
      • 7.*rfind():
      • 8.*find_first_of(s/c, pos):
      • 9.*find_last_of(s/c, pos):
      • 10.*find_first_not_of(s/c, pos):
      • 11.*find_last_not_of(s/c, pos):
      • 12.*compare():
    • 2.9 非成员函数
      • 1.*operator+:
      • 2.relational operators:
      • 3.swap(x, y):
      • 4.operator>> 与 operator<<:
      • 5.getline(string)
  • 三、部分函数底层实现
    • 3.1 string.h
    • 3.2 string.cpp
    • 3.3 test.cpp
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档