[C++]string类模拟实现
目录
前言:
1. string框架构造
2. 默认函数
2.1 构造函数
2.2 析构函数
2.3 拷贝构造
2.4 赋值重载
3. 迭代器
4. 整体程序
前言:
本篇文章模拟实现了C++中string的部分功能,有助于大家了解和熟悉string类,虽然这个类不难实现,但是其中有些小细节还是可以细细品味的。
1. string框架构造
我们得知道编写一个类的首要步骤不是直接开写代码,而是需要思考这个类需要用什么样的数据结构?需要准备哪些参数,这个类的作用域在哪里等等内容。
首先,咱们知道string类是有多个版本的,不同版本下的string类的变量是不同的,就我所知,在vs下string的实现是下方的格式,实际上比我写的要复杂,但是能够表示。
class string{
private:size_t _size;
size_t _capacity;
char* _buf;
char _arr[16];
};
这样做的用处主要是空间换取时间,当我们只是一个小字符串,那么就直接将数据存到了_arr的数组当中,因为是直接写在类里面的数组,所以就省去了向堆申请空间这一过程,如果数据大于了_arr数组大小,那么系统才会向堆申请空间。所以在vs下的string类有28个字节。
在Linux下则不同,string类有8个字节,结构如下:至于为什么是8个字节,那是因为Linux下跑C++代码默认是64位机哦,我现在还不会改为32位运行,请原谅博主。
class string{
private:
m_string* _point;
}
其中m_string是一个自定义类型,里面存了和vs下差不多样式的变量,只不过没有数组的存在,如下:
class m_string{
size_t _size;
size_t _capacity;
char* _buf;
};
这样做是因为Linux有一个特性,那就是赌,它赌我们在拷贝时不需要更改,不改那么他就直接将指针指向这个空间,一旦需要更改,那他就会触发写时拷贝这个牛技术。如下图:

对此我只能是表示,这些大佬们写代码是真牛哇,为了提升代码效率都能卷成这样了,我们自己的实现就不用这么高级的操作了,太难写了。既然是为了理解string类,所以我决定用下方的结构:(简单清楚好写)
class string{
private:
char* _buf;
size_t _size;
size_t _capacity;
};
还有一点,我们的这个类尽量写在一个自己的命名空间当中,防止与库中的string起冲突。
如下:
namespace yf
{class string{ private:char* _buf;size_t _size;size_t _capacity;};
}
2. 默认函数
2.1 构造函数
先看一下C++库中提供的构造函数:

真是有够恐怖的,这提供的接口有点猛,不过我们呢也不需要写这么多,写几个常用的就好。还有就是这些接口设计得有一些不合理,比如说default和from c-string两个函数很明显是可以写为一个函数的,以缺省参数来表示的,不过也能理解,毕竟C++是为了兼容C的。
代码:
//构造函数string(const char* str = ""):_size(strlen(str))
{_capacity = _size; _buf = new char[_capacity + 1];strcpy(_buf, str);
}
上方我讲解了可以用缺省参数来合并两个函数接口,但是大家再不看我的代码之前能想出来吗?除此之外,我还想了两个方法,一个是 ‘ ’,另一个是“\0”,有人看出不对了吗,这两个方式都是不行的,第二种倒也不是不行,而是太多余了,本身“”里面就已经有了一个‘\0’了显得我们不够专业,那这可不行。第一种用字符方式缺省那是真的错得离谱,我们之后可是要用字符串呢,用字符接收?这很明显不行哇,所以这种情况是一定要避免的。不然以后去公司面试,让我们写个类都乱写,简历上还写着熟悉C++,这不扯淡嘛。还有一点,为了保持new空间和delete保持一致,
然后我们的构造函数当中还用了初始化列表这一概念,通过我们传入的字符串长度去初始化_size,然后_capacity又在下方被赋值,再为_buf向堆上申请_capacity+1个字节大小的空间,为什么加1,当然是为了存我们的'\0'哇,小笨蛋,最后再使用strcpy函数将字符拷贝到我们的空间当中。
测试用例:
void test1()
{yf::string str1;yf::string str2("hello");yf::string str3("good morning xxxxxxxxxxxxx");
}
2.2 析构函数
大伙们回忆一下,为什么我们要有析构函数?因为我们向堆空间申请了空间了,所以需要释放,避免造成内存泄漏的过程。
//析构
~string()
{delete[] _buf;_size = 0;_capacity = 0;
}
看着这里释放内存的方式是delete[] _buf,也就与我们的构造函数对应起来了,如果构造函数的缺省参数是‘’的话,这里就不好析构,就得分情况,这不是脱了裤子放屁——多此一举嘛。
2.3 拷贝构造
拷贝构造和构造差不多,我也就不多做解释了,只是要注意参数得用引用。
string(const string& str)
{_capacity = str._capacity;_size = str._size;_buf = new char[str._capacity + 1];strcpy(_buf, str._buf);
}
2.4 赋值重载
如果是没有数据的重载,好,简单,但是呢,我的这个字符串本来是有数据的,另外被拷贝对象有大于的情况,有小于的情况,有等于的情况,如果拷贝错误怎么办?原来的数据这么办?这一些列的问题问下来还简单嘛?依然简单,只不过多数人在一时间想不到完全实现功能的代码罢了。
//赋值重载
string& operator=(const string& str)
{//不能自己拷贝自己if (this != &str){//当前容量大于被拷贝对象并不多时if (_capacity - str._capacity < 10){strcpy(_buf, str._buf);_capacity = str._capacity;_size = str._size;}else{//考虑到new失败的情况char* temp = new char[str._capacity + 1];strcpy(temp, str._buf);delete[] _buf;_capacity = str._capacity;_size = str._size;}}return *this;
}
请看我的代码,首先我们不能直接赋值给自己,这样会让我们把数据给delete掉了,导致整个程序出BUG了还不知道,这是很危险的。然后呢,我还考虑到了,当当前对象的容量大于被拷贝对象,并且大于的值并不多时,可以直接拷贝,不用delete后再整个赋值,节省了时间。
在下方需要delete的过程中,我们需要考虑到当new失败之后会怎么样,所以不能先释放掉原空间,需要创建一个变量去接收被拷贝对象的数据,如果new失败了,外部的try会直接接收到。
3. 迭代器
说起string类等一系列库类,里面都是提供了迭代器的,我们也模仿着在我们的类里面实现迭代器,我们呢还是从简,用指针来实现迭代器,不过我们不能将迭代器理解为指针,只是指针能够实现。
定义:
typedef char* iterator;
typedef const char* const_iterator;
对应接口:
//迭代器begin
iterator begin()
{return _buf;
}
const_iterator begin() const
{return _buf;
}//迭代器end
iterator end()
{return _buf + _size;
}const_iterator end() const
{return _buf + _size;
}
此时,我们的string类就能实现范围for这个语法糖了,至于为什么能实现,我只能说,范围for底层就是迭代器。
测试:
void test2()
{const yf::string str1("hello world");yf::string::const_iterator it = str1.begin();while (it != str1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : str1){cout << e << " ";}cout << endl;
}

