博主对大家的话

从今天开始,STL源码剖析的专栏就正式上线了!其实在很多人学习C++过程中,都是只学习一些STL的使用方式,并不了解底层的实现。博主本人认为,这样的学习这样的技术是不深的。如果我们想要熟悉的掌握一门语言,我认为,底层的实现必不能少!
但是,想从0开始模拟实现STL的容器,需要我们熟悉C++的语法,特别是类和对象部分的知识!
博主学习C++到现在,我认为C++类和对象基本语法的学习比任何部分都要重要,而我花在这上面的时间也是最多的!只要搞清楚C++面向对象编程的基本规则,我们才能在STL的世界里游刃有余!
所以我希望大家在学习STL之前,先将数据结构与算法,C++的类和对象部分内容掌握熟悉!

前言

那么这里博主先安利一下一些干货满满的专栏啦!

数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
STL源码剖析专栏:STL容器的模拟实现 以STL源码为基础,模拟实现STL的各种容器。从底层开始,一步一步解密STL。掌握底层,才是真正学会STL
算法专栏:算法
力扣刷题专栏:Leetcode
C的深度解剖专栏:C语言的深度解剖
**

实现过程一些要注意的点

因为STL库里面的string其实是很多年发展出来的类,里面一共有106个成员函数。
在这里面其实并不是每一个我们都要去实现的,我们要去实现的其实是一些重要的,必备的,有代表性的功能。其实关于string类发展到现在这么久,其实很多人对它有很多不同的评价,这里博主分享一篇文章。

因此,在博主的模拟实现中,只提供了STL中的部分接口,但是这些,对于在初学阶段来说,足够了!
对于实现过程的一些细节问题,博主会在源代码的注释中指出!

STL中string类模拟实现

