天接云涛连晓雾
星河欲转千帆舞
目录
契子✨
前面我们已经大致了解到 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.h
class string
{
public:
typedef char* iterator;
iterator begin();
iterator end();
//..
};
//string.cpp
string::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.第一个字符串等于第二个字符串,则返回0
4.第一个字符串小于第二个字符串,则返回小于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;
}
}
先介绍到这里啦~
有不对的地方请指出💞