★ C++基础篇 ★ string类的实现
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C++基础篇第五章下篇----string类的模拟实现 ~
上篇:★ C++基础篇 ★ string类-CSDN博客
C++基础篇专栏:★ C++基础篇 ★_椎名澄嵐的博客-CSDN博客

目录
一 基础结构
二 迭代器
三 各种接口
3.1 功能接口
3.2 插入相关接口
3.3 删除相关接口
3.4 查找相关接口
3.5 拷贝构造(深拷贝)& 赋值
3.6 其他接口
一 基础结构
首先,浅写一个基本的string类,但会有几个问题~
// string.h
namespace zmcl
{class string{public:string() // 无参构造:_str(nullptr) // 问题2,_size(0),_capacity(0){}string(const char* str) // 带参构造{_size = strlen(str);_capacity = _size; // _capacity不包含'/0'_str = new char[_capacity + 1];strcpy(_str, str);}const char* c_str() // 获取字符串{return _str;}~string() // 析构{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;};void test_string1() // 问题1{string s1;string("hello zmcl");}
}
// string.cpp
#include"string.h"
//.....//test.cpp
#include"string.h"
int main()
{zmcl::test_string1();return 0;
}
问题1:以上程序在运行时会报一个链接错误:

因为头文件会在两个.cpp文件中分别展开,类中的函数会因为内联,不会放入符号表中,不会报错。但类外的全局函数test_string1,会在两个cpp文件中各有一份,导致重定义。
两个解决方法:
- 改为static void test_string1()
- 声明定义分离
问题2:
void test_string1()
{string s1;string s2("hello zmcl");cout << s1.c_str() << endl;cout << s2.c_str() << endl;
}
此时程序会报错,因为类中的无参构造用了初始化列表,_str为空指针,并没有自带 '\0' ,而在获取字符串的时候,遇到' \0' 才会返回,所以,无参构造应该为:
string():_str(new char[1] {'\0'}), _size(0), _capacity(0)
{}
那~可不可以把有参和无参构造合并一下呢~
string(const char* str = "") // 给空会自带 \0
{_size = strlen(str);_capacity = _size; // _capacity不包含'/0'_str = new char[_capacity + 1];strcpy(_str, str); // 先拷贝后判断,\0也会拷贝
}
最后的基本结构为~~
namespace zmcl
{class string{public:string(const char* str = "") // 给空会自带 \0{_size = strlen(str);_capacity = _size; // _capacity不包含'/0'_str = new char[_capacity + 1];strcpy(_str, str); // 先拷贝后判断,\0也会拷贝}const char* c_str(){return _str;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;};
}
二 迭代器
class string
{
public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//...
}
有了迭代器,就可以使用迭代器遍历和范围for遍历了~
三 各种接口
3.1 功能接口
- reserve
void string::reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1]; //开新数组strcpy(tmp, _str); // 拷贝原数组delete[] _str; // 删除原数组_str = tmp; // 指向新数组_capacity = n;}
}
3.2 插入相关接口
- push_back 尾插字符
void string::push_back(char ch)
{if (_size == _capacity) // 扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0'; // 兼容C
}
空扩4,非空扩2倍~
最后位置要加 \0 ,不然会出现乱码~
- append 尾插字符串
void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){// 大于2倍开多少给多少 不够2倍按2倍开reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}strcpy(_str + _size, str);_size += len;
}
- operator+= 尾插
string& string::operator+=(char ch)
{push_back(ch);return *this;
}string& string::operator+=(const char* str)
{append(str);return *this;
}
测试一下~

- insert 指定位置插字符
void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity) // 扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪数据size_t end = _size;while (end >= pos)// pos~end位置的值往后移一格{_str[end + 1] = _str[end];--end;}_str[pos] = ch;
}
上程序在头插时会出大问题,end作为一个无符号整数,和另一个无符号整数pos比较时,end 不会减到负数,就算end改为int类型,比较时也会隐式类型转换,变为无符号整数。
两个解决方法:
- 比较时强转
while (end >= (int)pos)
- 使end指向\0后一个位置
void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity) // 扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪数据size_t end = _size + 1; // 使end指向\0后一个位置while (end > pos) // pos~end-1 位置的值往后移一格{_str[end] = _str[end - 1];--end;}_str[pos] = ch;
}
- insert 指定位置插字符串
void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (len == 0) // 0直接返回return; // 扩容if (_size + len > _capacity){// 大于2倍开多少给多少 不够2倍按2倍开reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}// 挪数据size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// 填数据for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

