【C++】string底层的实现原理(简单详细)

前言

本篇文章我将按照C++文档库中的模块顺序来实现和讲解其实现原理,我们只讲各板块中常用的

目录

一,Member functions(成员函数)

二、Iterators(迭代器)

三、Capacity(容器)

  1. 常见容器的实现
  2. 重点容器代码思想剖析

    四、Element access(成员访问)

    五、Modifiers(修改器)

    1. 常见修改器的实现
    2. 重点修改器代码思想剖析

      六、String operations(字符串操作)
      七、Non-member function overloads(非成员函数重载)
      八、整合版

      一、Member functions(成员函数)
      namespace L
      {class string{public://构造函数string(const char* str=""):_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造string(const string& str){_str = new char[str._capacity + 1];strcpy(_str, str._str);_capacity = str._capacity;_size = str._size;}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//指向字符串的指针size_t _size;//字符串的有效字符个数size_t _capacity;//字符串的容量大小};
      }
      
      二、Iterators(迭代器)
      namespace L
      {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;}private:char* _str;//指向字符串的指针size_t _size;//字符串的有效字符个数size_t _capacity;//字符串的容量大小};
      }
      

      string的迭代器我这里是用指针来实现的,迭代器主要就是通过begin(),end()来遍历字符串,由于我们实现了begin()和end()两个成员函数,所以我们在遍历的时候可以使用范围for来遍历字符串,范围for的底层就是替换成了迭代器

      三、Capacity(容器)

      1.常见容器的实现

      namespace L
      {class string{public:size_t size() const{return _size;}size_t capacity() const{return _capacity;}bool empty() const{return _size == 0;}void clear(){_size = 0;_str[_size] = '\0';}void resize(size_t n, char ch='\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;}}void reserve(size_t n){if (n > _capacity){char* temp = new char[n+1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}private:char* _str;size_t _size;size_t _capacity;};
      }
      

      2.重点代码思想剖析

      这个板块我们重点讲解两个成员函数,resize()和reserve()
      1、resize(size_t n, char ch='\0');
      ①功能描述:当n<=size (字符串的长度)时,只保留字符串的前n个字符;当n>size时 会引发扩容,并且从size位置开始一直到n位置的值都为ch;
      ②实现思想:当n<=size时,直接给n位置赋值成 ‘\0’(因为字符串的结束标识是\0),然后将有效字符个数改为n即可;当n>size时,从字符串的末尾开始添加字符直到size=n为止

      2、void reserve(size_t n);
      ①功能描述:reserve 主要是扩容,避免capacity多次扩容,影响效率;n<size时:不会缩容,也不会扩容;n>capacity时:会扩容
      ②实现思想:当n>capacity时,使用new动态开辟出一块大小为n+1的空间,多开1个空间是用来存放\0的,使用strcpy将原来的数据拷贝到新开的空间中,然后释放掉旧空间

      四、Element access(成员访问)
      namespace L
      {class string{public:char& operator[](size_t n) const{return _str[n];}private:char* _str;size_t _size;size_t _capacity;};
      }
      
      五、Modifiers(修改器)
      1、 常见修改器的实现
      namespace L
      {class string{public:void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos + 1;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strcpy(_str + _size, str);_size += len;}void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;}void insert(size_t pos,const char* str){assert(pos <= _size);int len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}int end = _size + len;while ((int)pos <= end - len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;}string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}private:char* _str;size_t _size;size_t _capacity;};
      }
      

      2、重点修改器的代码剖析
      void insert(size_t pos,const char* str);
      ①功能描述:往字符串中的任意位置插入一个字符串
      ②实现思想:先判断插入进来的字符串的长度+现有的有效字符个数会不会超过该字符串的容量,超过了就扩容,然后从pos位置开始,将其后的字符全部挪动len个字符,然后再使用strncpy拷贝这个字符串到其要插入的位置

      tips:容易犯错的点

      	void insert(size_t pos,const char* str){assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t end = _size + len;while (pos <= end - len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;}
      

      上面代码出现的问题,如下图解释

      在这里插入图片描述

      六、String operations(字符串操作)
      namespace L
      {class string{public:string substr(size_t pos = 0, size_t len = npos) const{assert(pos < _size);string temp;if (len == npos || len >= _size - pos){strcpy(temp._str, _str + pos);return temp;}else{for (size_t i = pos; i < len; i++){temp += _str[i];}_str[len] = '\0';return temp;}}size_t find(char c, size_t pos = 0) const{assert(pos < _size);for (size_t i = 0; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);char* p=strstr(_str, s);if (p){return p - _str;//指针相减得到他们之间的字符个数}else{return npos;}}private:char* _str;size_t _size;size_t _capacity;};
      }
      
      七、Non-member function overloads(非成员函数重载)
      namespace L
      {class string{public:private:char* _str;size_t _size;size_t _capacity;};ostream& operator<<(ostream& out, const string& str){for (size_t i = 0; i <str.size(); i++){out << str[i];}return out;}istream& operator>>(istream& in, string& str){char ch;ch = in.get();//get函数用于从输入流上读取一个字符while (ch != ' ' && ch != '\n'){str += ch;ch = in.get();}return in;}void swap(string& s1, string& s2){s1.swap(s2);}
      }
      

      八、整合版

      #pragma once
      #include<iostream>
      #include<assert.h>
      using namespace std;namespace L
      {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;}string(const char* str=""):_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//s1(s2)string(const string& str){_str = new char[str._capacity + 1];strcpy(_str, str._str);_capacity = str._capacity;_size = str._size;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}void reserve(size_t n){if (n > _capacity){char* temp = new char[n+1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strcpy(_str + _size, str);_size += len;}void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;}void insert(size_t pos,const char* str){assert(pos <= _size);int len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}int end = _size + len;while ((int)pos <= end - len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;}string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}bool empty() const{return _size == 0;}void clear(){_size = 0;_str[_size] = '\0';}void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}char& operator[](size_t n) const{return _str[n];}void resize(size_t n, char ch='\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;}}size_t find(char c, size_t pos = 0) const{assert(pos < _size);for (size_t i = 0; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);char* p=strstr(_str, s);if (p){return p - _str;}else{return npos;}}void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos + 1;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}string substr(size_t pos = 0, size_t len = npos) const{assert(pos < _size);string temp;if (len == npos || len >= _size - pos){strcpy(temp._str, _str + pos);return temp;}else{for (size_t i = pos; i < len; i++){temp += _str[i];}_str[len] = '\0';return temp;}}private:char* _str;size_t _size;size_t _capacity;public:static const int npos;};const int string::npos = -1;//静态成员在全局初始化ostream& operator<<(ostream& out, const string& str){for (size_t i = 0; i <str.size(); i++){out << str[i];}return out;}istream& operator>>(istream& in, string& str){char ch;ch = in.get();//get函数用于从输入流上读取一个字符while (ch != ' ' && ch != '\n'){str += ch;ch = in.get();}return in;}void swap(string& s1, string& s2){s1.swap(s2);}
      }
      

      本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1420888.html

      如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

      相关文章

      [AIGC] redis 持久化相关的几道面试题

      文章目录 1. 什么是Redis持久化&#xff1f;2. Redis 的持久化机制是什么&#xff1f;各自的优缺点&#xff1f;2.1 RDB&#xff08;Redis DataBase&#xff09;&#xff0c;快照2.2 AOF&#xff08;Append Only File&#xff09;&#xff0c;日志 3. 优缺点是什么&#xff1f;…

      【递归、回溯和剪枝】全排列 子集

      0.回溯算法介绍 什么是回溯算法 回溯算法是⼀种经典的递归算法&#xff0c;通常⽤于解决组合问题、排列问题和搜索问题等。 回溯算法的基本思想&#xff1a;从⼀个初始状态开始&#xff0c;按照⼀定的规则向前搜索&#xff0c;当搜索到某个状态⽆法前进时&#xff0c;回退到前…

      Electron、QT、WPF三强争霸,该支持谁呢?

      Electron、QT、WPF都是跨平台的桌面应用开发框架&#xff0c;都是非常流行的&#xff0c;作为开发者该选用哪个呢&#xff1f;本文从多个角度分析一下。 一、定义 Electron、Qt 和 WPF 都是用于创建桌面应用程序的框架或工具&#xff0c;它们各自有着不同的特点和优势。 Elec…

      插件:Best HTTP

      一、简介 WebSocket WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性的连接&#xff0c;并进行双向数据传输。…

      C++笔记(体系结构与内核分析)

      1.OOP面向对象编程 vs. GP泛型编程 OOP将data和method放在一起&#xff0c;目的是通过封装、继承、多态提高软件的可维护性和可扩展性GP将data和method分开&#xff0c;可以将任何容器与任何算法结合使用&#xff0c;只要容器满足塞饭所需的迭代器类型 2.算法与仿函数的区别 …

      Java8 ConcurrentHashMap 存储、扩容源码阅读

      文章目录 1. 概述2. 入门实例3. 属性4. 核心方法4.1 put4.2 initTable4.3 transfer4.4 sizeCtl4.5 sizeCtl bug 1. 概述 ConcurrentHashMap 是线程安全且高效的 HashMap。 HashMap 可以看下我这篇 传送门 。 2. 入门实例 public class MyStudy {public static void main(St…

      【从零开始学架构 架构基础】二 架构设计的复杂度来源:高性能复杂度来源

      架构设计的复杂度来源其实就是架构设计要解决的问题&#xff0c;主要有如下几个&#xff1a;高性能、高可用、可扩展、低成本、安全、规模。复杂度的关键&#xff0c;就是新旧技术之间不是完全的替代关系&#xff0c;有交叉&#xff0c;有各自的特点&#xff0c;所以才需要具体…

      DELL T630服务器iDRAC分辨率调整办法

      对于Dell T630服务器的iDRAC分辨率调整&#xff0c;您需要登录到iDRAC的Web界面。以下是详细的步骤&#xff1a; 登录iDRAC&#xff1a;在浏览器中输入iDRAC的IP地址&#xff0c;然后使用用户名&#xff08;通常是“root”&#xff09;和密码登录。 导航到虚拟控制台&#xff…

      在Ubuntu 24.04 LTS (Noble Numbat)上安装nfs server以及nfs client

      在Ubuntu 24.04 LTS (Noble Numbat)上,我使用的是最小化安装, 当然server版本的Ubuntu在安装的时候可能会有网络不通的问题,解决办法见如下文章: ubuntu 24.04 server 仅NAT模式上网设置静态IP设置-CSDN博客文章浏览阅读489次,点赞9次,收藏3次。在Ubuntu 24.04 上设置网…

      2024 cleanmymac有没有必要买呢,全反面分析

      在使用mac时&#xff0c;小编遇到了运行内存不足、硬盘空间不足的情况。遇到这种情况&#xff0c;我们可以借助经典的电脑深度清理软件——CleanMyMac X&#xff0c;清理不常用的软件和系统垃圾&#xff0c;非常好用&#xff01;不过&#xff0c;有许多网友发现CleanMyMac X有免…

      AI算法-高数5-线性代数1-基本概念、向量

      线性代数&#xff1a;主要研究1、张量>CV计算机视觉 2、研究张量的线性关系。 深度学习的表现之所以能够超过传统的机器学习算法离不开神经网络&#xff0c;然而神经网络最基本的数据结构就是向量和矩阵&#xff0c;神经网络的输入是向量&#xff0c;然后通过每个矩阵对向量…

      【动态规划】子序列问题I|最长递增子序列|摆动序列|最长递增子序列的个数|最长数对链

      一、最长递增子序列 300. 最长递增子序列 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.注意子序列和子数组的区别&#xff1a; (1)子序列&#xff1a;要求顺序是固定的&#xff08;要求没那么高&#xff0c;所以子序列就多一些&#xff09; (2)子数组&#xff1a;要…

      报告!Golang冲上来啦!

      今天又来讲Go语言&#xff0c;根据全球知名的编程语言排行榜TIOBE在4月份公布的最新的编程语言排名&#xff0c;令人瞩目的是&#xff0c;Go语言已经跃升至历史最高位&#xff0c;位列排行榜第七名&#xff0c;并且Go语言是前十榜单中最年轻的编程语言。这一成绩不仅彰显了Go语…

      数据结构·一篇搞定栈!

      好久不见&#xff0c;超级想念 废话不多说&#xff0c;直接看 引言 在数据结构的大家族中&#xff0c;栈&#xff08;Stack&#xff09;是一种非常重要的线性数据结构&#xff0c;它的特点是后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09;。栈在程序设…

      MFC的CPen与CBush画图对象使用步骤

      在MFC中&#xff0c;CPen和CBrush是两个常用的绘图对象&#xff0c;分别用于定义画笔和画刷&#xff0c;可以用于绘制图形、填充区域等。下面我会详细介绍如何在MFC中使用CPen和CBrush来绘制和填充图形。 使用 CPen 绘制图形&#xff1a; 创建 CPen 对象&#xff1a; 首先&am…

      练习题(2024/5/12)

      1二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4…

      鸿蒙开发学习:初探【ArkUI-X】

      ArkTS 是华为自研的开发语言。它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配 ArkUI 框架&#xff0c;扩展了声明式 UI 、状态管理等相应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨端应用。 ArkUI-X 进一步将 ArkUI 扩展到了多个 OS 平台…

      C++组合类

      类的数据成员不但可以是基本类型&#xff0c;也可以是其它类的对象。 组合类就是指一个类包含其他类的对象作为该类的数据成员。 当组合类创建对象时&#xff0c;其中包含的各个数据成员对象应首先被创建。因此&#xff0c;在创建类的对象时&#xff0c;既要对本类的基本…

      C#中数组与列表,集合等的联系

      C#中&#xff0c;所有数组都自动继承于System.Array这个抽象类&#xff0c;数组都为引用类型&#xff0c; 所有对数组的更新都会导致源数组的元素值的篡改。 而所有集合的根都来自可枚举接口IEnumerable 数组有三种样式&#xff1a; 数组的Rank&#xff08;秩&#xff09;属…

      软考高项总结:第20章高级项目管理

      一、高级项目管理基础 1、项目组合主要是为实现战略目标而进行的多个项目。比如村里要发展经济。制定了一个发展战略。要修路,要建厂,要种树,要整田。这些方面都有很多项目,在一起形成了项目组合。 2、项目集中的项目之间存在着关联关系,要统一考虑以实现更大利益。比如…