当前位置: 首页 > news >正文

【C++世界之string模拟实现】

string介绍

在这里插入图片描述

C++ string:在C语言基础上,提供了一个 类的封装,内部通常包含:
一个指向动态内存的 char*(保存字符串内容);
一个记录当前字符串长度的 _size;
一个记录已分配空间大小的 _capacity(一般比 _size 大,用于减少频繁扩容)

string 会 自动管理内存:当字符串变长时,会重新分配更大的空间,并把原来的内容复制过去。
为了效率,string 通常采用 按需扩容:容量不足时,不是只增加 1 个,而是成倍增加(比如翻倍)。

std::string 提供了很多便利的功能:
构造:直接用字面量初始化(string s = “hello”;)。
访问:下标 [],迭代器,at()。
修改:append、insert、erase、replace。
比较:==、!=、< 等重载运算符。
拼接:支持 +、+=。
安全性:自动管理内存,避免手动 new/delete。

string用法及常用函数

string的文档

初始化

int  main()
{string s1;string s2("张三");string s3("hello world");string s4(10, '*');string s5(s2);string s6(s3, 6, 5);//区间初始化 6:pos 5:len
}

赋值

int main()
{s1 = s2;s1 = "1111";s1 = '2';
}

增(各种插入)

push_back

string s1("hello");
//尾插一个字符
s1.push_back(' ');

append

//尾插一个字符串
s1.append("world");
//插入一个字符串前几个字符
s1.append("hello world",5);  //插入字符串的前五个字符
//插入一个string区间
s1.append(s2,3,6);// pos: 3   len: 6

重载+=

s1 += ' ';
s1 += "world";
s1 += s1;

insert

  s1.insert(0, "hello");//pos + 字符串(指定位置插入字符串)s1.insert(0,"hello world",5);// pos + 字符串 + len(插入字符串区间)s1.insert(0,s2);//pos + strings1.insert(0, s2,2,5);//pos + string + string的区间s1.insert(0,10,'x');// pos + n(个数) + chars1.insert(s1.beign(),10,'x'); //迭代器

erase

int main()
{string s1("hello world");s1.erase(5, 1);//pos + ncout << s1 << endl;s1.erase(5);//pos -> endcout << s1 << endl;string s2("hello world");s2.erase(0,1);cout << s2 << endl;s2.erase(s2.begin());//迭代器cout << s2 << endl;s2.erase(s2.begin(),s2.end());//全删s2 = '0';cout << s2 << endl;
}

实际效果:
在这里插入图片描述

迭代器

介绍:
迭代器(Iterator)就是 用来遍历容器元素的一种对象。
它相当于一个“指针”,但比普通指针更灵活、更安全,能屏蔽底层容器的具体实现方式。

比如 vector、list、map 这些 STL 容器,内部结构不一样,但都能用迭代器来统一遍历。

功能:
输入迭代器:只读,单向移动(常见于 istream_iterator)。
输出迭代器:只写,单向移动(常见于 ostream_iterator)。
前向迭代器:可读可写,单向遍历。
双向迭代器:可读可写,能前进和后退(如 list 的迭代器)。
随机访问迭代器:支持 + - [] < > 等操作(如 vector、deque 的迭代器)。

常见几种迭代器
//iterator
//const_iterator
//reverse_iterator(反向)
//const_reverse_iterator

打印

string::iterator it = s1.begin();while (it != s1.end()){(*it)--;++it;}it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}

范围for

范围for底层就是迭代器,不过比较简单
打印:

	for (auto ch : s1)//for(char ch:s1){cout << ch << " ";}cout << endl;

修改:(需要引用)

for (auto& ch : s1){ch++;}

迭代器+算法容器

反转:

	reverse(v.begin(), v.end());reverse(lt.begin(), lt.end());

排序:

sort(s1.begin(),s1.end());

…………

reserve & resize

reserve 开空间

	//开空间s1.reserve(100);cout << s1.size() << endl;cout << s1.capacity() << endl;

resize 开空间+初始值

//开空间+填值初始化s1.resize(200,'x');cout << s1.size() << endl;cout << s1.capacity() << endl;//扩容s1.resize(20);//可以删除数据,不缩容cout << s1.size() << endl;cout << s1.capacity() << endl;

上述为基本常用功能·,其他接口自行去文档查找。

模拟实现

前提准备

namespace bit
{
class string
{private:
size_t _size;
size_t _capacity;
char* _str;
static size_t npos;public:
………………
}
size_t string:: npos = -1; 
}

构造函数

字符串构造

string(const char* str = "")//相当于\0:_size(strlen(str)),_capacity(_size)
{_str = new char[_capacity + 1];memcpy(_str, str,_size+1);// 如果 只拷贝_size 则 _str[_size] = '\0';
}

我们也可以使用for循环逐个拷贝进去

for (size_t i = 0; i < _size; i++){_str[i] = s[i];}
_str[_size] = '\0';

