★ 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.安…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
