首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++强基篇】学习C++就看这篇--->STL之string使用及实现

【C++强基篇】学习C++就看这篇--->STL之string使用及实现

作者头像
HABuo
发布2025-07-15 14:27:41
发布2025-07-15 14:27:41
3030
举报

主页:HABUO🍁主页:HABUO

🍁C++入门到精通专栏🍁

🍁如果再也不能见到你,祝你早安,午安,晚安🍁

前言: 从这篇博客开始我们就进入了STL标准库的学习,对于这部分是我们以后写项目的重要基础,这部分学习主要分为三个阶段,第一阶段是会用STL中的一些容器,第二阶段是知道它们的底层是如何实现的,第三阶段是我们能对其改造。当然我们前两个阶段达到就已经相当ok了,在面对相当多的场景就已经够用,不奢求能达到第三阶段,接下来就让我们开启对string的学习吧!温馨提示:前面知识有遗忘的要多加回顾哦!

📕一、 STL简介

✨1.1 什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

✨1.2 STL的版本(了解)

  • 原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本--所有STL实现版本的始祖。

  • P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。

  • RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  • SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。

✨1.3 STL的六大组件

可以看到STL中包含了大量的容器和一些算法,学习了这些知识之后,相信你会忘却C语言的存在的。

📕二、 string

✨ 2.1 string简介

STL(Standard Template Library)中的 std::string 是 C++ 标准库提供的一个极其重要和常用的类,用于表示和操作字符序列(通常是文本字符串)。它是 basic_string<char> 模板类的类型别名,专为 char 类型(通常用于表示 ASCII 或 UTF-8 字符)设计。

✨2.2 string特点

  • 动态内存管理: 自动处理字符串所需的内存分配和释放。你无需担心 new/deletemalloc/free。字符串长度可以动态增长或缩小。
  • 丰富的成员函数: 提供了大量内置方法用于字符串操作(查找、替换、插入、删除、比较、子串提取、大小调整等),极大地简化了编程。
  • 安全性: 避免了 C 风格字符串常见的缓冲区溢出错误(如 strcpy 不当使用)。
  • 方便性: 支持运算符重载(=, +, +=, ==, !=, <, >, [] 等),使得字符串的赋值、连接、比较和访问像操作基本类型一样直观。
  • 可预测性: 作为标准库类,其行为在不同编译器和平台上是一致的。
  • 与 STL 算法集成: 可以像其他 STL 容器(如 vector)一样使用迭代器,方便地与标准算法(<algorithm> 头文件中的 find, sort, transform 等)协同工作。
  • 知道自身长度: 通过 size()length() 成员函数可以随时获取当前字符串的长度(字符数,不包括结尾的空字符 '\0')。

✨2.3 string的使用

1️⃣ 头文件与命名空间
代码语言:javascript
复制
#include <string> // 必须包含的头文件
using namespace std; // 或者显式使用 std::string
2️⃣ string类对象的常见构造

构造函数

功能说明

string()(重点)

构造空的 string 类对象,即空字符串

string(const char* s)(重点)

用 C-string 来构造 string 类对象

string(size_t n, char c)

string 类对象中包含 n 个字符 c

string(const string& s)(重点)

拷贝构造函数

代码示例如下:

代码语言:javascript
复制
std::string str1;              // 默认构造,空字符串 ""
std::string str2("Hello");     // 用 C 风格字符串初始化
std::string str3 = "World";    // 同上
std::string str4(str2);        // 拷贝构造 "Hello"
std::string str5(5, 'A');      // 构造包含 5 个 'A' 的字符串 "AAAAA"
std::string str6(str2, 1, 3);  // 从 "Hello" 的索引 1 开始取 3 个字符 "ell"
std::string str7(str2, 1);     // 如果第三个参数不写的话默认是最大的即npos,"ello"
std::string str8(str2, 1,string::npos); //与上述语句功能完全一致
std::string str9 = str2 + " " + str3; // 连接构造 "Hello World"
代码语言:javascript
复制
size_t string::npos = -1;

npos就是一个无符号整数的-1

  1. 无符号整数的特殊值
    • size_t无符号整数类型(通常为 unsigned intunsigned long)。
    • 当将 -1 赋值给无符号整数时,会发生整数回绕(wrap-around),结果等于该类型能表示的最大值(即所有位全为 1 的二进制数)。
  2. 实际值
    • 在 32 位系统中:npos = 4,294,967,295(即 232−1232−1)
    • 在 64 位系统中:npos = 18,446,744,073,709,551,615(即 264−1264−1)
3️⃣ string类对象的容量操作

函数名称

功能说明

size(重点)

返回字符串有效字符长度

length

返回字符串有效字符长度