字符构造

string(const char* str, size_t n):_size(n),_capacity(_size)
{_str = new char[_capacity + 1];memcpy(_str, str, n);_str[_size] = '\0';//注意添加
}

PS1:为什么一个是_size + 1 一个是 _size。
二者差异在memcpy的拷贝区间,因为s自带’\0’,所以拷贝到_size + 1.
但是字符没有‘\0’,所以要手动添加。

PS2: 为何不推荐使用strcpy?
1. 若字符串中包含’\0’字符(虽然这种情况较为少见),拷贝过程会提前终止,导致无法完整复制。
2. 为保持代码一致性及美观性,建议避免使用该函数。

拷贝构造函数

传统写法:

string(const string& str): _size(str._size), _capacity(str._capacity)
{_str = new char[_capacity + 1];memcpy(_str, str._str, _size + 1);
}

现代写法:(不推荐)

string(const string& str):_str(nullptr),_size(0),_capacity(0)
{string tmp(str._str);swap(tmp);
}

PS:
若 _str、_size 和 _capacity 未被初始化,可能导致以下问题:
访问非法内存地址(野指针)
_size 和 _capacity 值异常,引发数组越界
将对象初始化为空状态可确保:
swap 操作时数据始终有效
避免未初始化值带来的风险

重载=

传统写法:

string& operator=(const string& str){if (this == &str) return *this;delete[]_str;_str = new char[str._capacity + 1];memcpy(_str, str._str, str._size + 1);_size = str._size;_capacity = str._capacity;return *this;}

现代写法:

void swap(string& str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);//不能直接交换,会无限循环
}string& operator=(string str)//形参(传值)拷贝了一个str
{if (this == &str)  return *this;swap(str);//this->swap(tmp);return *this;
}

PS1: 为什么不能直接交换(this)和(str)?
由于std::swap在交换this和str时会涉及赋值操作,这会触发重载的=运算符,导致无限递归。
PS2: 为什么要传递形参str?
形参str是实参的副本,通过交换这个副本既能实现赋值目的,又不会影响原始实参的值。

析构函数

~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}

重载[]

char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}const char& operator[](size_t pos)const
{assert(pos < _size);return _str[pos];
}

字符串输出

const char* c_str()const { return _str;}

PS:
采用字符串形式输出,便于使用字符串处理函数。
直接支持打印功能,无需专门重载流输出和流插入操作符。

cout << s.c_str() << endl;

开空间

reserve

void reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];memcpy(tmp, _str, n+1);delete[] _str;_str = tmp;_capacity = n;}
}

PS:
调用reserve扩容时,需要先将原有数据拷贝到新空间,且仅当n大于当前_capacity时执行才有实际意义。

resize

void resize(size_t n,char ch = '\0')
{if (n < _size){_size = n;_str[_size] = '\0';}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}
}

PS:使用resize缩小空间时需手动添加’\0’进行截断。

push_back

void push_back(char c)
{if (_size == _capacity){//2倍扩容size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size++] = c;_str[_size] = '\0';//插入字符需要手动补充'\0'
}

append

void append(const char* s)
{size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}memcpy(_str + _size, s,len+1);_size += len;
}
void append(const string& str) {  append(str.c_str());}

PS:插入流程如下:首先检查空间是否合适,接着通过reserve调整空间大小,最后执行数据插入操作。

+=

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

PS:实现+=比较简单,主要是函数push_back 和 append 的复用。

insert