#pragma once
#include<cstring>
#include<cassert>
#include<iostream>
using namespace std; //为了区分和库里面的,我们用命名空间包起来
namespace MyString {
class string {
public:
//迭代器和const迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
//const迭代器 -- 只读不写
const_iterator begin() const {
return _str;
}
const_iterator end() const {
return _str + _size;
}
//构造
#if false
string()//初始化的时候一定不能给nullptr,因为如果创建一个空对象的时候,去打印
//就会造成空指针的解引用
//或者给全却称
:_str(new char[1]),
_size(0),
_capacity(0)
{
_str[0] = '\0';//给一个空间放'\0'
}
#endif
string(const char* str = "\0")//写""是等价的,因为""会自带'\0'
//:_str(new char[strlen(str) + 1]),
//_size(strlen(str)),
//_capacity(strlen(str)) -- 这样搞用三次strlen() -- 别用初始化列表了
{
//动态开辟空间
size_t len = strlen(str);
_str = new char[len + 1];
_size = len;
_capacity = len;
strcpy(_str, str);
}
//析构
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//拷贝构造(实现一个深拷贝)
//写法1:传统写法
#if 0
string(const string& s)
:_str(new char[s._capacity+1]),
_size(s._size),
_capacity(s._capacity)
{
strcpy(_str, s._str);
} //赋值重载
string& operator=(const string& s) {
if (this == &s)return *this;//自己给自己赋值 -- 直接避开
//无论什么情况,都选择把原来的空间释放掉+开新的空间+拷贝
char*tmp = new char[s._capacity + 1];//一定要记得+1
//new失败怎么办 -- 这样就会造成拷贝失败,而且原来的string被释放了,所以最好先new再delete原来的
strcpy(tmp, s._str);
delete[] _str; _str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
//这样写的话,自己给自己赋值就会出问题 -- 因为自己给自己释放了 -- 拷贝自己 -- 拷贝到的就是随机值!
}
#endif
//现代写法
//构造来当打工人
void swap(string& tmp) {
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string(const string& s)
: _str(nullptr),
_size(0),
_capacity(0)
{
//因为s里面的东西可能是随机值
//如果换给tmp之后,tmp出了作用域是会析构的 -- 析构随机的东西就会出问题
//析构0或者null是不会出问题的,所以初始化一下
//因为swap要频繁调用,我们这里自己再写一个最好
string tmp(s._str);
swap(tmp);//this->swap(tmp)
}
//写法2:
#if 0
string& operator=(const string& s) {
if (this == &s)return *this;//自己给自己赋值 -- 直接避开
string tmp(s);
swap(tmp);//this和swap换
return *this;
//把原来的s换给tmp之后 -- tmp还需要帮忙打扫s的空间,因为tmp是一个局部对象!
}
#endif
//写法3
string& operator=(string s) {//s就是顶替tmp,s就完成拷贝了,而且是局部对象!
swap(s);
return *this;
}
//在以后复杂数据结构的学习中 -- 现代写法的优势会更大 //size
size_t size()const {
return _size;
}
size_t capacity()const {
return _capacity;
}
//[]重载
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos) const { //这个是不能写的
assert(pos < _size);
return _str[pos];
}
//兼容C字符串接口
const char* c_str() {
return _str;
}
void reserve(size_t n) {
if (n > _capacity) {
//扩容不缩容
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str; _str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0') {
//开空间+初始化
if (n > _size) {
//插入数据
reserve(n);
for (size_t i = _size; i < n; i++) {
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else {
//删除数据
_str[n] = '\0';
_size = n;
}
}
void push_back(char ch) {
//不用去写CheckCapacity函数,我们先写reserve这个函数,复用!
#if 0
if (_size==_capacity)
{
//注意:如果一开始是用了构造的缺省值,也就是0,就不能扩二倍!
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';//一定要记得'\0'
#endif
insert(_size, ch);
}
void append(const char* str) {
#if 0
size_t len = strlen(str);
if (_size + len > _capacity)//注意,此时不能扩容二倍了!因为可能不够
{
reserve(_size + len);
}
strcpy(_str + _size, str);//哪个好?
//strcat(_str, str);//这个效率是比较低的 -- 因为要去strcat要去找'\0',持续追加的时候效率非常低!
_size += len;
#endif
insert(_size, str);
}
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
//insert
string& insert(size_t pos, char ch) {
assert(pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//pos==0情况第一种处理方法
#if 0
int end = _size;
while (end >= (int)pos) {
//注意:pos==0的时候会出问题,end--之后会变成-1->整型最大值,所以给pos强转一下,_size改成int就行
_str[end + 1] = _str[end];
--end;
}
#endif
//第二种处理方法(比较推荐)
size_t end = _size + 1;//这些都别改,继续用uint
while (end > pos)//这里写成大于!
{
_str[end] = _str[end - 1];//这里改成-1
--end;
}
_str[pos] = ch;
++_size;
return * this;
}
//实现这些接口一定要很细心很细心!
string& insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
//挪动数据
size_t end = _size + len;//这些都别改,继续用uint
while (end >= pos + len)//这里写成大于!
{
_str[end] = _str[end - len];//这里改成-1
--end;
}
//放数据
strncpy(_str + pos, str, len);//这里用ncpy,不要把'\0'拷贝进去
_size += len;
return *this;
}
//erase
string& erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (len == npos || pos + len >= _size) {
//相当于pos后面的全部删掉了
_str[pos] = '\0';
_size = pos;
}
else {
//删除部分
//需要挪动数据
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
void clear() {
_str[0] = '\0';
_size = 0;
}
//find系列
#if true
size_t find(char ch, size_t pos = 0) {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (ch == _str[i]) {
return i;
}
return npos;
}
}
size_t find(const char* sub, size_t pos = 0) {
//strstr -- 暴力
//字符串匹配算法 kmp/bm
assert(pos < _size);
assert(sub);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr) {
return npos;
}
return ptr - _str;
}
//substr -- 从pos位置开始,取len个字符
string substr(size_t pos = 0, size_t len = npos)const {
//不用改变自己,const也行
assert(pos < _size);
size_t realLen = len;
if (len == npos || pos + len > _size) {
//超范围了
//有多少取多少
realLen = _size - pos;
}
string sub;
for (size_t i = 0; i < realLen; ++i) {
sub += _str[pos + i];
}
return sub;
}
#endif
//运算符重载比较系列
#if true
bool operator>(const string& s)const {
return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const {
return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s)const {
return *this > s || *this == s;
}
bool operator<=(const string& s)const {
return !(*this > s);
}
bool operator<(const string& s)const {
return !(*this >= s);
}
bool operator!=(const string& s)const {
return !(*this == s);
}
#endif
private:
char* _str;
size_t _size;
size_t _capacity;
public:
//这个npos按道理在外面也可以取到才对
const static size_t npos = -1;
};
//流提取和流插入
//不是必须是友元的,因为不一定要访问私有,这里就不用
ostream& operator<<(ostream& out, const string& s) {
for (size_t i = 0; i < s.size(); i++) {
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s) { s.clear(); //输入字符串很长,不断+=,频繁扩容,效率很低,大家可以想办法优化一下
//1.reserve() -- 缺陷:浪费空间
//2.别动string先,先放到一个临时数组里面,因为这个临时数组是在栈上的 -- 出了>>就销毁了
char ch;
ch = in.get(); const size_t N = 32;
char buff[N];
size_t i = 0; //s.reserve(128);
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1) {//表示满了
buff[i] = '\0';
s += buff;//一批一批加到string里面
i = 0;
}
ch = in.get();
}
//把最后一批加上去
//buff相当于缓冲了
buff[i] = '\0';
s += buff;
return in;
//但是现在会有bug
//如果字符串里面原来有东西的话,会留下
//我们先要清空一下string
}
//size_t string::npos = -1;//在类外面定义
//但是const static变量可以在类里面给缺省,外面就不用写了,这是C++的特例
}

尾声

看到这里,相信大家对string类的模拟实现已经有一定的了解了!string的模拟实现,知识我们掌握STL的开始,后面,博主将会给大家带来vectorlist等等STL容器的模拟实现,持续关注,订阅专栏,点赞收藏都是我创作的最大动力!

( 转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的https://blog.csdn.net/Yu_Cblog?type=blog

【STL源码剖析】string类模拟实现 了解底层-走进底层-掌握底层【超详细的注释和解释】的更多相关文章

  1. String -- 从源码剖析String类

    几乎所有的 Java 面试都是以 String 开始的,String 源码属于所有源码中最基础.最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底. String 是如何实现的 ...

  2. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  3. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  4. STL"源码"剖析

    STL"源码"剖析-重点知识总结   STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...

  5. (原创滴~)STL源码剖析读书总结1——GP和内存管理

    读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...

  6. 《STL源码剖析》环境配置

    首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...

  7. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

  8. 深入源码剖析String,StringBuilder,StringBuffer

    [String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...

  9. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  10. c++ stl源码剖析学习笔记(一)uninitialized_copy()函数

    template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...

随机推荐

  1. The 18th Zhejiang Provincial Collegiate Programming Contest 补题记录(ACFGJLM)

    补题链接:Here A. League of Legends 签到题,求和判断即可 ll suma, sumb; void solve() { ll x; for (int i = 1; i < ...

  2. Codeforces Round #712 (Div. 2) 个人题解

    这一场打的又很差(掉分预定),D题想不出来. A. Déjà Vu 这题首先判断字符串是否全由 a 组成,如果是的话输出 NO int main() { ios_base::sync_with_std ...

  3. SpringCloud学习 系列三、 创建一个没有使用springCloud的服务提供者和消费者

    系列导航 SpringCloud学习 系列一. 前言-为什么要学习微服务 SpringCloud学习 系列二. 简介 SpringCloud学习 系列三. 创建一个没有使用springCloud的服务 ...

  4. el-date-picker 组件时间格式化方式

    1 <el-form-item label="安放龙骨时间"> 2 <el-date-picker 3 v-model="baseInfoForm.se ...

  5. C#设计模式14——模板方法的写法

    模板方法(Template Method)是一种设计模式,它定义了一个操作中的算法的骨架,将某些步骤推迟到子类中实现,从而使得子类可以在不改变算法骨架的情况下重新定义算法的某些步骤. 作用: 使用模板 ...

  6. windows无法远程访问liunx的mysql解决方案(8.0.27版本)

    一.安装后mysql后发现windows上的无法正常访问,报错如下: 不管是navicat软件,还是使用python的pymsql进行连接 1.navicat软件如下:"Access den ...

  7. Web API接口返回实现类集合的姿势了解

    大家好,我是沙漠尽头的狼. 一. 问题描述 如下图,定义两个子类Student和Employ,都继承自抽象类PersonBase: public abstract class PersonBase { ...

  8. 【KEIL 】Options for File

    使用" 项目 "窗口的上下文菜单打开此对话框 :菜单选项项目.该对话框包括带有三态替代项的复选框: -已选中且呈灰色 -属性是从父对象继承的.- 选中和白色 -为对象单独设置的属性 ...

  9. GitLab的安装、配置、使用

    前言 上周去参与"中国数字经济创新发展大会"了,然后又忙新项目的事情,博客又有一段时间没有更新,今天周一事情比较少,立刻开始写文,最近有挺多值得记录的东西~ 进入正文,最近我们搭了 ...

  10. drop_caches 的简单学习

    drop_caches 的简单学习 背景 最近一段时间一直在学习内存相关的知识 Linux系统里面的内存管理还是非常复杂的. 我这边理解 Linux从宏观层次的 段页式内存管理 到细节的buddy和s ...