3.3 删除相关接口
- erase 删除一段
// string.h
// 声明时给默认
void erase(size_t pos, size_t len = npos);
static const size_t npos;// string.cpp
const size_t string::npos = -1;
// 定义时给不给默认
void string::erase(size_t pos, size_t len)
{if (len > _size - pos)// pos后全删{_str[pos] = '\0';_size = pos;}else // 删了中间一段{for (size_t i = pos + len; i < _size; i++) // 往前调{_str[i - len] = _str[i];}_size -= len;_str[_size] = '\0';}
}
3.4 查找相关接口
- find 查找字符或字符串
// string.h 声明
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
// string.cpp 定义
size_t string::find(char ch, size_t pos)
{for (size_t i = 0; i < _size; i++){if (_str[i] == ch)return i;}return npos;
}
size_t string::find(const char* str, size_t pos)
{assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (str == nullptr)return npos;elsereturn ptr - _str;
}
- substr 查找子串
// string.h 声明
string substr(size_t pos = 0, size_t len = npos);
// string.cpp 定义
string string::substr(size_t pos, size_t len)
{assert(pos < _size);if (len > _size - pos) // 取到结尾的情况{len = _size - pos; // len大于剩余字符长度,更新len}string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;
}
我们小测一下这三个查找接口~
void test_string()
{string s("zmcl.cpp.zip");size_t pos = s.find(".");string suffix = s.substr(pos);cout << suffix.c_str() << endl;
}
报错了了了!!!>_<

此时因为没有手写拷贝构造,编译器使用了浅拷贝,sub出函数会销毁,release版本下进行了优化suffix直接充当了sub,故不会报错~
但是程序还需要改进~(加拷贝构造~)
3.5 拷贝构造(深拷贝)& 赋值
以下程序使用了浅拷贝在debug 和 release版本下都会崩~
void test_string()
{string s("zmcl.cpp.zip");string copy(s);cout << copy.c_str() << endl;
}

- 手写深拷贝~
// s2(s1) s2-this s1-s
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

加完以后就不会报错辣~
当然还有更简单的写法!直接创一个新的,再交换给this~
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
// s2(s1) s2-this s1-s
string(const string& s)
{string tmp(s._str);swap(tmp);
}
此时s为空会出现随机值,需要在类里给初始值~
private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;
- 赋值
不手动写赋值也会出问题,甚至会发生内存泄漏~
// s2 = s1 s2-this s1-s
string& operator=(const string& s)
{if (this != &s) // 防止自己给自己赋值,直接返回~{delete[] _str; // 预防浪费空间,直接删除原空间_str = new char[s._capacity + 1]; // 开辟新空间strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;
}
赋值也可以使用现代写法~让tmp当黑奴()
// s2 = s1 s2-this s1-s
string& operator=(const string& s)
{if (this != &s){string tmp(s);swap(tmp);}return *this;
}
甚至还能再简化~
string& operator=(string tmp)
{swap(tmp);return *this;
}
3.6 其他接口
- operator 比较
operator 系列写两个,其他复用~
bool operator<(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) > 0;
}
bool operator>(const string& s1, const string& s2)
{return !(s1 <= s2);
}
bool operator<=(const string& s1, const string& s2)
{return s1 < s2 || s1 == s2;
}
bool operator>=(const string& s1, const string& s2)
{return s1 > s2 || s1 == s2;
}
bool operator==(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}
- io流
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 int N = 256;// buff是临时数组,不会永久存在,并且栈中开空间很快char buff[N]; int i = 0;char ch;in >> ch;while (ch != ' ' && ch != '\n'){// 先传到buff数组,满了则+=到s并重置ibuff[i++] = ch;if (i == 255){buff[i] = '\0';s += buff;i = 0;}// in >> ch; 这样写遇到空格不会停止ch = in.get();}// 若buff中还有内容if (i > 0){buff[i] = '\0';s += buff;}return in;
}

