c++领域展开第十五幕——STL(String类的模拟实现)超详细!!!!

c++领域展开第十五幕——STL(String类的模拟实现)超详细!!!!
在这里插入图片描述

文章目录

前言

上篇博客已经简单的介绍了string类的一些接口,并且做了一些了解
同时也刷了一些oj题目,熟练使用一些string的函数
今天我们来模拟实现一下string类
fellow me

string类的模拟实现

首先,string类是在stl库实现之前出现的,后面的stl库的内容大部分都和string类的接口类似
string类比起以前的一些数据结构,多了很多东西
迭代器就是一方面,能够更好的耦合算法和类和对象

string类——迭代器的模拟

我们先来看迭代器以及,string类的成员变量

classstring{public:typedefchar* iterator;//迭代器 begin endtypedefconstchar* const_iterator;constchar*c_str()const// 返回字符常量 {return _str;} iterator begin(){return _str;} iterator end(){return _str + _size;} const_iterator begin()const// 这里是const修饰的迭代器函数{// 在处理一些const修饰的对象时,如果直接访问上面的普通begin return _str;// 会引发权限的 放大 导致程序不能执行 所以这里复写了const的版本} const_iterator end()const{return _str + _size;} size_t size()const// 返回大小 // 这里从const是修饰大小和容量不能被改变{// 权限能缩小 不会放大return _size;} size_t capacity()const// 返回容量{return _size;}private:// char* _str =nullptr;// 字符串 size_t _size =0;// 大小 size_t _capacity =0;// 容量conststatic size_t npos;// 模拟string类里面缺省参数的 npos参数};

string类——默认成员函数

构造函数、析构函数、拷贝构造、运算符重载

在string类标准库里面有好几个版本的构造函数
这里模拟实现了其中两个
析构函数还是比较简单的
主要是拷贝构造函数两个版本来实现

string::string(size_t n,char ch)// 初始化列表:_str(newchar[n +1]),_size(n),_capacity(n){for(size_t i =0; i < n; i++){ _str[i]= ch;} _str[_size]='\0';} string::string(constchar* str)// 构造函数 :_size(strlen(str)){ _capacity = _size; _str =newchar[_size +1];strcpy(_str, str);}string::~string()// 析构函数{delete[] _str; _str =nullptr; _size =0; _capacity =0;} string::string(const string& s)// 拷贝构造 正常我们就是这样实现拷贝构造{// 防止在一些占用空间的参数 比如字符串 _str =newchar[s._capacity +1];// 调用系统的拷贝构造,导致浅拷贝,多次析构同一位置strcpy(_str, s._str);// 引起程序崩溃 _size = s._size; _capacity = s._capacity;}// 这里提供一种新的实现方法 同时也更简单明了// 而且这里的swap函数还有其他的作用 能被复用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 如果this和其交换{// 两种方法原理是一样的 string tmp(s._str);swap(tmp);}// 另外还有一个 销毁函数voidclear()// 销毁{ _str[0]='\0'; _size =0;}

下面就是运算符重载部分了

// s1 = s2// s1 = s1void string::swap(string& s){ std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);} string& string::operator=(const string& s)// 赋值运算符重载{if(this!=&s){ string tmp(s._str);swap(tmp);// 现代写法就是直接复用swap//delete[] _str; // 传统的就是这样一步一步赋值给另外一个对象//_str = new char[s._capacity + 1];//strcpy(_str, s._str);//_size = s._size;//_capacity = s._capacity;}return*this;}// 处理string像访问数组一样 直接堆 [] 进行重载char&operator[](size_t pos)// []重载 {assert(pos < _size);return _str[pos];}constchar&operator[](size_t pos)const// const修饰 不能改变内容{assert(pos < _size);return _str[pos];}// 下面就是一些判断字符串大小的函数 比较大小的函数还是比较简单的bool string::operator==(const string& s)const{returnstrcmp(_str, s._str)==0;// 这里复用了c语言里面的 strcmp 比较函数}bool string::operator!=(const string& s)const{return!(*this== s);}bool string::operator<(const string& s)const{returnstrcmp(_str, s._str)<0;}bool string::operator<=(const string& s)const{return*this< s ||*this== s;}bool string::operator>(const string& s)const{return!(*this<= s);}bool string::operator>=(const string& s)const{return!(*this< s);}

string类——常用函数接口

这里模拟实现一些string类的常用接口函数
reserve、push_back、append、insert、erase、find、substr等接口
实现string的增删改查功能
话不多说,上代码

// reserve函数 预留空间 void string::reserve(size_t n){if(n > _capacity){//cout << "reserve:" << n << endl;char* tmp =newchar[n +1];// 如果原本的空间没有到达指定大小 strcpy(tmp, _str);// 扩容delete[] _str; _str = tmp; _capacity = n;}}// 尾插函数 可以做 +=单个字符的复用函数void string::push_back(char ch){if(_size +1> _capacity)// 如果空间不够 扩容{// 扩容reserve(_capacity ==0?4: _capacity *2);} _str[_size]= ch;// 正常尾插 ++_size; _str[_size]='\0';// 注意\0 结尾}// append函数 在字符串尾部接入字符串 可以做+=字符串函数的复用void string::append(constchar* str){ size_t len =strlen(str);if(_size + len > _capacity){// 扩容 size_t newCapacity =2* _capacity;if(_size + len >2* _capacity){ newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str); _size += len;}// +=字符串 和 += 字符 函数的重载 string& string::operator+=(char ch){push_back(ch);return*this;} string& string::operator+=(constchar* str){append(str);return*this;}// 插入函数 在pos 位置 插入 n 个 ch 字符void string::insert(size_t pos, size_t n,char ch){assert(pos <= _size);assert(n >0);if(_size + n > _capacity)// 判断扩容{// 扩容 size_t newCapacity =2* _capacity;if(_size + n >2* _capacity){ newCapacity = _size + n;}reserve(newCapacity);}// 挪动数据/* int end = _size; // pos为size_t 在和int比较时 int 会转为size_t 导致程序出错 while (end >= (int)pos) { _str[end + n] = _str[end]; --end; }*/ size_t end = _size + n;// 这样写代码更健壮 而且end不会到负数部分while(end > pos + n -1){ _str[end]= _str[end - n];--end;}for(size_t i =0; i < n; i++){ _str[pos + i]= ch;} _size += n;/*string tmp(n, ch); insert(pos, tmp.c_str());*/// 这里复用insert的 在pos位置插入字符串}// insert 在pos位置插入字符串void string::insert(size_t pos,constchar* str){//assert(pos <= _size);//size_t n = strlen(str);//if (_size + n > _capacity)//{// // 扩容// size_t newCapacity = 2 * _capacity;// if (_size + n > 2 * _capacity)// {// newCapacity = _size + n;// }// reserve(newCapacity);//}//size_t end = _size + n;//while (end > pos + n - 1)//{// _str[end] = _str[end - n];// --end;//} // 正常写法  size_t n =strlen(str);// 间接扩容insert(pos, n,'x');// 直接用前面的insert复用 先插入 n 个字符 然后再覆盖一下for(size_t i =0; i < n; i++){ _str[pos + i]= str[i];// 直接覆盖 // 这样的代码就简洁很多 复用性高 好溯源}}// 删除函数 erase 指定位置删除长度为 len 的字符串void string::erase(size_t pos, size_t len){if(len >= _size - pos){// 删完了 _str[pos]='\0'; _size = pos;}else{ size_t end = pos + len;// size_t防止int强转while(end <= _size){ _str[end - len]= _str[end];++end;} _size -= len;}}// find查找函数 查找一个字符 size_t string::find(char ch, size_t pos){for(size_t i = pos; i < _size; i++){if(_str[i]== ch){return i;}}return npos;}// 查找一个字符串 size_t string::find(constchar* str, size_t pos){constchar* p =strstr(_str + pos, str);// 这里复用c里面的strstr if(p ==nullptr){return npos;}else{return p - _str;}}//substr 截取字符串 string string::substr(size_t pos, size_t len){ size_t leftlen = _size - pos;if(len > leftlen) len = leftlen; string tmp;// 构造tmp tmp.reserve(len);for(size_t i =0; i < len; i++){ tmp += _str[pos + i];}return tmp;}

string类——输入输出重载

剩下最后一个有点麻烦但又还好的接口
重载输入流和输出流,另外还有getline这个函数

// 输出函数是比较简单的 ostream&operator<<(ostream& out,const string& s){for(auto ch : s){ out << ch;}return out;}// 输入函数就有很多地方需要处理了 istream&operator>>(istream& in, string& s){ s.clear();// 先清除s里面的内容 防止意外// 输入短串,不会浪费空间// 输入长串,避免不断扩容const size_t N =1024;// 如果输入很长的字符串 一步一步输入的话会不断扩容char buff[N];// 这里开一个字符数组存起来 后序直接 += 就行 大大减少了扩容一步到位int i =0;char ch = in.get();while(ch !=' '&& ch !='\n'){ buff[i++]= ch;if(i == N -1){ buff[i]='\0'; s += buff; i =0;} ch = in.get();}if(i >0){ buff[i]='\0'; s += buff;}return in;}// getline 输入字符串 直到指定字符截止 istream&getline(istream& in, string& s,char delim){ s.clear();const size_t N =1024;char buff[N];int i =0;char ch = in.get();while(ch != delim)// 如果字符不是指定截止字符 就一直输入{// 不管是空格还是\n buff[i++]= ch;if(i == N -1){ buff[i]='\0'; s += buff; i =0;} ch = in.get();}if(i >0){ buff[i]='\0'; s += buff;}return in;}

整个string类的模拟实现就差不多到这里啦

总结

今天把string类模拟实现了一遍
在模拟实现过程中,发现了很多的问题
比如哪些重复且作用相同的代码,复用问题
充分利用已有的一些东西,比如std::swap,这个在拷贝构造和赋值重载的时候用的很爽
还有就是一些优化,哪些地方一直构造会费时费力,哪些地方直接复用效果会更好而且效率高
还有就是一些简单的语法知识,比如不经意间的类型强转过程会有意想不到的误区
总之,在模仿中一步一步优化,一步一步学习
借前人之鉴,涨己人之学识,加油

种一棵树最好的时间是十年前,其次是现在
在这里插入图片描述

Read more

《MySQL 表基础语法:从入门到熟练的核心技巧》

《MySQL 表基础语法:从入门到熟练的核心技巧》

前引:MySQL 表的增删查是数据库操作的基础,也是日常开发、数据分析中最高频的需求。很多初学者会卡在语法细节、场景适配或效率优化上,明明掌握了基础命令,实际应用中却频频出错。本文聚焦 “实用 + 避坑”,从核心语法到高频场景,再到优化技巧,帮你彻底吃透 MySQL 表增删查,告别 “只会用不会用对” 的尴尬 SQL查询中各个关键字的执行先后顺序: from > on> join > where > group by > with > having > select > distinct > order by > limit 目录 【一】增 (1)基本创建 (2)

By Ne0inhk
Spring Boot 4.0 与 Spring Cloud Alibaba 2025 整合完整指南

Spring Boot 4.0 与 Spring Cloud Alibaba 2025 整合完整指南

Spring Boot 4.0 与 Spring Cloud Alibaba 2025 整合完整指南 Spring Cloud全栈实战:手撸企业级项目,从入门到架构师! 概述 本文将详细介绍如何在 Spring Boot 4.0 中整合 Spring Cloud Alibaba 2025 最新版本。Spring Cloud Alibaba 为分布式应用开发提供了一站式解决方案,包含服务发现、配置管理、流量控制等核心功能。 环境要求 Spring Cloud全栈实战:手撸企业级项目,从入门到架构师! * JDK: 21+ (Spring Boot 4.0 要求) * Maven: 3.6+ 或 Gradle

By Ne0inhk
Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config

Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config

Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config. Run openclaw setup``control ui requires HTTPS or localhost``Proxy headers detected from untrusted address 按错误关键词 Ctrl+F 秒搜定位,建议收藏备用! 文章目录 * Docker 安装 OpenClaw 报错排查:如何解决`Gateway auth is set to token, but

By Ne0inhk

Spring AI 接入与简单使用:从环境搭建到多轮对话(JDK 17 + Spring Boot 3.5)

前言 Spring AI 是 Spring 生态中用于对接大语言模型(LLM)的抽象层,可以统一调用 OpenAI、Azure OpenAI、以及各类 OpenAI 兼容 API(如 DeepSeek、国内大模型等)。通过少量配置和几行代码,就能实现同步调用、流式输出,以及带上下文记忆的多轮对话,非常适合在现有 Spring Boot 项目里快速接入 AI 能力。本文基于 JDK 17、Spring Boot 3.5、Spring AI 1.1 记录从零接入到简单使用的完整过程,并总结对接时的注意项。 特别说明:本文除本段外,全部由AI生成。项目地址:https://gitee.com/husolar/

By Ne0inhk