【C++进阶】深入STL之string:模拟实现走进C++字符串的世界

📝个人主页🌹:Eternity._

⏩收录专栏⏪:C++ " 登神长阶 "

🤡往期回顾🤡:C++模板入门

🌹🌹期待您的关注 🌹🌹

❀STL之string

  • [📒1. string类的成员变量](#📒1. string类的成员变量)
  • [📒2. string的构造函数](#📒2. string的构造函数)
  • [📒3. string的析构函数](#📒3. string的析构函数)
  • [📒4. string的拷贝构造函数](#📒4. string的拷贝构造函数)
    • [🔥 浅拷贝](#🔥 浅拷贝)
    • [💧 深拷贝](#💧 深拷贝)
  • [📒5. string类的运算符重载](#📒5. string类的运算符重载)
  • [📒6. string容量相关函数](#📒6. string容量相关函数)
  • [📒7. string常用函数模拟](#📒7. string常用函数模拟)
  • [📒8. 总结](#📒8. 总结)

前言:在C++中,string是一个极其重要且常用的类,它为我们提供了丰富的字符串操作功能。然而,了解其背后的实现原理,不仅可以帮助我们更好地使用它,还能让我们对C++的内存管理、模板编程等有更深入的理解。本文将带你走进C++字符串的世界,通过模拟实现一个简单的string类,来探索其内部机制

模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数


📒1. string类的成员变量

首先我们要先搞清楚string的成员变量,我们清楚string类在底层实际上就是一个字符指针,在模拟实现string之前,我们创建一个属于自己的命名空间来与库里面的区分

cpp 复制代码
namespace pxt
{
	class string
	{
	public:
		const char* c_str() const // 为了能更好的实现,我们提前实现以下c.str
		{
			return _str;
		}
	private:
		// 成员变量
		char* _str; // 指向一段空间的指针
		size_t _size; // 有效字符串长度
		size_t _capacity; // 空间总大小
	};
}

📒2. string的构造函数

🎈无参的构造函数

cpp 复制代码
string()
	:_str(new char[1]{ '\0' })
	,_size(0)
	, _capacity(0)
{}

注意:在调用无参的构造函数时,库里面并不只是开了空间,它还干了其他事情,所以我们在自己模拟现实时,一定不能用nullptr去初始化,否则就会出错,因此我们放一个'\0'进去!

std::string无参构造:


🎩带参的构造函数

cpp 复制代码
string(const char* str = "")
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

在带参的构造函数因为常量字符串最后自带了一个'\0',因此我们什么都不用带


📒3. string的析构函数

cpp 复制代码
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

string的析构函数非常简单,只需要将空间用delete释放,并且将各个指针置为空,将空间大小变为0


📒4. string的拷贝构造函数

🔥 浅拷贝

首先我们来看一段拷贝构造的模拟实现:

cpp 复制代码
// 拷贝构造
string(const char* str = "")
{
	if (nullptr == str)
	{
 		return;
 	}
 	_str = new char[strlen(str) + 1];
 	strcpy(_str, str);
}

// 测试
void test_string()
{
 	string s1("hello world");
 	string s2(s1);
}

为什么会引发异常呢?

我们发现s1和s2都指向都一块空间,在释放时同一块空间是不可以被释放多次的,从而引起了崩溃,而这就是浅拷贝

浅拷贝: 也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规,

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享


💧 深拷贝

cpp 复制代码
// 我们用s作为s1的别名
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
}

深拷贝:每个对象都有一份独立的资源,不要和其他对象共享

注意: 关于浅拷贝一定要引起重视!


📒5. string类的运算符重载

operator=

operator=上,我们有两种写法

cpp 复制代码
// 传统写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1]; // 存放'\0'
		strcpy(tmp, s._str);

		delete[] _str;
		_str = tmp;
		_size = s.size();
		_capacity = s.capacity();
	}

	return *this;
}

// 现代写法
string& operator=(string s)
{
	swap(_str, s._str); // swap库中存在,可以直接使用
	return *this;
}

传统写法

  • 传统写法函数的运用引用传参,通过创建中间变量,并开辟空间然后将参数拷贝进中间变量,再把这个中间变量的地址传给this,从而实现了operator=的功能
    现代写法

  • 现代写法使用了库中的swap函数,从而让函数达到一个简洁的目的,该函数的参数是一个临时拷贝变量,深拷贝后,通过swap交换即可


operator<, operator==

cpp 复制代码
bool operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0; // 使用strcmp来比较字符串大小
}

bool operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}

关于比较我们就讲这两个,对于其他的都可用operator<, operator==去进行推导!


📒6. string容量相关函数

size,capacity,resize,reverse


cpp 复制代码
size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

size,capacity这两个函数的模拟实现相对简单,我们简单实现一下就可以


cpp 复制代码
void reserve(size_t n)
{
	if (n > _capacity) // n < _capacity时,reserve不会作出回应
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);

		delete[]_str;
		_str = tmp;
		_capacity = n;
	}
}

reverse只会改变capacity的大小,并不会改变size的大小


cpp 复制代码
void resize(size_t n, char ch = '\0')
{
	if (n < _size) // 当 n < _size时会将size变小到n
	{
		_str[n] = '\0';
		_size = n;
	}
	else // 当 n > _size时,就和reserve类似
	{
		reserve(n);
		while (n < _size)
		{
			_str[_size++] = ch;
		}
		_str[_size] = '\0';
	}
}

resize与reserve类似会改变size大小,但是也会改变capacity大小


📒7. string常用函数模拟

⭐查找

cpp 复制代码
// 查找单个字符
size_t find(char ch, size_t pos = 0)
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}
// 查找字符串
size_t find(const char* sub, size_t pos = 0)
{
	// strstr为字符串匹配函数
	const char* p = strstr(_str + pos, sub);
	if (p)
	{
		return p - _str;
	}
	else
	{
		return npos;
	}
}
// 返回以pos开头,len位置结尾的字符串
string substr(size_t pos, size_t len = npos)
{
	string s;
	if (len == npos || len + pos > _size)
	{
		len = _size - pos;		
	}
	reserve(len);
	for (size_t i = pos; i < _size; i++)
	{
		s += _str[i];
	}
	return s;
}

⭐ 添加

cpp 复制代码
// 尾插单个字符
void push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}
// 插入字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}

string& operator+=(const char* str)
{
	append(str);
	return *this;
}

因为在添加中+=既可以添加字符也可以添加字符串,往往在日常中的使用频率是最高的,所以推荐大家使用+=来代替push_backappend


📒8. 总结

经过对STL中string的深入探索与模拟实现,我们仿佛揭开了一个隐藏在C++深处的奇妙世界。这个旅程不仅让我们对string这一基础数据类型有了更为深刻的理解,也让我们领略了STL背后的设计理念与精巧实现,让我们携手共进,共同走进C++字符串的奇妙世界!


谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495648 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr8 小时前
2607C++,soui与安卓
c++·soui
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177611 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao11 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
fqbqrr12 小时前
2607C++,使用微软detours勾挂工具
c++