4. 整体程序
其余的函数都是对于string类的补充,比如reserve、resize、+=、[]、<<、>>函数等等,博主很懒,并且认为这些大家都是有基础能实现的所以就不做讲解了,附下代码:
#pragma once#include<iostream>
#include<string>
#include<assert.h>
using std::cout;
using std::cin;
using std::endl;namespace yf
{class string{friend std::ostream& operator<<(std::ostream& out, const string& str);friend std::istream& operator>>(std::istream& in, yf::string& str);public:typedef char* iterator;typedef const char* const_iterator;//构造函数string(const char* str = ""):_size(strlen(str)){_capacity = _size; _buf = new char[_capacity + 1];strcpy(_buf, str);}//拷贝构造string(const string& str){_capacity = str._capacity;_size = str._size;_buf = new char[str._capacity + 1];strcpy(_buf, str._buf);}//赋值重载string& operator=(const string& str){//不能自己拷贝自己if (this != &str){//当前容量大于被拷贝对象并不多时if (_capacity - str._capacity < 10){strcpy(_buf, str._buf);_capacity = str._capacity;_size = str._size;}else{//考虑到new失败的情况char* temp = new char[str._capacity + 1];strcpy(temp, str._buf);delete[] _buf;_capacity = str._capacity;_size = str._size;}}return *this;}char& operator[](size_t pos){assert(pos < _size);return _buf[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _buf[pos];}//tidy_capacityvoid reserve(size_t n);void resize(size_t n, char c = '\0');//addvoid push_back(char c);void append(const string& str);void append(const char* s);string& operator+=(char c){push_back(c);return *this;}string& operator+=(const string& str){append(str);return *this;}string& operator+=(const char* s){append(s);return *this;}//insertstring& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);string& insert(size_t pos, const string& str);//迭代器beginiterator begin(){return _buf;}const_iterator begin() const{return _buf;}//迭代器enditerator end(){return _buf + _size;}const_iterator end() const{return _buf + _size;}//输出void print() const{cout << _buf << endl;}const char* c_str() const{return _buf;}//大小size_t size() const {return _size;}//容量size_t capacity() const{return _capacity;}//匹配字符size_t find_first_of(char c, size_t pos = 0){const_iterator it = begin();it += pos;while (it != end()){if (*it == c){return (it - begin());}it++;}return -1;}//获取字串string substr(size_t pos = 0, size_t len = -1) const;//析构~string(){delete[] _buf;_size = 0;_capacity = 0;}const size_t npos = -1;private:char* _buf;size_t _size;size_t _capacity;};
}
#define _CRT_SECURE_NO_WARNINGS 1#include"string.h"//tidy_capacity->reserve
void yf::string::reserve(size_t n)
{if (n > _capacity){char* temp = new char[n + 1];strcpy(temp, _buf);delete[] _buf;_buf = temp;_capacity = n;}
}//tidy_capacity->resize
void yf::string::resize(size_t n, char c)
{//长度小于_size时,需要删除n位置后的数据if (n < _size){_size = n;_buf[_size] = '\0';}else{//当n>_size有两种情况,大于capacity和小于capacityif (n > _capacity){//大于需要扩容操作reserve(n); //复用reserve}//二者都有同样的需求,对后面的数据初始化为cwhile (_size != n){_buf[_size] = c;++_size;}_buf[_size] = '\0';}
}//add
void yf::string::push_back(char c)
{if (_size + 1 > _capacity){//加一预防_capacity初始为0reserve(2 * _capacity + 1);}_buf[_size++] = c;_buf[_size] = '\0';
}void yf::string::append(const string& str)
{if (_size + str._size > _capacity){reserve(_size + str._size);}size_t len = str._size;int i = 0;while (i!=len){_buf[_size++] = str[i];i++;}_buf[_size] = '\0';
}void yf::string::append(const char* s)
{size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}while (*s != '\0'){_buf[_size++] = *s;s++;}_buf[_size] = '\0';
}//insert
yf::string& yf::string::insert(size_t pos, size_t n, char c)
{if (_size + n > _capacity){reserve(_size + n);}size_t end = _size + n;while (end - n + 1 > pos){_buf[end] = _buf[end - n];--end;}for (size_t i = 0; i < n;++i){_buf[pos + i] = c;}_size += n;return *this;
}//插入字符串
yf::string& yf::string::insert(size_t pos, const char* s)
{size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end - len + 1 > pos){_buf[end] = _buf[end - len];--end;}strncpy(_buf + pos, s, len);_size += len;return *this;
}yf::string& yf::string::insert(size_t pos, const yf::string& str)
{size_t len = str._size;if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end - len + 1 > pos){_buf[end] = _buf[end - len];--end;}strncpy(_buf + pos, str._buf, len);_size += len;return *this;
}//获取子串
yf::string yf::string::substr(size_t pos, size_t len) const
{yf::string::const_iterator it = begin();yf::string temp;it += pos;if (len > size())len = size();while (it != begin() + len){temp += *it;++it;}return temp;
}//流插入
std::ostream& yf::operator<<(std::ostream& out, const yf::string& s)
{for (auto ch : s){out << ch;}return out;
}//流提取
std::istream& yf::operator>>(std::istream& in, yf::string& s)
{//从缓冲区获取字符char ch = in.get();while (ch != '\n'){s += ch;//记录上一次位置,拿到下一个字符ch = in.get();}return in;
}
相关文章:
[C++]string类模拟实现
目录 前言: 1. string框架构造 2. 默认函数 2.1 构造函数 2.2 析构函数 2.3 拷贝构造 2.4 赋值重载 3. 迭代器 4. 整体程序 前言: 本篇文章模拟实现了C中string的部分功能,有助于大家了解和熟悉string类,虽然这个类不难实…...
一个更适合Java初学者的轻量级开发工具:BlueJ
Java是世界上最流行的编程语言之一,它被广泛用于从Web开发到移动应用的各种应用程序。大部分Java工程师主要是用IDEA、Eclipse为主,这两个开发工具由于有强大的能力,所以复杂度上就更高一些。如果您刚刚开始使用Java,或者您更适合…...
从程序员到项目组长,要经历六重修炼
最近和粉丝朋友们交流时发现,有很多刚刚开始做项目组长的朋友自我认可度非常低,感觉做组长之后天天打杂,技术也荒废了。领导天天找你要成果,下属天天找你说困难,你在中间受领导和下属的夹板气。时间久了,你…...
我的 System Verilog 学习记录(5)
、 引言 本文简单介绍 System Verilog 语言的 控制流。 前文链接: 我的 System Verilog 学习记录(1) 我的 System Verilog 学习记录(2) 我的 System Verilog 学习记录(3) 我的 System Ver…...
多芯片设计 Designing For Multiple Die
Why a system-level approach is essential, and why its so challenging作者:Ann MutschlerAnn Mutschler is executive editor at Semiconductor Engineering.将多个裸片或芯粒集成到一个封装中,与将它们放在同一硅片上有着很大的区别。在同一硅片上&a…...
2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A(10)
目录 竞赛内容 模块A 基础设施设置与安全加固 一、项目和任务描述: 二、服务器环境说明 三、具体任务(每个任务得分以电子答题卡为准) A-1任务一 登录安全加固(Windows, Linux) 1.密码策略(Windows, …...
数据结构-简介
目录 1、简介 2、作用 3、分类 4、实现分类 1、简介 数据结构指的是组织和存储数据的方法。它涉及到一系列的算法和原则,用来设计和实现不同种类的数据类型,如数组、链表、树、图等等。数据结构的目的是在计算机程序中有效地管理和操作数据ÿ…...
python装饰器及其用法
python装饰器是什么? Python装饰器是一种语法结构,它可以让开发者在不修改原函数的基础上,在函数的前后运行额外的代码,这些代码可以达到修改函数行为的目的。Python装饰器的实质是一个可调用的对象,它可以接收函数作为参数…...
Appium自动化测试之启动时跳过初始化设置
Appium每次启动时都会检查和安装Appium Settings,这是完全没有必要的,在首次使用Appium连接设备是Appium Settings便已经安装好。怎样跳过安装Appium Settings呢?之前的做法是修改appium中的源文件中的android-helpers.js实现,如M…...
JavaScript DOM【快速掌握知识点】
目录 DOM简介 获取元素 修改元素 添加和移除元素 事件处理 DOM简介 JavaScript DOM 是指 JavaScript 中的文档对象模型(Document Object Model);它允许 JavaScript 与 HTML 页面交互,使开发者可以通过编程方式动态地修改网页…...
不需要高深技术,只需要Python:创建一个可定制的HTTP服务器!
目录 1、编写服务端代码,命名为httpserver.py文件。 2、编写网页htmlcss文件,命名为index.html和style.css文件。 3、复制htmlcss到服务端py文件同一文件夹下。 4、运行服务端程序。 5、浏览器中输入localhost:8080显示如下: 要编写一个简单的能发布…...
渗透测试常用浏览器插件汇总
1、shodan这个插件可以自动探测当前网站所属的国家、城市,解析IP地址以及开放的服务和端口,包括但不限于FTP、DNS、SSH或者其他服务等,属被动信息搜集中的一种。2、hackbar(收费之后用Max Hackerbar代替)这个插件可用于…...
社区1月月报|OceanBase 4.1 即将发版,哪些功能将会更新?
我们每个月都会和大家展开一次社区进展的汇报沟通会,希望通过更多的互动交流让OceanBase 开源社区更加透明,实现信息共享,也希望能营造更加轻松的氛围,为大家答疑解惑,让大家畅所欲言。如果您对我们的社区有任何建议&a…...
Javascript的API基本内容(二)
一、事件监听 结合 DOM 使用事件时,需要为 DOM 对象添加事件监听,等待事件发生(触发)时,便立即调用一个函数。 addEventListener 是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和…...
ChatGPT热度“狂飙”,OceanBase也去找它唠了唠
最近互联网的关键字 非 ChatGPT 莫属 就是这个小东西 集唠嗑、提问、答疑、科普、写作于一体 让我看看哪个孤独的打工人 还没和 ChatGPT 聊上一聊 有人说 ChatGPT 这么智能 或将取代人类的工作 OceanBase 的小编表示不服气 于是,抱着好奇之心试了一试 对 …...
HTTP协议基础知识点扫盲;HTTPS协议及密码学基础
目录 一、Http协议的特性 二、http协议的请求 1.请求行第一行,包含三个信息:请求方式,url,http协议版本 2.请求头浏览器向服务器发送一些状态数据,标识数据等等 3.请求主体请求代理端项服务器端,发送的…...
【golang/go语言】Go语言之反射
本文参考了李文周的博客——Go语言基础之反射。 一、反射初识 1. 什么是反射 在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够…...
Java+Swing+Mysql实现超市管理系统
一、系统介绍1.开发环境操作系统:Win10开发工具 :IDEA2018JDK版本:jdk1.8数据库:Mysql8.02.技术选型JavaSwingMysql3.功能模块4.系统功能1.系统登录登出管理员可以登录、退出系统2.商品信息管理管理员可以对商品信息进行查询、添加…...
华为OD机试题,用 Java 解【机器人走迷宫】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
软件测试基本概念
软件测试基本概念 1. 什么是软件测试 软件测试就是验证软件产品特性(功能, 界面, 兼容性, 性能…)是否符合用户的需求,同时软件测试不仅要测试系统是否做了其应该做的, 还需要测试系统是否未作其不应该做的。 2. 调试与测试 软件测试与调试的区别: …...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