capacity

返回空间总大小

empty(重点)

检测字符串是否为空串,是则返回 true,否则返回 false

clear(重点)

清空有效字符

reserve(重点)

为字符串预留空间

resize(重点)

将有效字符的个数改成 n 个,多出的空间用字符 c 填充

代码语言:javascript
复制
s.reserve(100);//reserve提前开空间,但要考虑内存对齐,一般都是开给定空间的整数倍
s.resize(100, 'x');//resize是不光开空间而且将开辟的空间给值,不自己设定值默认是'\0'
代码语言:javascript
复制
string s("hello world");
s.resize(5);//会发生截断
s.resize(20, 'x');//在hello world后面补充x直至达到20个空间
cout << s << endl;//也就是说resize是管你空间的,你原本就够的给你截断,你原本不够的给你补充
4️⃣ string类对象的访问及遍历操作

函数名称

功能说明

operator[](重点)

返回指定位置(pos)的字符,适用于 const string 类对象

begin + end

迭代器:begin 获取第一个字符的迭代器,end 获取最后一个字符下一个位置的迭代器

rbegin + rend

反向迭代器:rbegin 获取最后一个字符的迭代器,rend 获取第一个字符前一个位置的迭代器

范围 for

C++11 支持的更简洁的范围 for 循环遍历方式

5️⃣ string类对象的修改操作

函数名称

功能说明

push_back

在字符串后尾插字符 c

append

在字符串后追加一个字符串

operator+=(重点)

在字符串后追加字符串 str

c_str(重点)

返回 C 格式字符串(即以 \0 结尾的字符数组)

find + npos(重点)

从字符串 pos 位置开始往后找字符 c,返回该字符在字符串中的位置

rfind

从字符串 pos 位置开始往前找字符 c,返回该字符在字符串中的位置

substr

在字符串中从 pos 位置开始,截取 n 个字符,然后将其返回

代码语言:javascript
复制
cout << s1 << endl;//调用的是string重载的operator<< 会将对象数组中的所有字符都输出
cout << s1.c_str() << endl;//直接输出const char*是C语言的形式 遇到\0就会结束
//就会发现cout << s1 << endl;是会打印完的,而cout << s1.c_str() << endl;只打印到\0处
6️⃣ string类非成员函数

函数名称

功能说明

operator+

尽量少用,因为传值返回,导致深拷贝效率低

operator>>(重点)

输入运算符重载

operator<<(重点)

输出运算符重载

getline(重点)

获取一行字符串

relational operators(重点)

大小比较(如 ==, !=, <, <=, >, >= 等)

cin:使用 >> 运算符读取输入,以空白字符(空格、制表符、换行符)作为分隔符。遇到空白符时停止读取,剩余输入留在缓冲区。 getline:读取整行输入(包括空格),以换行符 '\n' 为结束标志。换行符被读取但不存储到目标变量中。

代码语言:javascript
复制
int main() {
	string s1, s2;

	cout << "Enter a string for cin: ";
	cin >> s1;  // 输入 "Hello World",s1 只得到 "Hello"

	cin.ignore(); // 清除缓冲区残留的换行符

	cout << "Enter a string for getline: ";
	getline(cin, s2); // 输入 "Hello World",s2 得到完整内容

	cout << "cin: " << s1 << endl;    // 输出 "Hello"
	cout << "getline: " << s2;        // 输出 "Hello World"
	return 0;
}

代码示例总结如下:

  • 赋值:
代码语言:javascript
复制
str1 = "New Value"; // 赋值(重要)
str1.assign("Assigned", 3); // 赋值前 3 个字符 "Ass"
  • 访问字符:
代码语言:javascript
复制
char first = str2[0];   // 'H' (下标运算符,不检查边界)
const char* cstr = str2.c_str(); // 获取指向内部 C 风格字符串的指针 (只读,内容可能随 str2 改变而失效)
---------------------下面不常用---------------------------------------------------------
char second = str2.at(1); // 'e' (at 方法,会检查边界,越界抛 std::out_of_range)
char last = str2.back(); // 'o' (C++11)
char first = str2.front(); // 'H' (C++11)
const char* dataptr = str2.data(); // 类似 c_str() (C++11 起,data() 在非 const string 上也返回 const char*,直到 C++17 才有非 const 版本)
  • 修改内容:
