【C++程序员的自我修炼】简单实现 string 库的常用接口函数

天接云涛连晓雾

星河欲转千帆舞


目录

string 类环境的搭建

实现 c_str() 函数

 实现 size() 函数

重载运算符operator[]

实现简单迭代器 begin()、end()

实现 reserve() 函数

实现 push_back() 函数

实现 append() 函数 

 重载运算符operator+=

实现 insert() 函数

实现 erase() 函数

 实现 find() 函数

实现 swap() 函数

 string 的深拷贝

 string 的赋值拷贝

实现 substr() 函数

实现 string 比较大小运算符重载

实现 clear() 函数

实现 string 中的流重载 

整体代码的实现

 契子

  前面我们已经大致了解到 string 库底层的许多接口函数,为了巩固理解以及快速上手今天我们就来实现一下简单 string 库的实现

  首先这里提前说明:只是简单的实现底层接口掌握其基本逻辑,并不会涉及太多,比如说模板以及迭代器的底层实现


string 类环境的搭建

首先我们练习的话可以声明与定义分开来写,然后再用一个单独的文件进行测试,像这样:

(这里顺便提一下)在C++ 标准 string 库里一般都是放在 .h文件 中且都是写成内联的形式 ~

首先,先把我们的构造、析构函数先搭建出来:

//string.h
#pragma once
#include<iostream>
#include<assert.h>
using std::cout;
using std::endl;
using std::cin;
using std::istream;
using std::ostream;namespace Mack
{class string{public:string(const char* str = "");~string();private:char* _str;size_t _capacity;size_t _size;const static size_t npos;};
}
//string.cpp
#include"string.h"
namespace Mack
{const size_t string::npos = -1;string::string(const char* str):_size(strlen(str)){_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}string::~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}
}

 然后把在标准库中我们的要用的函数的命名空间展开 ~

实现 c_str() 函数

在 C++ 中 c_str() 库函数的作用就是获取一个字符串的首元素地址,所以我们直接返回 _str 即可

    const char* c_str() const{return _str;}
//测试代码
void TestString()
{string s1("hello world");cout << s1.c_str() << endl;
}


 实现 size() 函数

在 C++ 中 size() 库函数的作用就是获取一个字符串的长度(不包含 '\0' 哦)

所以我们直接返回  _size 即可

	size_t size() const{return _size;}

重载运算符operator[]

对于运用场景的不同我们重载了两个operator[],不过基础逻辑还是一样的,提取当前 pos 位置的字符并返回,注意要判断 pos 的合法范围哦 ~ 不要越界了还不知道

	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 TestString()
{string s1("hello world");for (int i = 0; i < s1.size(); i++){cout << s1[i] << " ";}
}

这里和上面 size() 一起测试了,都没有问题 


实现简单迭代器 begin()、end()

对于迭代器我们的印象是这样一长串东西(是一个模板)

我们简单来看,其实可以用 指针 浅浅的代替一下:

begin() 返回的是字符串的首元素位置,我们直接返回 _str 即可

end() 返回的是字符串的末尾位置,我们之间返回 _str + _size 即可

//string.hclass string{public:typedef char* iterator;iterator begin();iterator end();//..};//string.cppstring::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}
//代码测试
void TestString()
{string s1("hello world");for (auto i : s1){cout << i << " ";}cout << endl;string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}
}

我们知道我们 范围for 的底层逻辑就是迭代器,这里浅浅的实现了一下 


实现 reserve() 函数

根据我们前面所学,我们知道 resserve 是对空间的预留,我们可以开辟一个我们自己预定的空间tmp,再将已有数据拷贝到该空间,并释放原 _str ,最后将 _str 指向 tmp 即可

	void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}


实现 push_back() 函数

