天接云涛连晓雾
星河欲转千帆舞
目录
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;}}
先介绍到这里啦~
有不对的地方请指出💞