代码语言:javascript
复制
str2 += " C++";         // 追加 " C++" -> "Hello C++" (operator+=)
str2.insert(5, " dear"); // 在索引 5 处插入 " dear" -> "Hello dear C++!!!"
str2.erase(5, 5);       // 从索引 5 开始删除 5 个字符 -> "Hello C++!!!"
str2.clear();           // 清空字符串 -> ""
str2.resize(10, 'X');   // 改变大小为 10,不足部分用 'X' 填充 -> "XXXXXXXXXX"
str2.append("!!!");     // 追加 "!!!" -> "Hello C++!!!"
str2.push_back('!');    // 追加单个字符 '!'
str2.replace(6, 3, "Standard"); // 从索引 6 开始替换 3 个字符 ("C++") 为 "Standard" -> "Hello Standard!!!"

//插入一般喜欢用+=
  • 查找:
代码语言:javascript
复制
size_t pos1 = str.find("World"); // 查找子串 "World" 首次出现的位置,找不到返回 std::string::npos
size_t pos2 = str.find("lo", 1); // 从索引 1 开始查找 "lo"
size_t pos3 = str.rfind("l");    // 查找 'l' 最后一次出现的位置
size_t pos4 = str.find_first_of("aeiou"); // 查找 "a", "e", "i", "o", "u" 中任意一个首次出现的位置
size_t pos5 = str.find_first_not_of("0123456789"); // 查找第一个非数字字符的位置
// 类似的有 find_last_of, find_last_not_of

应用场景,如分离网址:

代码语言:javascript
复制
//取文件后缀名
void test_string1()
{
	string s1("string.cpp");
	string s2("string.c");
	string s3("string.txt");
	size_t pos1 = s1.find('.');//如果没找到的话返回的就是无符号整型的最大值
	if (pos1 != string::npos)
	{
		cout << s1.substr(pos1) << endl;
	}
	size_t pos2 = s2.find('.');
	if (pos2 != string::npos)
	{
		cout << s2.substr(pos1) << endl;
	}
}
//分离网址
void test_string2()
{
	string habcsdnurl("https://blog.csdn.net/qq_53706413?spm=1011.2124.3001.5343");
	string& url = habcsdnurl;
	//分离url 协议   域名    资源名称
	size_t pos1 = url.find(':');
	if (pos1 != string::npos)
	{
		cout << url.substr(0, pos1) << endl;
	}

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 != string::npos)
	{
		cout << url.substr(pos1 + 3, pos2 - (pos1 + 3)) << endl;
	}
	cout << url.substr(pos2+1);
}
  • 子串提取:
代码语言:javascript
复制
std::string sub1 = str.substr(6);     // 从索引 6 开始到结尾的子串 ("Standard!!!")
std::string sub2 = str.substr(6, 8);  // 从索引 6 开始长度为 8 的子串 ("Standard")
  • 比较:
代码语言:javascript
复制
if (str1 == str2) { ... }    // 相等比较
if (str1 != str3) { ... }    // 不等比较
if (str1 < str4) { ... }     // 小于比较 (字典序)
int result = str1.compare(str2); // 返回 0(相等), >0(str1>str2), <0(str1<str2)
int result = str1.compare(0, 3, "Hel"); // 比较 str1 的前 3 个字符和 "Hel"
  • 大小与容量:
代码语言:javascript
复制
size_t len = str.size();      // 或 str.length(), 当前字符数
bool isEmpty = str.empty();   // 是否为空字符串 (size() == 0)
size_t cap = str.capacity(); // 当前已分配的内存能容纳的字符数 (通常 >= size())
str.reserve(100);             // 请求预留至少 100 字符的容量 (优化后续操作,减少重新分配)
str.shrink_to_fit();          // 请求减少 capacity() 到 size() (C++11, 请求非强制)
  • 输入/输出:
代码语言:javascript
复制
std::cout << str << std::endl; // 输出字符串
std::cin >> str;              // 读取一个单词 (遇空格/换行停止)
std::getline(std::cin, str);  // 读取一整行 (包括空格,直到换行符)

事实上string的接口非常多,穷举都要举办天,所以一般我们会用一些常见的就够用了,对于不常见的碰到了,查文档就可以,文档网址如下: https://legacy.cplusplus.com/

📕三、 string的模拟实现

上述对于它的使用是第一层次的,当我们会用之后,可以进入下一层,对它的底层实现加以了解,这会在我们使用它的时候更加得心应手!

事实上,校招找工作的时候会场面临着让你简单实现一个string类的情况。

string的简单模拟实现:

代码语言:javascript
复制
namespace hab 
{
	class string 
	{
	public:
		/*string(char* str)
			:_str(new char[strlen(str) + 1])
		{
			strcpy(_str, str);
		}
		string()
			:_str(new char[1])
		{
			_str[0] = '\0';
		}*/
		//合并写法
		string(const char* str ="\0")
			:_str(new char[strlen(str) + 1])
		{
			strcpy(_str, str);
		}
		string(const string& s)//为什么const string s报错,因为陷入了死循环,一直在调用拷贝构造
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}//这是深拷贝,即重新开辟空间
		//但是如果是编译器默认生成的就会造成浅拷贝的问题,浅拷贝就是对同一块空间释放多次导致的错误。
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[strlen(s._str) + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
			}
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		const char* c_str()
		{
			return _str;
		}
		size_t size()
		{
			return strlen(_str);
		}

	
	private:
		char* _str;
	};
}