push_back() 相当于我们顺序表的尾插,要先判断是否需要扩容,再考虑末尾插入,别忘了 '\0' 哦 

    void string::push_back(char ch){if (_capacity == _size){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}
//代码测试
void TestString()
{string s1("hello world");s1.push_back('x');cout << s1.c_str() << endl;s1.push_back('y');cout << s1.c_str() << endl;
}


实现 append() 函数 

append() 的常见用法就是在末尾追加一个字符串,可以将目标字符串 str 拷贝到源字符串 _str 中

(拷贝时覆盖 _str 中 '\0' 的位置,但是 str 末尾的 '\0' 会保留,所以我们不需要补 '\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 TestString()
{string s1("hello world");s1.append("xxxxx");cout << s1.c_str() << endl;
}


 重载运算符operator+=

在我们的印象中,operator+= 可以追加一个字符或者字符串,这样我们就可以复用一下

	string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}
//测试
void TestString()
{string s1("hello world");s1 += 'x';cout << s1.c_str() << endl;s1 += "yyy";cout << s1.c_str() << endl;
}


实现 insert() 函数

insert() 函数常用于我们的头插,但是经过我们上次的学习我们知道 insert() 的功能不仅于此

insert() 不仅可以指定位置插入字符,还可以指定位置插入字符串

<1> 指定位置插入字符:跟我们顺序表的指定位置插入差不多,挪动数据在进行插入即可

<2> 指定位置插入字符串:我们可以挪动将指定位置后的数据挪动 len 个(目标字符串 str 长度)然后将 str 拷贝到指定位置即可

	void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newcpacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcpacity);}size_t len = _size + 1;while (pos < len){_str[len] = _str[len - 1];len--;}_str[pos] = ch;_size++;}void insert(size_t pos, const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = len + _size;while (end > pos){_str[end] = _str[end - len];end--;}memcpy(_str + pos, str, len);_size += len;}
//代码测试
void TestString()
{string s1("hello world");s1.insert(0, 'x');cout << s1.c_str() << endl;s1.insert(0, "yyy");cout << s1.c_str() << endl;s1.insert(6, 'z');cout << s1.c_str() << endl;s1.insert(9, "mmm");cout << s1.c_str() << endl;
}


实现 erase() 函数

根据我们之前对 string 库的理解,可以知道 erase() 可以做到 删除指定位置 pos 的任意字符 len

一开始我们可以分类讨论一下:

<1> len 超过了从 pos 位置开始的剩余字符长度 -- 全部删完(将该位置用 '\0' 覆盖)

<2> len 没有超过 pos 位置开始的剩余字符长度 -- 删中间(将剩余字符拼接到 pos 位置)

	void erase(size_t pos, size_t len){assert(pos < _size);if(len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}
//代码测试
void TestString()
{string s1("hello world");s1.erase(8, 5);cout << s1.c_str() << endl;s1.erase(2, 3);cout << s1.c_str() << endl;
}


 实现 find() 函数

根据我们之前的学习,我们知道 find() 函数有两种常见的功能:
<1> 从 pos 位置开始在主串中查找单个字符

<2> 从 pos 位置开始在主串中查找与子串相匹配的位置:我们可以使用 BF 暴力算法, strstr 的底层就是 BF

	size_t find(char ch, size_t pos){for (int i = pos; i < size(); i++){if (_str[i] == ch)return i;}return npos;}size_t find(const char* sub, size_t pos){char* p = strstr(_str + pos, sub);return  p - _str;}


实现 swap() 函数

为什么 string 库里面要单独搞一个 swap 出来呢?用下面这个不香吗

虽然上面这个也能完成交换,但是造成了很大的代价!!!

简单来说明一下:

<1> 我们想交换 s1、s2 的数据首先要拷贝构造一个与 s1 一模一样的空间 c

<2> 然后开个与 s2 一样大的空间并将字符串拷贝过去,让 s1指向这块空间,并释放原空间

<3> 最后 s2 开一个与 c 一样大的空间并将字符串拷贝过去,让 s2 指向这块空间,并释放原空间

以上用了一个拷贝构造和两次赋值构造,都是深拷贝要开空间拷贝数据

所以 string 库里专门提供了一个内部的 swap :

这个 swap 只要借助一个变量交换一下指针指向即可

    void swap(string& s){std::swap(s._str, _str); std::swap(s._size, _size); std::swap(s._capacity, _capacity); }

只是交换指针指向的话,我们可以借助 std 内部的

//代码测试
void TestString()
{string s1("hello");string s2("world");s1.swap(s2);cout << s1.c_str() << endl;cout << s2.c_str() << endl;
}


 string 的深拷贝

    string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}

 string 的赋值拷贝

 string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}
//代码测试
void TestString()
{string s1("hello world");string s3;s3 = s1;cout << s1.c_str() << endl;
}


实现 substr() 函数

substr 常被我们用来提取字符串

<1> 从 pos 位置开始剩余字符串小于 len (要提取的字符串长度)我们就直接提取完

<2> 从 pos 位置开始剩余字符串不小于 len 则从 pos 位置开始一个字符一个字符的拷贝 len 个字符

    string substr(size_t pos, size_t len){if (_size - pos < len){string sub(_str + pos);return sub;}else{string sub;sub.reserve(len);for (int i = pos; i < pos + len; i++){sub += _str[i];}return sub;}}
//代码测试
void TestString()
{string s1("hello world");string s3 = s1.substr(6, 5);cout << s3.c_str() << endl;
}


 