void insert(size_t pos,size_t n ,char ch)
{assert(pos <= size());if (size() + n > _capacity){reserve(size() + n);}//挪动数据int end = _size;*** end 也需要是 int 类型for (int i = end; i >=(int)pos; i--){_str[i + n] = _str[i];}//拷贝for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}

PS:为什么要将pos强制转换为int类型?
size_t 类型始终为非负数。
当i递减到0时,再执行–i操作不会得到-1,
而是会回绕成一个极大的无符号数值(例如18446744073709551615)。
这将导致死循环,并引发内存访问异常。(非常重要) 其他方法我认为比较难以理解故不作解释。

erase

void erase(size_t pos, size_t len = npos)
{assert(pos < size());if (len == npos || len + pos >= _size){_size = pos;_str[_size] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size -= len;}
}

PS:
检查位置:assert(pos < size()); 确保删除起点合法。
判断删除范围:
如果删到末尾(len == npos 或者超出范围),直接把 _size 截到 pos。
否则就把后面的内容往前搬。
更新 _size:调整字符串长度并补 ‘\0’ 结尾(end==_size 可以添加到’\0’)。

迭代器

begin

end

iterator begin(){ 	return _str;}iterator end(){ return _str+size();}const_iterator begin()const  {  return _str;}const_iterator end()const{  return _str+size();}

PS:STL中的end()返回的是容器末尾元素的下一个位置,因此这里使用_str+_size表示。
PS:STL迭代器分为只读迭代器和可读写迭代器两种类型,因此需要区分这两类。

find

size_t find(const char* s, size_t pos = 0)const
{const char* ptr = strstr(_str + pos, s);return ptr ? (ptr - _str) : npos;
}size_t find(string& str, size_t pos = 0)const
{return find(str.c_str(), pos);
}

字符串匹配

size_t find(const char* s, size_t pos = 0)const{size_t len1 = _size;size_t len2 = strlen(s);if (len2 == 0) return pos;if (len1 < pos || len1 < len2 + pos) return npos;for (int i = pos; i < len1; i++){size_t j = 0;while (j < len2 && _str[i + j] == s[j])j++;if (j == len2) return i;}

PS:

字串提取

substr

string substr(size_t pos = 0, size_t len = npos) const
{assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);for (size_t i = 0 ; i < n; i++){tmp += _str[pos + i];}return tmp;  
}

PS:
检查位置合法:assert(pos < _size);
确定实际截取长度:
如果 len == npos 或超范围,就取 _size - pos。
否则用传入的 len。
创建结果字符串:预留空间 reserve(n)。
逐个字符拷贝:把 _str[pos ~ pos+n-1] 复制进 tmp。
返回子串。

总结

下文是vector的讲解和模拟实现,如果有余力将完成string的其余实现和部分题目。


文章转载自:

http://2zUTYfEg.tgnwt.cn
http://bdsebPF1.tgnwt.cn
http://tNLxlJud.tgnwt.cn
http://EBTQ4AYn.tgnwt.cn
http://WB3NibwS.tgnwt.cn
http://Zzngtkyt.tgnwt.cn
http://8JMUxFim.tgnwt.cn
http://7vPttdJv.tgnwt.cn
http://T6fCUMnU.tgnwt.cn
http://G0LLyLWf.tgnwt.cn
http://q2XBZY1u.tgnwt.cn
http://Yd3gdIzH.tgnwt.cn
http://vrBKvufs.tgnwt.cn
http://sCNNprGh.tgnwt.cn
http://Su7dYhTo.tgnwt.cn
http://qcLCxaCD.tgnwt.cn
http://u7k7pzKL.tgnwt.cn
http://zcr4wnT1.tgnwt.cn
http://0gSGVbS1.tgnwt.cn
http://AAq1YaOG.tgnwt.cn
http://PWomvWvY.tgnwt.cn
http://AW4aHddZ.tgnwt.cn
http://PY8y3Quo.tgnwt.cn
http://CFujVTxv.tgnwt.cn
http://6kg5G1Qt.tgnwt.cn
http://Z2jGwUq5.tgnwt.cn
http://FFlM50dL.tgnwt.cn
http://mdKCjFzm.tgnwt.cn
http://hGuqUZ5i.tgnwt.cn
http://M7Bd9qg9.tgnwt.cn
http://www.dtcms.com/a/377761.html

相关文章:

  • 打工人日报#20250910
  • LeetCode100-206反转链表
  • function-call怎么训练的,预料如何构建
  • OpenLayers数据源集成 -- 章节四:矢量格式图层详解
  • 220V供电遥测终端 220V供电测控终端 选型
  • 【LLM】Transformer注意力机制全解析:MHA到MLA
  • 三十六、案例-文件上传-阿里云OSS-集成
  • 网编.hw.9.10
  • 4215kg轻型载货汽车变速器设计cad+设计说明书
  • Python数据可视化科技图表绘制系列教程(七)
  • 【 VMware Workstation 提示“虚拟机已在使用”怎么办?一篇文章彻底解决!】
  • WebSocket网络编程深度实践:从协议原理到生产级应用
  • 数字健康新图景:AI健康小屋如何重塑我们的健康生活
  • ⚡ Linux sed 命令全面详解(包括参数、指令、模式空间、保持空间)
  • Codeforces Round 1049 (Div. 2) D题题解记录
  • 视频分类标注工具
  • 【学习】vue计算属性
  • Torch 安装
  • 如何使用 DeepSeek 帮助自己的工作?的技术文章大纲
  • Object.values(allImages).forEach(src => { }
  • git rebase 的使用场景
  • 嵌入式场景kvdb数据库的使用(二)——UnQLite数据库的移
  • 基于MQTT的实时消息推送系统设计与实现(Java后端+Vue前端)
  • 柔性数组与队列杂记
  • XCVP1902-2MSEVSVA6865 AMD 赛灵思 XilinxVersal Premium FPGA
  • iPaaS与ESB:企业集成方案的选择与实践!
  • [硬件电路-177]:如何用交流电流源(偏置电流+交变电流)模拟PD的暗电流 + 变化的光电流
  • O3.1 opencv高阶
  • 【JAVA】java的程序逻辑控制
  • 真正有效的数据指标体系应该长什么样?