【string类的简单模拟实现】

news/2025/1/12 3:45:19/

目录

1 类中成员变量的声明

2 迭代器

3 一些常用接口

4 六大默认函数

4.1 构造

4.2 拷贝构造

4.3 赋值运算符重载

4.4 析构

5 开空间&&增删查改

6 其他运算符重载


1 类中成员变量的声明

通过上一篇文章对string类的简单使用相信大家对于string类中成员变量已经很熟悉了,这里就不再多说了:

class string{private:char* _str;int _size;//当前有效数据个数int _capacity;//存储有效数据的最大容量,不包括'\0'static const size_t npos;typedef char* iterator;typedef const char* const_iterator;};

但是为了测试时避免与库里面的冲突我们就把该类放在一个独立的命名空间中:

namespace grm
{class string{private:char* _str;int _size;//当前有效数据个数int _capacity;//存储有效数据的最大容量,不包括'\0'static const size_t npos;typedef char* iterator;typedef const char* const_iterator;};
}

2 迭代器

        iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}

3 一些常用接口

        size_t size()const     //无论是不是const对象都能够调用{return _size;}char* c_str()const{return _str;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void clear()const{_str[0] = '\0';_size = 0;}size_t capacity()const{return _capacity;}bool empty()const{return _size == 0;}

4 六大默认函数

4.1 构造

       string(const char* s):_size(strlen(s)),_capacity(_size){_str = new char[_capacity + 1];//多开出一个空间用来存储'\0'strcpy(_str, s);}

但是我们发现如果是无参又该怎么办?

有人会说像这样特殊处理一下就好了:

        string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '\0';}

这样处理是没有问题的,但是我们学过缺省参数,在这里能不能够给一个缺省值呢?缺省值又该给那个呢?

C++中是这样处理的:

        string(const char* s="")//给一个空字符串:_size(strlen(s)),_capacity(_size){_str = new char[_capacity + 1];//多开出一个空间用来存储'\0'strcpy(_str, s);}

缺省值给的是一个空字符串。

4.2 拷贝构造

传统写法:

        //传统写法string(const string& s):_size(s._size),_capacity(s._capacity){_str = new char[_capacity+1];strcpy(_str, s._str);}

这种写法就是自己手动开空间拷贝。

现代写法:我们发现上面我们已经实现好了默认的构造函数,我们可以直接用构造函数来解决

        void swap(string& s){std::swap(s._str, _str);std::swap(s._capacity, _capacity);std::swap(s._size, _size);}//现代写法string(const string& s):_str(nullptr)//必须要给初始值,否则交换后调用析构函数就会析构随机值会崩溃,_size(0),_capacity(0){string tmp(s._str);swap(tmp);}

其中需要注意的是使用这种方式用初始化列表将_str初始化成nullptr,不然交换后调用tmp的析构函数就会释放随机地址而出错。

4.3 赋值运算符重载

与拷贝构造一样,赋值运算符重载也分为传统版本和现代版本。

传统版本:

        //传统写法string& operator=(const string& s){if (this != &s){//这种方式如果new失败了抛异常就会有问题,我们并不想_str维护的空间被释放delete[] _str;_str = new char[s._capacity+1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}

大家可以从注释中看见传统写法都是要自己手动开空间拷贝,而且大家也发现了代码中还多了一个判断this是否与&s相等,否则当直接delete_str后由于s与_str指向的是同一块空间,再对s解引用就是典型的野指针问题了。而且还有一个问题就是当我们new空间失败时我们并不想_str被释放,所以可以用下面这种写法:

        //传统写法string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity+1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

而且大家发现没有这样写即使不用写上面的if判断程序依旧可以正常运行,但是加上判断当自己给自己赋值时可以减少拷贝。

现代写法 方法1:

        //现代写法 方法1:string& operator=(const string& s){if (this != &s) //这里加上判断条件在自己给自己赋值能够减少拷贝,不加也是没有问题的{string tmp(s);swap(tmp);}return *this;}

现代写法 方法2:

        //现代写法 方法2:string& operator=(string s){swap(s);return *this;}

这种方式就更加巧妙了,直接用了传值传参,而传值传参就是一个拷贝构造。

4.4 析构