实现 string 比较大小运算符重载

关于 string 的比较大小,我们可以借助 strcmp :

1.strcmp常用于比较字符串的大小2.第一个字符串大于第二个字符串,则返回大于0的值3.第一个字符串等于第二个字符串,则返回04.第一个字符串小于第二个字符串,则返回小于0的值5.比较两个字符串对应位置上的字符ASCLL码值的大小来判断字符串的大小

完成一部分比较后,我们还可以对结果进行复用 

	bool operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool operator>(const string& s) const{return !(*this <= s);}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 strcmp(_str, s._str) == 0;}bool operator!=(const string& s) const{return !(*this == s);}

 


实现 clear() 函数

clear 在我们 Linux 中经常使用就是清空的意思,而这里就是清空整个字符串

我们只需要将 '\0' 塞在开头即可

    void clear(){_str[0] = '\0';_size = 0;}


 

实现 string 中的流重载 

我们之前写日期类的时候是将流重载写成友元函数,而这里我们可以不写成友元,因为我们可以不访问类中元素就可以实现相关操作

我们先来看我们的流提取

	istream& operator>> (istream& is, string& str){str.clear();char ch;ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;}

cin 进行提取会将原内容进行覆盖,所以我们要提前清空一下数据

这里我们使用了 get() -- 显示提取一个字符 

我们把取到的字符追加到对应的字符串即可,但我们输入 空格 或者 回撤 键就会退出提取

	ostream& operator<< (ostream& os, const string& str){for (int i = 0; i < str.size(); i++){os << str[i];}return os;}

流插入我们可以一个字符一个字符的输出到我们的显示屏上

//代码测试
void TestString()
{string s1;cin >> s1;cout << s1 << endl;
}

整体代码的实现

string.h

#pragma once
#include<iostream>
#include<assert.h>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;namespace Mack
{class string{public:typedef char* iterator;string(const string& s);string& operator=(const string& s);string(const char* str = "");~string();const char* c_str() const;size_t size() const;char& operator[](size_t pos);const char& operator[](size_t pos) const;iterator begin();iterator end();void reserve(size_t n = 0);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* ch);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len);size_t find(char ch, size_t pos);size_t find(const char* sub, size_t pos);void swap(string& s);string substr(size_t pos, size_t len);bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;void clear();private:char* _str;size_t _capacity;size_t _size;const static size_t npos;};ostream& operator<< (ostream& os, const string& str);istream& operator>> (istream& is, string& str);
}

string.cpp

#include"string.h"namespace Mack
{string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}const size_t string::npos = -1;string::string(const char* str):_size(strlen(str)){_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}string::~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t pos) {assert(pos < _size);return _str[pos];}const char& string::operator[](size_t pos) const{assert(pos < _size);return _str[pos];}string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_capacity == _size){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strcpy(_str + _size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newcpacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcpacity);}size_t len = _size + 1;while (pos < len){_str[len] = _str[len - 1];len--;}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = len + _size;while (end > pos){_str[end] = _str[end - len];end--;}memcpy(_str + pos, str, len);_size += len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if(len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}size_t string::find(char ch, size_t pos){for (int i = pos; i < size(); i++){if (_str[i] == ch)return i;}return npos;}size_t string::find(const char* sub, size_t pos){char* p = strstr(_str + pos, sub);return  p - _str;}void string::swap(string& s){std::swap(s._str, _str); std::swap(s._size, _size); std::swap(s._capacity, _capacity); }string string::substr(size_t pos, size_t len){if (_size - pos < len){string sub(_str + pos);return sub;}else{string sub;sub.reserve(len);for (int i = pos; i < pos + len; i++){sub += _str[i];}return sub;}}bool string::operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool string::operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool string::operator!=(const string& s) const{return (*this) != s;}bool string::operator<=(const string& s) const{return (*this<s) || (*this == s);}bool string::operator>(const string& s) const{return !(*this < s);}bool string::operator>=(const string& s) const{return !(*this < s) || (*this == s);}void string::clear(){_str[0] = '\0';_size = 0;}istream& operator>> (istream& is, string& str){str.clear();char ch;ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;}ostream& operator<< (ostream& os, const string& str){for (int i = 0; i < str.size(); i++){os << str[i];}return os;}}

先介绍到这里啦~

有不对的地方请指出💞

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库的三大范式!!!初学者必看

数据库的三大范式&#xff01;&#xff01;&#xff01;初学者必看 三大范式是 Mysql数据库设计表结构所遵循的规范和指导方法目的是为了减少冗余&#xff0c;建立结构合理的数据库&#xff0c;从而提高数据存储和使用的性能。 三大范式之间是具有依赖关系的&#xff0c;比如第…