上面是传统的实现方法,对于部分接口有现代的一些写法更简便,如下:

代码语言:javascript
复制
		string(const string& s)//拷贝构造的另一种写法
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(_str, tmp._str);
		}//有点空手套白狼的感觉
		
		//沿用上述思路,可以对赋值进行更改
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		swap(_str, tmp._str);//这里遇到如果由capacity和size时交换存在问题
		//	}
		//	return *this;
		//}
		//也可以用下述的写法
		string& operator=(string s)//这里最巧的就是这个传值操作
		{
			swap(_str, s._str);
			return *this;
		}

我们在模拟实现operator输入的时候,会遇到用in无法读取空格或换行符的情况,最终导致输入无法停止的现象,如下:

代码语言:javascript
复制
istream& operator>>(istream& in, string& s)
	{
		while (1)
		{
			char ch;
			//in >> ch;//这里会导致无法读取换行符或者空格的问题,
					 //原因就在于当执行 in >> ch 时,运算符会忽略(跳过)所有前导空白字符(包括空格、换行符 '\n'、制表符等)。
			ch = in.get();
			if (ch == ' ' || ch == '\n')
				break;
			else
				s += ch;
		}
		return in;
	}

解释:

  1. in >> ch 的行为
    • 当执行 in >> ch 时,运算符会忽略(跳过)所有前导空白字符(包括空格、换行符 '\n'、制表符等)。
    • 只有读取到非空白字符时,才会将其赋值给 ch
    • 如果输入缓冲区中只有空白字符(例如用户按下回车键输入的 '\n'),in >> ch 会持续等待非空白字符输入,导致循环无法终止。
  2. 循环逻辑缺陷
    • 循环条件 while (1) 是死循环,仅依赖 ch == ' ' || ch == '\n' 来跳出。
    • 由于 >> 跳过了换行符,ch 永远不会等于 '\n',因此循环无法通过换行符终止。

📕四、总结

本篇博客我们主要了解学习了string这个容器, string 类是 C++ 标准库中用于处理字符串的重要组件,它提供了丰富的构造函数,如默认构造、用 C 风格字符串构造、拷贝构造等,还支持通过 + 进行字符串连接。在容量管理方面,sizelength 可获取字符串长度,capacity 返回当前容量,reserve 用于预留空间,resize 可调整字符串大小并填充指定字符。访问和遍历字符串可通过下标运算符 []、迭代器(beginendrbeginrend)以及 C++11 的范围 for 循环实现。修改操作包括 push_back 追加字符、append 追加字符串、operator+= 追加字符串、c_str 返回 C 风格字符串、findrfind 查找字符或子串、substr 提取子串等。非成员函数如 operator+(连接字符串)、operator>>(输入)、operator<<(输出)、getline(读取一行)以及关系运算符(比较字符串)等也极大地方便了字符串的使用。模拟实现 string 类时,需关注深拷贝与浅拷贝问题,确保正确管理内存,避免多次释放同一块内存空间。希望大家有所收获!

写在后面的话: 对于string这个类,上面的接口大家对标注重要的部分加以记忆即可,构造方面:拷贝构造、字符串构造、无参构造这些就足够,修改方面:+=、insert(分为在指定位置插入字符串和字符)、erase、reserve、resize、find、rfind这些是相对比较重要的。另外要格外注意capacity是能存放多少有效字符,而size是现有多少有效字符,这点要格外注意,并且字符串后面一定要时刻提防'\0',不能把它遗忘。 另外,对于string的大部分接口的实现,可以详见本人代码库: https://gitee.com/hanaobo/c-learningcode/tree/master/string_simulate

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 📕一、 STL简介
    • ✨1.1 什么是STL
    • ✨1.2 STL的版本(了解)
    • ✨1.3 STL的六大组件
  • 📕二、 string
    • ✨ 2.1 string简介
    • ✨2.2 string特点
    • ✨2.3 string的使用
      • 1️⃣ 头文件与命名空间
      • 2️⃣ string类对象的常见构造
      • 3️⃣ string类对象的容量操作
      • 4️⃣ string类对象的访问及遍历操作
      • 5️⃣ string类对象的修改操作
      • 6️⃣ string类非成员函数
  • 📕三、 string的模拟实现
  • 📕四、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档