[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. 调试与测试 软件测试与调试的区别: …...
数学建模介绍
🚀write in front🚀 📜所属专栏: 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励…...
【LVGL】学习笔记--(2)GUI Guider的使用
基于上一篇【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL,已经成功地移植了LVGL到我们的嵌入式板子上,并配合磁控旋钮编码器(或者诸如触摸屏、按键、键盘等其他输入设备均可),实现了简单界面的显示工作。这一章将学习…...
OpenCV-PyQT项目实战(6)项目案例02:滚动条应用
欢迎关注『OpenCV-PyQT项目实战 Youcans』系列,持续更新中 OpenCV-PyQT项目实战(1)安装与环境配置 OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战(3)信号与槽机制 …...
3 决策树及Python实现
1 主要思想 1.1 数据 1.2 训练和使用模型 训练:建立模型(树) 测试:使用模型(树) Weka演示ID3(终端用户模式) 双击weka.jar选择Explorer载入weather.arff选择trees–>ID3构建树…...
小程序和Vue+uniapp+unicloud培训课件
文章目录**一、什么是小程序****1.1** **小程序简介****1.2** **小程序的特点****1.3** **小程序的开发流程**个人小程序和企业小程序的区别1.4 小程序代码构成1.4.1 JSON 配置1.4.2 WXML 模板**数据绑定**逻辑语法条件逻辑列表渲染模板引用共同属性1.4.3 WXSS 样式1.4.4 JS 逻…...
C语言--指针进阶2
目录前言函数指针函数指针数组指向函数指针数组的指针回调函数前言 本篇文章我们将继续学习指针进阶的有关内容 函数指针 我们依然用类比的方法1来理解函数指针这一全新的概念,如图1 我们用一段代码来验证一下: int Add(int x, int y) {return xy;…...
【步进电机和 Arduino】
【步进电机和 Arduino】 前言1. 什么是步进电机及其工作原理?1.1 步进电机结构1.2 绕线方式1.3 通电方式2. 如何使用Arduino和A17步进驱动器控制NEMA4988步进电机2.1 A4988 和 Arduino 连接2.2 测量AB相2.3 A4988 限流3. 步进电机和 Arduino3.1 示例代码 13.2 示例代码 24. 使…...
【面试一:|和||、和区别】
相同点: ||和&&都是逻辑运算符,而|和&是位运算符。位运算符的优先级要比逻辑运算符的优先级高。 &和&&的区别 &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运…...
【一天一门编程语言】使用汇编语言实现斐波那契数列
文章目录使用汇编语言实现斐波那契数列一、什么是斐波那契数列二、如何用汇编语言实现斐波那契数列一、汇编语言概念1.1 什么是汇编语言1.2 汇编语言的特点二、汇编语言指令2.1 简单指令2.2 复杂指令汇编语言程序结构代码实例指令集常用指令指令代码实例使用汇编语言实现斐波那…...
RabbitMQ实现死信队列
目录死信队列是什么怎样实现一个死信队列说明实现过程导入依赖添加配置编写mq配置类添加业务队列的消费者添加死信队列的消费者添加消息发送者添加消息测试类测试死信队列的应用场景总结死信队列是什么 “死信”是RabbitMQ中的一种消息机制,当你在消费消息时&#…...
安徽网站建设公司/优秀网站网页设计
Masked AutoEncoders(MAE) Top-1准确率87.8% masked autoencoders(MAE) 是一种可扩展的计算机视觉自监督学习方法。 本文的MAE方法很简单:mask输入图像的随机patch,并重建丢失的像素 。它基于两个核心设计的。 首先…...
建个网站要多少钱/百度的总部在哪里
文章目录前言连续控制DPGDPG的优化目标On-Policy DPGOff-Policy DPG随机高斯策略前言 本文总结《深度强化学习》中连续控制章节的内容,如有错误,欢迎指出。 连续控制 前面几篇博客总结的强化学习方法,动作空间都是离散有限的。但动作空间不…...
卓越亚马逊网站建设目的/做网站的网络公司
概述 Intellij IDEA真是越用越觉得它强大,它总是在我们写代码的时候,不时给我们来个小惊喜。出于对Intellij IDEA的喜爱,我决定写一个与其相关的专栏或者系列,把一些好用的Intellij IDEA技巧分享给大家。本文是这个系列的第一篇&a…...
建网站找汉狮/公司官网制作多少钱
扩展NSAttributedString 简单的实现方法是为NSAttributedString 添加一个category。 然后为此category添加额外的方法。 具体实现如下: [代码]c#/cpp/oc代码: interface NSAttributedString (Hyperlink) (id)hyperlinkFromString:(NSString*)inString wi…...
wordpress图片上传失败/网址推广
目录传送门:《Flutter快速上手指南》先导篇在 Dart 中,仅使用 int 和 double 两种数据类型来表示整数和浮点数。int不同于 Java 等语言,在 Dart 中,int 的取值范围是 -2^63 ~ 2^63 - 1 。var x 1; var hex 0xDEADBEE…...
文化建设基金管理有限公司网站/百度下载安装免费
为什么80%的码农都做不了架构师?>>> 用winusb 先安装winusb $ sudo add-apt-repository ppa:colingille/freshlight $ sudo apt-get update $ sudo apt-get install winusb 转载于:https://my.oschina.net/lyyrj/blog/505384...