产品经理也要学个PMP证书?

随着互联网行业竞争的加剧&#xff0c;越来越多的互联网公司将产品经理视为重点培养对象。为了提升自身能力&#xff0c;许多产品经理选择考取项目管理专业认证PMP&#xff08;Project Management Professional&#xff09;。那么&#xff0c;PMP对产品经理来说是否真的有帮助呢…

Spring学习①__Spring初识

Spring Spring初识一、框架二、Spring&#xff08;春天&#xff09;简介Spring官网Spring是什么?Spring介绍拓展 Spring初识 一、框架 ​框架就是一些类和接口的集合&#xff0c;通过这些类和接口协调来完成一系列的程序实现。 JAVA框架可以分为三层&#xff1a; 表示层业务…

HTML常见标签-标题标签

标题标签 标题标签一般用于在页面上定义一些标题性的内容,如新闻标题,文章标题等,有h1到h6六级标题 代码 <body><h1>一级标题</h1><h2>二级标题</h2><h3>三级标题</h3><h4>四级标题</h4><h5>五级标题</h5>…

如何在Goland中配置一键运行项目

打开goland,点击配置,如下图 点开如下, 选择go构建 上图中有以下几点需要注意&#xff1a; 1.名称&#xff1a;为本条配置信息的名称&#xff0c;可以自定义&#xff0c;也可以使用系统默认的值&#xff1b; 2.运行种类(Run kind)&#xff1a;main包的文件名称可能为其他 设置…

ubuntu使用交叉编译链编译FFTW动态库

一、从官方下载FFTW压缩包 1. 在 /usr/local 新建目录fftw目录&#xff0c; 将压缩包放在 /usr/local/fftw 中解压。 2.打开解压后的文件 导入环境变量&#xff1a; export PATH$PATH:/home/theer/workspace/a64/cqa64_linuxqt5.8_bv3s/buildroot-2017.02.3/output/host/…

什么是访问学者?如何申请访问学者?一文秒懂访问学者

一、什么是访问学者&#xff1f; 访问学者是指具有一定学术背景、科研能力或者工作经历的人&#xff0c;前往国外大学进行专业领域的短期进修学习。访问学者需要在规定时间内完成规定进修或研究计划&#xff0c;通常为几个月至一年。进修期间&#xff0c;访问学者可参加课程、…

利用CAD绘制角度斜线的简易指南---模大狮模型网

在CAD设计中&#xff0c;绘制角度斜线是常见的需求&#xff0c;尤其在工程、建筑等领域中。正确绘制角度斜线不仅可以提高图纸的清晰度和美观度&#xff0c;还有助于准确表达设计意图。本文将介绍如何利用CAD软件进行角度斜线的绘制&#xff0c;为您提供简明易懂的操作指南。 一…

【2024】最新开源版 酒店预约小程序源码 酒店管理系统源码

源码简介&#xff1a; 随着移动互联网的快速发展&#xff0c;酒店行业也逐渐步入数字化、智能化的新时代。通过引入酒店预约小程序和酒店管理系统&#xff0c;酒店可以实现线上线下无缝对接&#xff0c;提高客户体验和服务质量。 分享一款【2024】最新酒店预约小程序源码、酒…

STM32IAP学习笔记

单片机不同的程序下载方式 ICP ICP是指在电路中编程。使用厂家配套的软件或仿真器进行程序烧录&#xff0c;目前主流的有JTAG接口和SWD接口&#xff0c;常用的烧录工具为J-Link、ST-Link等。在程序开发阶段&#xff0c;通常在连接下载器的情况下直接使用编程软件进行程序下载调…

【QuikGraph】C#调用第三方库计算有向图、无向图的连通分量

QuikGraph库 项目地址&#xff1a;https://github.com/KeRNeLith/QuikGraph 相关概念 图论、连通分量、强连通分量相关概念&#xff0c;可以从其他博客中复习&#xff1a; https://blog.csdn.net/weixin_50564032/article/details/123289611 https://zhuanlan.zhihu.com/p/3…

安装ps提示找不到msvcp140.dll,无法继续执行此代码如何修复

MSVCP140.dll&#xff0c;作为Windows操作系统中的一个关键组件&#xff0c;扮演着不可或缺的角色&#xff0c;尤其对于基于C开发的应用程序而言。本文旨在深入探讨这一动态链接库文件的功能、重要性、常见问题及解决方案&#xff0c;为您提供全面的MSVCP140.dll指南。 一、MSV…