当前位置: 首页 > news >正文

★ 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&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇&#xff1a;★ C基础篇 ★ string类-CSDN博客 C基础篇专栏&#xff1a;★ 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&#xff0c;时间还长 NO COMPRESS 1820 5924 0:5 R…...

创建一个Oracle版本的JDK的Docker镜像

背景说明 OpenJDK 和Oracle JDK 一般情况下我们选择OpenJDK&#xff0c;两者针对大部分场景都可以满足&#xff0c;有些地方例如反射技术获得某些包路径下的类对象等&#xff0c;有时候选择OpenJDK会导致空指针异常。 两者在底层实现方面有部分区别。 创建镜像 这里是Linux…...

Harmony OS DevEco Studio 如何导入第三方库(以lottie为例)?-- HarmonyOS自学2

在做鸿蒙开发时&#xff0c;离不开第三方库的引入 一.有哪些支持的Harmony OS的 第三方库&#xff1f; 第三方库下载地址&#xff1a; 1 tpc_resource: 三方组件资源汇总 2 OpenHarmony三方库中心仓 二. 如何加入到DevEco Studio工程 以 lottie为例 OpenHarmony-TPC/lot…...

JAVA数据导出为Excel

目录 一、导入依赖 二、使用的相关类 1、XSSFWorkbook 构造方法 创建表 操作表 保存表 样式和格式 日期处理 密码保护 其他 2、XSSFSheet 获取属性和信息 行操作 列操作 表的属性 合并单元格 保护表 页眉和页脚 注释 其它 3、XSSFRow 获取属性和信息 单…...

【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234

1. 力扣876&#xff1a;链表的中间节点 1.1 题目&#xff1a; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,…...

第十五届蓝桥杯图形化省赛题目及解析

第十五届蓝桥杯图形化省赛题目及解析 一. 单选题 1. 运行以下程序&#xff0c;角色会说( )? A、29 B、31 C、33 D、35 正确答案&#xff1a;C 答案解析&#xff1a; 重复执行直到m>n不成立&#xff0c;即重复执行直到m<n。所有当m小于或者 等于n时&…...

linux下NTP服务器实战(chrony软件)

linux下NTP服务器实战(chrony软件) 记录linux下NTP服务器搭建及相关管理操作&#xff0c;使用chrony软件包安装部署。相比ntp服务&#xff0c;Chrony服务适用于更高精度、更高稳定性、自动化等场景。 1. 安装 chrony 在大多数Linux发行版上&#xff0c;chrony可以通过包管理…...

Java设计模式之命令模式介绍和案例示范

一、命令模式简介 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使你可以用不同的请求对客户端进行参数化、对请求排队或记录日志&#xff0c;以及支持可撤销的操作。命令模式的核心思想是将发出请…...

Leetcode面试经典150题-74.搜索二维矩阵

解法都在代码里&#xff0c;不懂就留言或者私信 二分查找&#xff0c;比较简单 class Solution {/**解题思路&#xff1a;每一行有序、每一列也有序&#xff0c;只是整体不是严格有序的&#xff0c;那我们需要找一个点&#xff0c;只能往两个方向走&#xff0c;往一个方向走是…...

【数字集成电路与系统设计】基本的组合逻辑电路

目录 一、简单例子引入 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项目 在这一部分&#xff0c;我们将带你一步步地建立一个简单的Web3项目&#xff0c;从环境搭建到智能合约的创建与部署&#xff0c;再到开发一个去中心化应用&#xff08;dApp&#xff09;并与智能合约交互。这是你迈向Web3开发的第一步。 1. 环境搭建…...

衡石分析平台使用手册-容器部署

容器部署​ 本文介绍如何在容器上部署 HENGSHI SENSE&#xff0c;以及部署后如何进行版本升级和数据备份。 部署前准备工作​ 单机部署前&#xff0c;请完成如下准备工作。 1.检查 docker 的环境。需要满足 Docker 版本 > 17.09安装 docker-compose。 2.获取并导入离线…...

静态库,动态库以及makefile基础

一.静态&#xff08;链接&#xff09;库 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 当成一个计算器&#xff0c;来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问&#xff0c;为什么不是1.6666666666666667呢&#xff1f; 其实在编程中&#xff0c;一般没有“四舍五入”这样的规则…...

使用 Python/java/go做一个微信机器人

E云是一套完整的的第三方服务平台&#xff0c;包含微信API服务、企微API服务、SCRM系统定制、企微系统定制、服务类软件定制等模块&#xff0c;本文档主要讲述个微API服务相关&#xff0c;以下简称API&#xff0c;它能处理用户微信中的各种事件&#xff0c;提供了开发者与个微对…...

【北京迅为】iTOP-i.MX6开发板使用手册第四部分固件编译第十四章非设备树Android4.4系统编译

可根据用户需求更换&#xff0c;百变定制&#xff0c;高端产品无忧&#xff01; 迅为IMX6Q兼容四核商业级 、双核商业级、四核工业级 、更可提供i.MX6Q家族PLUS版本核心板。 核心板采用十层PCB沉金盲埋设计&#xff0c;更能保证电磁兼容与系统稳定。 公众号&#xff1a;迅为电…...

测评造假?Mistral首个多模态模型Pixtral 12B发布

测评造假&#xff1f;Mistral首个多模态模型Pixtral 12B发布&#xff01; 近日&#xff0c;法国人工智能&#xff08;AI&#xff09;初创公司Mistral于9月11日宣布推出其首款多模态AI大模型——Pixtral 12B&#xff0c;成功吸引了全球科技界的广泛关注。这款集图像与文本处理能…...

【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) 进入下载地址后选择最新版点击连接 点击链接后&#xff0c;向下滑动&#xff0c;下载适合自己电脑版本的安装包 这里大家没有梯子可能打不开页面&#xff0c;可以直接从本文开头下载。 2.安…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...