~完~
相关文章:
★ C++基础篇 ★ string类的实现
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇:★ C基础篇 ★ string类-CSDN博客 C基础篇专栏:★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 目录 一 基础结构 二 迭代器 …...
rman compress
级别 初始 备完 耗时 low 1804 3572 0:10 High 1812 3176 2:00 MEDIUM 1820 3288 0:13 BASIC 1828 3444 0:56 ---不如MEDIUM,时间还长 NO COMPRESS 1820 5924 0:5 R…...
创建一个Oracle版本的JDK的Docker镜像
背景说明 OpenJDK 和Oracle JDK 一般情况下我们选择OpenJDK,两者针对大部分场景都可以满足,有些地方例如反射技术获得某些包路径下的类对象等,有时候选择OpenJDK会导致空指针异常。 两者在底层实现方面有部分区别。 创建镜像 这里是Linux…...
Harmony OS DevEco Studio 如何导入第三方库(以lottie为例)?-- HarmonyOS自学2
在做鸿蒙开发时,离不开第三方库的引入 一.有哪些支持的Harmony OS的 第三方库? 第三方库下载地址: 1 tpc_resource: 三方组件资源汇总 2 OpenHarmony三方库中心仓 二. 如何加入到DevEco Studio工程 以 lottie为例 OpenHarmony-TPC/lot…...
JAVA数据导出为Excel
目录 一、导入依赖 二、使用的相关类 1、XSSFWorkbook 构造方法 创建表 操作表 保存表 样式和格式 日期处理 密码保护 其他 2、XSSFSheet 获取属性和信息 行操作 列操作 表的属性 合并单元格 保护表 页眉和页脚 注释 其它 3、XSSFRow 获取属性和信息 单…...
【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234
1. 力扣876:链表的中间节点 1.1 题目: 给你单链表的头结点 head ,请你找出并返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 示例 1: 输入:head [1,2,3,4,5] 输出:[3,4,…...
第十五届蓝桥杯图形化省赛题目及解析
第十五届蓝桥杯图形化省赛题目及解析 一. 单选题 1. 运行以下程序,角色会说( )? A、29 B、31 C、33 D、35 正确答案:C 答案解析: 重复执行直到m>n不成立,即重复执行直到m<n。所有当m小于或者 等于n时&…...
linux下NTP服务器实战(chrony软件)
linux下NTP服务器实战(chrony软件) 记录linux下NTP服务器搭建及相关管理操作,使用chrony软件包安装部署。相比ntp服务,Chrony服务适用于更高精度、更高稳定性、自动化等场景。 1. 安装 chrony 在大多数Linux发行版上,chrony可以通过包管理…...
Java设计模式之命令模式介绍和案例示范
一、命令模式简介 命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化、对请求排队或记录日志,以及支持可撤销的操作。命令模式的核心思想是将发出请…...
Leetcode面试经典150题-74.搜索二维矩阵
解法都在代码里,不懂就留言或者私信 二分查找,比较简单 class Solution {/**解题思路:每一行有序、每一列也有序,只是整体不是严格有序的,那我们需要找一个点,只能往两个方向走,往一个方向走是…...
【数字集成电路与系统设计】基本的组合逻辑电路
目录 一、简单例子引入 1.1 端口声明 1.1.2 Verilog实现 1.1.3 Chisel实现 逐行解释 1.2 内部逻辑实现 1.2.1 Verilog实现 1.2.2 Chisel实现 Chisel 关键点解释 1.3 常用的硬件原语 二、Chisel主要数据类型介绍 2.1 数据类型 2.2 数据宽度 2.3 数据转换 2.4 运算…...
11. 建立你的第一个Web3项目
11. 建立你的第一个Web3项目 在这一部分,我们将带你一步步地建立一个简单的Web3项目,从环境搭建到智能合约的创建与部署,再到开发一个去中心化应用(dApp)并与智能合约交互。这是你迈向Web3开发的第一步。 1. 环境搭建…...
衡石分析平台使用手册-容器部署
容器部署 本文介绍如何在容器上部署 HENGSHI SENSE,以及部署后如何进行版本升级和数据备份。 部署前准备工作 单机部署前,请完成如下准备工作。 1.检查 docker 的环境。需要满足 Docker 版本 > 17.09安装 docker-compose。 2.获取并导入离线…...
静态库,动态库以及makefile基础
一.静态(链接)库 libfun.a 静态链接进可执行程序 可执行程序偏大 运行时只需要可执行程序即可 生成静态库步骤 gcc -c fun.c -o fun.o ar rcv libfun.a fun.o //需要用.o文件生成数据库 运行 gcc main.c libfun.a 二.动态库 libfun.so 动…...
Python基础语法(1)上
常量和表达式 我们可以把 Python 当成一个计算器,来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问,为什么不是1.6666666666666667呢? 其实在编程中,一般没有“四舍五入”这样的规则…...
使用 Python/java/go做一个微信机器人
E云是一套完整的的第三方服务平台,包含微信API服务、企微API服务、SCRM系统定制、企微系统定制、服务类软件定制等模块,本文档主要讲述个微API服务相关,以下简称API,它能处理用户微信中的各种事件,提供了开发者与个微对…...
【北京迅为】iTOP-i.MX6开发板使用手册第四部分固件编译第十四章非设备树Android4.4系统编译
可根据用户需求更换,百变定制,高端产品无忧! 迅为IMX6Q兼容四核商业级 、双核商业级、四核工业级 、更可提供i.MX6Q家族PLUS版本核心板。 核心板采用十层PCB沉金盲埋设计,更能保证电磁兼容与系统稳定。 公众号:迅为电…...
测评造假?Mistral首个多模态模型Pixtral 12B发布
测评造假?Mistral首个多模态模型Pixtral 12B发布! 近日,法国人工智能(AI)初创公司Mistral于9月11日宣布推出其首款多模态AI大模型——Pixtral 12B,成功吸引了全球科技界的广泛关注。这款集图像与文本处理能…...
【Java-简单练习题】
1.”AABBBCCC“>>"A2B3C3" public class Test6 {public static void main(String[] args) {String ns "AABBBCCCC";String retcompress(ns);System.out.println(ret);}public static String compress(String str) {StringBuilder ret new StringB…...
Notepad++ 下载安装教程
目录 1.下教程 2.安装教程 1.下教程 Downloads | Notepad (notepad-plus-plus.org) 进入下载地址后选择最新版点击连接 点击链接后,向下滑动,下载适合自己电脑版本的安装包 这里大家没有梯子可能打不开页面,可以直接从本文开头下载。 2.安…...
【YOLO目标检测全栈实战】39 多模型流水线:当YOLO遇上OCR和语音合成,如何让四个模型“共线生产”?
DIA DALI,我们把187ms的串行方案优化到15ms,性能提升12倍。但说实话,那只是两个模型之间的“小打小闹”。 今天我们要面对的,是一个真正的“四国联军”——YOLOv8检测、ResNet分类、OCR文字识别、语音合成,四个模型串联成一条生产线。 你可能会想:“不就是把四个模型串…...
AI超级计算机架构演进与性能优化解析
1. AI超级计算机的技术架构演进AI超级计算机的核心架构在过去六年发生了显著变化。2019年主流系统如Summit主要采用NVIDIA V100 GPU,而到2025年,xAI的Colossus已升级到H100/H200混合架构。这种演进主要体现在三个维度:1.1 计算单元设计原理现…...
如何用D2DX游戏优化工具突破《暗黑破坏神2》25fps限制:宽屏适配与性能提升的终极解决方案
如何用D2DX游戏优化工具突破《暗黑破坏神2》25fps限制:宽屏适配与性能提升的终极解决方案 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/…...
TVA动态阈值在昇腾310的适配要点
重磅预告:本专栏将独家连载系列丛书《智能体视觉技术与应用》部分精华内容,该书是世界首套系统阐述“因式智能体”视觉理论与实践的专著,特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、“…...
广告投放ROI断崖式下滑?立即排查ElevenLabs这4个语音合成致命偏差,2小时内修复
更多请点击: https://intelliparadigm.com 第一章:广告投放ROI断崖式下滑的语音归因真相 当广告主发现iOS 17设备上语音搜索转化路径中归因丢失率高达68%,却仍在依赖传统点击归因(Click-Through Attribution)模型时&a…...
深度学习立体匹配:从MC-CNN架构解析到工程实践优化
1. 项目概述:从传统到深度,立体匹配的范式革新在计算机视觉领域,立体匹配是一个经典且核心的问题,它的目标是从一对经过校正的左右图像中,为每个像素找到其在另一幅图像中的对应点,从而计算出场景的深度信息…...
如何深度定制MPC-HC实现专业级影音播放:终极实战配置指南
如何深度定制MPC-HC实现专业级影音播放:终极实战配置指南 【免费下载链接】mpc-hc MPC-HCs main repository. For support use our Trac: https://trac.mpc-hc.org/ 项目地址: https://gitcode.com/gh_mirrors/mpc/mpc-hc 想要将MPC-HC从普通播放器升级为专业…...
I2C总线设计实战:从物理层到协议层,解决多设备挂载与信号完整性问题
1. 项目概述:从“能挂多少”到“如何挂好”的深度思考“I2C总线上最多能挂多少个设备?” 这几乎是每个嵌入式工程师在项目初期都会问的问题。乍一看,答案似乎很简单:7位地址能寻址128个,10位地址能寻址1024个。但如果你…...
【NotebookLM学术写作黄金法则】:20年科研老炮亲授5大避坑指南与3步合规提速法
更多请点击: https://intelliparadigm.com 第一章:NotebookLM学术写作规范的底层逻辑与认知革命 NotebookLM 并非传统意义上的文档编辑器,而是一个以“语义锚点”和“引用可追溯性”为基石的学术协作文本引擎。其底层逻辑颠覆了线性写作范式…...
终极指南:如何使用ViGEmBus虚拟游戏控制器驱动程序提升Windows游戏体验
终极指南:如何使用ViGEmBus虚拟游戏控制器驱动程序提升Windows游戏体验 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 你是否曾经遇到过想在Win…...
