★ 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.安…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...