        ~string(){if (_str){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}}

5 开空间&&增删查改

void reserve(size_t n) //这里要求开n个有效空间,不包括'\0'{if (_capacity < n){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){if (n > _capacity){reserve(n);}memset(_str + _size, ch, n - _size);}_size = n;_str[_size] = '\0';}
        void push_back(char ch){if (_capacity == _size)reserve(_capacity == 0 ? 4 : _capacity * 2);_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* s){int len = strlen(s);if (_size + len > _capacity)reserve(_size + len);strcpy(_str + _size, s);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+= (const char* s){append(s);return *this;}size_t find(char ch){for (size_t i = 0; i < _size; i++)if (_str[i] == ch)return i;return -1;}size_t find(const char* s, size_t pos=0){int i = pos, j = 0;//i是主串遍历,j是子串遍历while (i < _size && j < strlen(s)){if (s[j] == _str[i]){i++;j++;}else{i = i - j + 1;j = 0;}}if (j == strlen(s))return i - j;elsereturn -1;}
        string& insert(size_t pos, const char* s)//在pos位置插入,字符串从pos位置开始插{assert(pos <= _size);int len = strlen(s);if (_size + len > _capacity)reserve(_size + len);int end = _size + len - 1;int gap = _size - pos + 1;while (gap--){_str[end] = _str[end - len];end--;}strncpy(_str + pos, s, len);_str[_size + len] = '\0';return *this;}string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos+1;}strcpy(_str + pos, _str + len + pos);_size -= len;return *this;}

这些代码我们之前上顺序表都已经比较详细的介绍了,大家可以参考参考。

我们可以自己测试一下来看看:

 

 可以发现是没有多大问题的。


6 其他运算符重载

        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 strcmp(_str, s._str) <= 0;}bool operator>=(const string& s)const{return strcmp(_str, s._str) >= 0;}bool operator!=(const string& s)const{return strcmp(_str, s._str) != 0;}ostream& operator<<(ostream& out, const string& s){//out << s.c_str();//能这么写吗? 不能,也许我们会在字符中间插入一个'\0'for (auto& e : s){out << e;}/*for (int i = 0; i < s.size(); i++){out << s[i];}*/return out;}istream& operator>>(istream& in, string& s){s.clear();//要加上这个,否则可能出错char ch = in.get();while (ch != '\0' && ch != '\n'){s += ch;ch = in.get();}return in;}

上面这几个比较可以自己重载成成员方法,当然也可以重载成全局函数。下面流提取运算符和流插入运算符我们在之前已经讲过了,里面需要注意的细节代码中都有注释。


有需要源码的老铁可以去博主的码云中获得:

nijhttps://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72https://gitee.com/monday-sky/text_cpp/commit/e4716c73bba49dd2bad8c86986d0cfe26beece72


http://www.ppmy.cn/news/21579.html

相关文章

【快速开始】vuejs环境搭建第一个项目

本篇包含vuejs环境安装以及通过vue客户端快速创建运行第一个项目。(注&#xff1a;以下内容均已windows平台为基准) 目录&#xff1a; 一、安装nodejs 二、配置国内源加速 三、安装vue客户端 四、创建第一个应用 1、安装nodejs&#xff1a; 1.1、下载 官网下载地址&…

AXI 总线协议学习笔记(3)

引言 上篇文章主要介绍了 AMBA以及AXI协议的基本内容&#xff0c;本文接续前文&#xff0c;继续介绍AXI协议的 原子访问、传输行为和事务顺序等。 AXI 总线协议学习笔记&#xff08;2&#xff09;https://blog.csdn.net/qq_43045275/article/details/128824643 原子访问 原子…

【C语言练习】杨氏矩阵、杨辉三角

目录一&#xff1a;杨氏矩阵&#x1f43b;何为杨氏矩阵&#xff1f;&#x1f43b;题目描述&#xff1a;&#x1f43b;思路一&#xff1a;&#x1f43b;思路二&#xff1a;二&#xff1a;杨辉三角&#x1f43b;何为杨辉三角&#xff1f;&#x1f43b;题目描述&#xff1a;&#…

[Spring Boot]11 使用@Cacheable注解实现Redis缓存

前言 为了方便讲解&#xff0c;模拟一个需要使用Redis缓存的场景&#xff0c;比如&#xff1a;一款APP的首页&#xff0c;由于其需要加载的数据量较大&#xff0c;于是决定把首页的部分数据使用Redis进行缓存&#xff0c;举例&#xff1a;比如要缓存首页的文章列表(ArticleLis…

最小生成树与最短路径

目录 一.最小生成树 1.1概念 1.2Kruskal算法 1.3Prim算法 二.最短路径 2.11单源最短路径--Dijkstra算法 2.1.2单源最短路径--Bellman-Ford算法 一.最小生成树 1.1概念 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去…

C++ 多态

目录 一. 概念 二. 定义与实现 1.构成条件 2.虚函数 3.虚函数的重写 4.协变 5.override 和 final 三. 抽象类 四. 虚函数表 1.单继承虚函数表 2.多继承的虚函数表 一. 概念 通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#x…

Python | 数据类型之集合 | 函数

知识目录一、集合简介1.1 集合的定义1.2 实例二、集合的基本操作三、函数3.1 函数的定义3.2 函数的调用3.3 全局变量和局部变量一、集合简介 1.1 集合的定义 集合&#xff08;set&#xff09;是一个无序的不重复元素序列。 可以使用大括号 { } 或者 set() 函数创建集合&…

4.6 QR分解二:Householder变换

1 Householder reflector Householder反射是这样子的(图片来自瑞典皇家理工学院)&#xff1a;   图中u是长度为1的向量。x是任意向量&#xff0c;H是u的Householder reflector。可见无论x是什么向量&#xff0c;HxHxHx始终除于和u正交的平面上。H和u的关系是&#xff1a; HI…