该篇文章承接上一篇文章string类的实现(上),希望大家可以先看看上一篇文章,链接如下:【C++】深入探索string类的实现(上)
我们来看看访问接口有哪些,如下:
这几个接口非常简单,前两个就是根据下标去取对应的字符,后面两个接口一个是取最后一个字符,一个是取第一个元素,这里我们直接给出实现了,如下:
char& operator[](size_t n)
{
assert(n >= 0 && n < _size);
return _str[n];
}
char& at(size_t n)
{
assert(n >= 0 && n < _size);
return _str[n];
}
char& front()
{
return _str[0];
}
char& back()
{
return _str[_size - 1];
}
const char& operator[](size_t n) const
{
assert(n >= 0 && n < _size);
return _str[n];
}
const char& at(size_t n) const
{
assert(n >= 0 && n < _size);
return _str[n];
}
const char& front() const
{
return _str[0];
}
const char& back() const
{
return _str[_size - 1];
}
string类和我们之后要讲的vector的迭代器都非常好实现,因为它们两个容器的本质都是顺序表,底层都是数组,可以直接通过指针访问数据,所以string类的迭代器其实就是指针,只是被typedef了,而list等容器的迭代器则是一个类,我们以后会讲到,string类的迭代器如下:
typedef char* iterator;
typedef const char* const_iterator;
接下来我们来实现普通迭代器和const迭代器的begin和end,begin就是第一个元素的地址,end就是最后一个元素的下一个空间的地址,如下:
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//const迭代器
const_iterator begin() const
{
return _str;
}
//const迭代器
const_iterator end() const
{
return _str + _size;
}
接下来我们使用范围for来测试一下迭代器,因为范围for的底层其实就是迭代器,如下:
可以看到范围for可以跑通,说明我们的迭代器基本没问题,如果还有疑问可以自己尝试使用迭代器的方法测试
我们来看看这些接口有哪些:
可以看到其中有些接口是比较简单的,比如size、capacity、empty、clear等等,这些我们在顺序表那里就学过了,只是其中这个clear没有学过,但是也非常简单,清空数据可以直接把size置为0,然后补上\0就可以了,上面这些函数的代码我们就直接写出来了,如下:
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return _size == 0;
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
这里我们只需要实现resize接口就可以了,因为扩容接口reserve我们已经实现过了,我们来看看它的所有接口:
这两个接口如果n比size大都会填充字符,只是第一个字符填充\0,而第二个接口需要指定填充的字符,所以我们其实可以把这两个接口写成一个接口,其中字符c的默认值为\0就可以了,接下来我们来分析如何实现resize
resize有三种情况我们就用if将这三种情况进行划分,如果n小于或等于原本的size,那么我们就直接将size改成n就行了,进行对应的缩容,但是不要忘了把\0添加到最后,如果n大于size但是小于等于当前的容量,那么我们就把中间的空间都填充成对应的字符,如果n大于当前容量就进行扩容,并且从size到n都填充对应的字符,如下:
void string::resize(size_t n, char c)
{
assert(n >= 0);
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else if (n <= _capacity)
{
//走到这里n一定大于size,但是小于等于capacity
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
else
{
//n大于capacity,扩容
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[_size] = '\0';
}
}
上面就是resize的实现,但是我们发现有两个地方代码有一些冗余,一个地方是三个判断中我们都要将size位置的数据改为\0,我们可以把它拿出来减少冗余
第二个地方就是第二个和第三个判断中挪动数据的逻辑一致,只是有一个没有用reserve,但是我们想想我们之前实现的reserve是不是在扩容前会进行一次判断,如果参数小于capacity就不会扩容,所以第二个判断中n大于size但是小于capacity,就算使用reserve也不会发生扩容,因为在reserve函数中作了判断,所以我们可以将第二个判断和第三个判断合并,优化后如下:
void string::resize(size_t n, char c)
{
assert(n >= 0);
if (n <= _size)
{
_size = n;
}
else
{
//如果n小于capacity不会发生扩容
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
}
//都要执行这个逻辑就拿出来
_str[_size] = '\0';
}
我们来看看有哪些函数:
首先第一个c_str最简单,直接返回string对象底层的数组即可,如下:
const char* c_str() const
{
return _str;
}
接下来我们来实现两个查找方法,首先是正向的find,我们来看看具体有哪些接口:
这里我们实现两个接口,一个是查找一个字符,一个是查找string类对象,首先我们来看查找一个字符,只需要我们从pos位置开始遍历容器即可,如果查找到就返回下标,否则返回npos,如下:
size_t string::find(char c, size_t pos)
{
//如果pos过大进入不了循环
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
接下来我们就来实现查找一个string对象,它的本质其实是查找这个string对象中的字符串,我们可以通过函数strstr实现,具体方法就是在遍历过程中看第一个字符是否相同,如果相同那么就通过strstr函数进行匹配,成功了就返回,失败继续查找,到最后都没有找到就返回npos,如下:
size_t string::find(const string& str, size_t pos)
{
//如果pos过大进入不了循环
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == str[i])
{
char* ret = strstr(_str, str._str);
if (ret)
{
return i;
}
}
}
return npos;
}
rfind的接口和find的接口也差不多,如下:
rfind我们也实现两个接口,一个是查找字符,一个是查找string对象,其实rfind和find的实现基本上没什么不同,只是rfind从后往前找而已,如下:
size_t string::rfind(char c, size_t pos)
{
if (pos > _size)
pos = _size - 1;
for (size_t i = pos; i >= 0; i--)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::rfind(const string& str, size_t pos)
{
if (pos > _size)
pos = _size - 1;
for (size_t i = pos; i >= 0; i--)
{
if (_str[i] == str[0])
{
char* ret = strstr(_str, str._str);
if (ret)
{
return i;
}
}
}
return npos;
}
substr的接口形式只有一个,就是从pos位置开始,向后截取长度为len的字符串,最后以string的形式返回,如下:
这里我们要注意的是在截取字符串的时候是否越界,比如当len > _size + pos,这个时候就会发生越界,因为_size - pos表示当前字符串还剩多少个字符可以被截取,如果len大于了这个数量就会照成越界,此时只需让len = _size + pos即可
解决了越界的问题我们就开始截取字符串,方法也很简单,我们用循环的方式把对应的字符插入一个新的string类对象中即可,如下:
string string::substr(size_t pos, size_t len)
{
//pos不会小于0,因为类型为size_t
assert(pos < _size);
//_size - pos表示剩余的字符数,len不能大于这个数
if (len > _size - pos)
len = _size - pos;
string str;
str.reserve(len);
for (size_t i = pos; i < pos + len; i++)
{
str += _str[i];
}
return str;
}
我们来看看compare函数的接口,如下:
compare的底层就是封装了strcmp,我们就实现图上的第一个接口就行了,如下:
int string::compare(const string& str) const
{
return strcmp(_str, str._str);
}
关系运算就是等于、大于、小于等运算,接下来我们来实现一下这些关系运算,只需要利用之前写的compare函数即可,如下:
bool operator==(const string& str1, const string& str2)
{
return str1.compare(str2) == 0;
}
bool operator!=(const string& str1, const string& str2)
{
return !(str1 == str2);
}
bool operator<(const string& str1, const string& str2)
{
return str1.compare(str2) < 0;
}
bool operator<=(const string& str1, const string& str2)
{
return str1 < str2 || str1 == str2;
}
bool operator>(const string& str1, const string& str2)
{
return !(str1 <= str2);
}
bool operator>=(const string& str1, const string& str2)
{
return str1 > str2 || str1 == str2;
}
流提取运算符的重载是为了方便我们输出string类对象,方法也很简单,就是把string对象的字符一个一个输出即可,如下:
ostream& operator<<(ostream& out, const string& str)
{
for (int i = 0; i < str.size(); i++)
{
cout << str[i];
}
return out;
}
流插入运算符重载是为了方便我们给string对象手动进行输入,写这个函数要注意的地方还是挺多的,第一个注意的点是我们去读取字符的时候不能直接用cin,因为cin不会读取空格和\n,要想读取所有类型的字符就要用istream对象的成员函数get,等下我们会演示
第二个点是我们在进行插入之前要把以前的数据清空,否则就达不到我们的预期,其实还有第三个点,但是我们可以后面说,我们先按照上面的大致思路写出代码,如下:
istream& operator>>(istream& in, string& str)
{
//清空之前的数据
str.clear();
char c = in.get();
while (c != ' ' && c != '\n')
{
str += c;
c = in.get();
}
return in;
}
上面的代码其实已经可以运行了,但是其实还是有一些缺点,就是效率可能不是很高,因为如果我们输入的字符串很长,那么就会反复进行扩容,所以为了提高效率我们可以设计一个缓冲区,先将数据放入这个缓冲区,等缓冲区满了再一次性写入对象,如下:
istream& operator>>(istream& in, string& str)
{
str.clear();
char c = in.get();
int i = 0;
char buff[256];
while (c != ' ' && c != '\n')
{
buff[i++] = c;
c = in.get();
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
getline和流插入运算符重载其实很像,只是它们循环的判断条件不同,getline是根据分隔符delim来判断循环是否继续,delim的默认值是\n,如下:
istream& getline(istream& in, string& str, char delim)
{
str.clear();
char c = in.get();
int i = 0;
char buff[256];
while (c != delim)
{
buff[i++] = c;
c = in.get();
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
#pragma once
#include <iostream>
#include <cstring>
#include <cassert>
using namespace std;
namespace TL
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//构造
string(const char* s = "");
string(size_t n, char c);
//现代写法需要用到swap
void swap(string& str);
//拷贝构造与赋值重载
string(const string& str);
string& operator=(const string& str);
//析构
~string();
void reserve(size_t n);
void push_back(const char c);
void pop_back();
void append(const string& str);
void append(size_t n, char c);
void insert(size_t pos, const string& str);
void insert(size_t pos, size_t n, char c);
void erase(size_t pos = 0, size_t len = npos);
string& operator+=(const string& str);
string& operator+=(char c);
void resize(size_t n, char c = '\0');
size_t find(char c, size_t pos = 0);
size_t find(const string& str, size_t pos = 0);
size_t rfind(char c, size_t pos = npos);
size_t rfind(const string& str, size_t pos = npos);
string substr(size_t pos = 0, size_t len = npos);
int compare(const string& str) const;
char& operator[](size_t n)
{
assert(n < _size);
return _str[n];
}
char& at(size_t n)
{
assert(n < _size);
return _str[n];
}
char& front()
{
return _str[0];
}
char& back()
{
return _str[_size - 1];
}
const char& operator[](size_t n) const
{
assert(n < _size);
return _str[n];
}
const char& at(size_t n) const
{
assert(n < _size);
return _str[n];
}
const char& front() const
{
return _str[0];
}
const char& back() const
{
return _str[_size - 1];
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//const迭代器
const_iterator begin() const
{
return _str;
}
//const迭代器
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return _size == 0;
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
const char* c_str() const
{
return _str;
}
const static size_t npos;
private:
char* _str;
size_t _size;
size_t _capacity;
};
string operator+(char c, const string& str);
string operator+(const char* s, const string& str);
string operator+(const string& str, char c);
string operator+(const string& str, const char* s);
void swap(string& s1, string& s2);
istream& getline(istream& in, string& str, char delim = '\n');
ostream& operator<<(ostream& out, const string& str);
istream& operator>>(istream& in, string& str);
bool operator==(const string& str1, const string& str2);
bool operator!=(const string& str1, const string& str2);
bool operator<(const string& str1, const string& str2);
bool operator<=(const string& str1, const string& str2);
bool operator>(const string& str1, const string& str2);
bool operator>=(const string& str1, const string& str2);
}
#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
namespace TL
{
const size_t string::npos = -1;
string::string(const char* s)
:_size(strlen(s))
{
_capacity = _size;
//注意要多开一个空间存放\0
_str = new char[_capacity + 1];
//strcpy会拷贝\0
strcpy(_str, s);
}
string::string(size_t n, char c)
:_size(n)
{
_capacity = _size;
//注意要多开一个空间存放\0
_str = new char[_capacity + 1];
for (size_t i = 0; i < _size; i++)
{
_str[i] = c;
}
_str[_size] = '\0';
}
void string::swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
string::string(const string& str)
{
//以前的写法
/*if (_str)
delete[] _str;
_str = new char[str._capacity + 1];
_size = str._size;
_capacity = str._capacity;
strcpy(_str, str._str);*/
//现代写法(利用构造)
string tmp(str._str);
swap(tmp);
}
string& string::operator=(const string& str)
{
//注意判断,不要自己给自己赋值
if (this != &str)
{
//普通写法
/*if (_str)
delete[] _str;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;*/
//利用拷贝构造,也可以利用构造
string tmp(str);
swap(tmp);
}
return *this;
}
string::~string()
{
if (_str)
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
void string::reserve(size_t n)
{
//如果参数小于等于容量就不予处理
if (n <= _capacity)
{
return;
}
//2倍扩容
size_t newCapacity = _capacity * 2;
//如果2倍扩容不够就直接扩容到n
if (newCapacity < n)
newCapacity = n;
//开新空间,注意还是要多开一个空间以后放\0用
char* tmp = new char[newCapacity + 1];
//数据的拷贝
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = newCapacity;
}
void string::push_back(char c)
{
//注意查看空间是否满了,如果满了就扩容
if (_size == _capacity)
{
reserve(2 * _capacity);
}
_str[_size++] = c;
_str[_size] = '\0';
}
void string::pop_back()
{
//确保数组不为空
assert(_size > 0);
//size--相当于就把它删除了
_size--;
//补上\0
_str[_size] = '\0';
}
void string::append(const string& str)
{
size_t len = strlen(str._str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
//将str的字符串拷贝到当前对象
//注意\0会跟着一起拷贝,不需要专门给
strcpy(_str + _size, str._str);
_size += len;
}
void string::append(size_t n, char c)
{
if (n + _size > _capacity)
{
reserve(n + _size);
}
for (size_t i = _size; i < n + _size; i++)
{
_str[i] = c;
}
_size += n;
_str[_size] = '\0';
}
void string::insert(size_t pos, const string& str)
{
assert(pos <= _size);
size_t len = strlen(str._str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t src = _size - 1;
size_t dest = src + len;
//这里如果不强转为int就会导致死循环和越界访问
//因为src类型是size_t,pos为0再--会变成非常大的数
while ((int)src >= (int)pos)
{
_str[dest--] = _str[src--];
}
//下面这句strcpy也能实现上面的拷贝功能
//strcpy(_str + pos + len, _str + pos);
/*dest = pos;
for (size_t i = 0; i < str._size; i++)
{
_str[dest++] = str._str[i];
}*/
//不拷贝\0
strncpy(_str + pos, str._str, strlen(str._str));
_size += len;
_str[_size] = '\0';
}
void string::insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
reserve(_size + n);
}
size_t src = _size - 1;
size_t dest = src + n;
while ((int)src >= (int)pos)
{
_str[dest--] = _str[src--];
}
//循环填充字符
for (size_t i = pos; i < pos + n; i++)
{
_str[i] = c;
}
_size += n;
_str[_size] = '\0';
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos)
len = _size - pos;
size_t src = pos + len;
size_t dest = pos;
/*for (size_t i = pos + len; i < _size; i++)
{
_str[dest++] = _str[src++];
}*/
while (src < _size)
{
_str[dest++] = _str[src++];
}
//下面这句strcpy也可以实现数据的覆盖拷贝,达到删除的目的
//strcpy(_str + pos, _str + pos + len);
_size -= len;
_str[_size] = '\0';
}
//void string::resize(size_t n, char c)
//{
// assert(n >= 0);
// if (n <= _size)
// {
// _size = n;
// _str[_size] = '\0';
// }
// else if (n <= _capacity)
// {
// //走到这里n一定大于size,但是小于等于capacity
// for (size_t i = _size; i < n; i++)
// {
// _str[i] = c;
// }
// _size = n;
// _str[_size] = '\0';
// }
// else
// {
// //n大于capacity,扩容
// reserve(n);
// for (size_t i = _size; i < n; i++)
// {
// _str[i] = c;
// }
// _str[_size] = '\0';
// }
//}
void string::resize(size_t n, char c)
{
if (n <= _size)
{
_size = n;
}
else
{
//如果n小于capacity不会发生扩容
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
}
//都要执行这个逻辑就拿出来
_str[_size] = '\0';
}
string& string::operator+=(const string& str)
{
append(str);
return *this;
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
//不是成员函数
string operator+(char c, const string& str)
{
//之前实现的n个c字符的构造
string tmp(1, c);
//直接复用+=
return tmp += str;
}
string operator+(const char* s, const string& str)
{
//字符串的构造
string tmp(s);
//复用+=
return tmp += str;
}
string operator+(const string& str, char c)
{
string tmp(str);
//利用匿名对象
return tmp += string(1, c);
}
string operator+(const string& str, const char* s)
{
string tmp(str);
//利用隐式类型转换,将s转换为string然后+=
return tmp += s;
}
void swap(string& s1, string& s2)
{
s1.swap(s2);
}
size_t string::find(char c, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::find(const string& str, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == str[i])
{
char* ret = strstr(_str, str._str);
if (ret)
{
return i;
}
}
}
return npos;
}
size_t string::rfind(char c, size_t pos)
{
if (pos > _size)
pos = _size - 1;
for (size_t i = pos; i >= 0; i--)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::rfind(const string& str, size_t pos)
{
if (pos > _size)
pos = _size - 1;
for (size_t i = pos; i >= 0; i--)
{
if (_str[i] == str[0])
{
char* ret = strstr(_str, str._str);
if (ret)
{
return i;
}
}
}
return npos;
}
string string::substr(size_t pos, size_t len)
{
//pos不会小于0,因为类型为size_t
assert(pos < _size);
//_size - pos表示剩余的字符数,len不能大于这个数
if (len > _size - pos)
len = _size - pos;
string str;
str.reserve(len);
for (size_t i = pos; i < pos + len; i++)
{
str += _str[i];
}
return str;
}
int string::compare(const string& str) const
{
return strcmp(_str, str._str);
}
istream& getline(istream& in, string& str, char delim)
{
str.clear();
char c = in.get();
int i = 0;
char buff[256];
while (c != delim)
{
buff[i++] = c;
c = in.get();
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
ostream& operator<<(ostream& out, const string& str)
{
for (int i = 0; i < str.size(); i++)
{
cout << str[i];
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear();
char c = in.get();
int i = 0;
char buff[256];
while (c != ' ' && c != '\n')
{
buff[i++] = c;
c = in.get();
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
bool operator==(const string& str1, const string& str2)
{
return str1.compare(str2) == 0;
}
bool operator!=(const string& str1, const string& str2)
{
return !(str1 == str2);
}
bool operator<(const string& str1, const string& str2)
{
return str1.compare(str2) < 0;
}
bool operator<=(const string& str1, const string& str2)
{
return str1 < str2 || str1 == str2;
}
bool operator>(const string& str1, const string& str2)
{
return !(str1 <= str2);
}
bool operator>=(const string& str1, const string& str2)
{
return str1 > str2 || str1 == str2;
}
}
那么到这里string类的实现我们就讲完了,是不是对string有了新的理解呢?下一篇文章我们就开始学习vector了,敬请期待吧! bye~