【STL源码剖析】string类模拟实现 了解底层-走进底层-掌握底层【超详细的注释和解释】
博主对大家的话
从今天开始,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的开始,后面,博主将会给大家带来vector
,list
等等STL容器的模拟实现,持续关注,订阅专栏,点赞收藏都是我创作的最大动力!
( 转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的https://blog.csdn.net/Yu_Cblog?type=blog
【STL源码剖析】string类模拟实现 了解底层-走进底层-掌握底层【超详细的注释和解释】的更多相关文章
- String -- 从源码剖析String类
几乎所有的 Java 面试都是以 String 开始的,String 源码属于所有源码中最基础.最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底. String 是如何实现的 ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- 【转载】STL"源码"剖析-重点知识总结
原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...
- STL"源码"剖析
STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...
- (原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
- 《STL源码剖析》环境配置
首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...
- STL源码剖析读书笔记之vector
STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...
- 深入源码剖析String,StringBuilder,StringBuffer
[String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- c++ stl源码剖析学习笔记(一)uninitialized_copy()函数
template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...
随机推荐
- 阿里云 Serverless 应用引擎(SAE)2
8月7日,阿里云 Serverless 应用引擎(SAE)2.0正式公测上线!全面升级后的SAE 2.0具备极简体验.标准开放.极致弹性三大优势,应用冷启动全面提效,秒级完成创建发布应用,应用成本下降 ...
- 八、java操作swift对象存储(静态大对象)
系列导航 一.swift对象存储环境搭建 二.swift添加存储策略 三.swift大对象--动态大对象 四.swift大对象--静态态大对象 五.java操作swift对象存储(官网样例) 六.ja ...
- excel常用函数整理(可检索)
目录: 一.数字函数 1.1 sum 1.2 sumif 1.3 sumifs 1.4 sumproduct 1.5 abs二.统计函数 2.1 count 2.2 counta 2.3 counti ...
- Go 标准库之 io.Copy 和 ioutil.ReadAll
1. go 标准库之 io.Copy 和 ioutil.ReadAll 1.1 介绍 go 标准库中通过 ioutil.ReadAll 实现数据流的读取,io.Copy 实现数据流的读取和写入. 那两 ...
- JVM 内存模型及特点总结
本文为博主原创,未经允许不得转载: JVM 内存区域主要分为线程私有区域[程序计数器.虚拟机栈.本地方法区].线程共享区域[JAVA 堆.方法区].直接内存. 线程私有数据区域生命周期与线程相同, 依 ...
- SV 字符串类型
概述 常见使用方式 string b; string b=""; // 拼接字符串 string a = {"hi",b}; // 将字符串a赋值给[15:0] ...
- springboot入参下划线转驼峰出参驼峰转下划线
springboot入参出参下划线转驼峰 前言 因为历史原因前端入参和出参都为下划线,下划线对有亿点强迫症的我来说是不可接受的.因此就有了下面这篇. 本篇基于之前的一篇springboot封装统一返回 ...
- [转帖]Nacos使用2.0.1版本启动出现9848端口错误的解决方式(亲测有效)
目录 一.背景 二.报错如下 三.报错原因 四.解决方式 一.背景 nacos服务端和客户端都是 2.x版本. centos7使用原始安装nacos单机版,没有使用docker安装naocs集群. 二 ...
- [转帖]SQL中 join 、in 、exists 使用场景和执行效率
https://www.jianshu.com/p/c825c9bf42c2 众所周知,在sql 中,join /in /exists 都可以用来实现,"查询A表中在(或者不在)B表中的记录 ...
- [转帖]Kafka 核心技术与实战学习笔记(七)kafka集群参数配置(上)
一.Broker 端参数 Broke存储信息配置 log.dirs:非常重要,指定Broker需要使用的若干文件目录路径,没有默认值必须亲自指定. log.dir:他只能表示单个路径,补充上一个参数用 ...