前言
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架。
STL有六大组件:算法,容器,迭代器,仿函数,空间配置器和配接器。
今天我们从string的入手,模拟实现string。
模拟实现string
我这里就不详细讲解string的具体功能是什么了,想要学习的可以通过以下网站自行学习:
https://legacy.cplusplus.com/reference/string/string/?kw=string
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace szg
{
class string
{
//流输出
friend ostream& operator<<(ostream& out, const szg::string& s);
//流输入
friend istream& operator>>(istream& in, szg::string& s);
public:
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
public:
//构造函数
string(const char* s = "")
{
_size = strlen(s);
_capcity = _size;
_str = new char[_capcity + 1];
strcpy(_str, s);
}
string(const string& s)
{
_size = s._size;
_capcity = s._capcity;
_str = new char[_capcity + 1];
strcpy(_str, s._str);
}
//析构函数
~string()
{
delete[] _str;
}
//赋值重载
string& operator=(const string& s)
{
string temp(s);
swap(temp);
return *this;
}
//容量扩展
void reserve(int n)
{
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
}
//尺寸扩展
void resize(size_t n, char c = '\0')
{
if (n > _capcity)
{
reserve(n);
_capcity = n;
}
for (size_t i = _size + 1; i <= n; ++i)
{
_str[i] = c;
}
}
size_t size()const
{
return _size;
}
const char* c_str()const
{
return _str;
}
char& operator[](size_t n)
{
assert(n >= _size);
return _str[n];
}
const char& operator[](size_t n)const
{
assert(n >= _size);
return _str[n];
}
//修改内容
void push_back(const char c)
{
if (_size == _capcity)
{
_capcity = 2 * _capcity + 1;
reserve(_capcity);
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
void append(const char* s)
{
size_t len = strlen(s);
if (_capcity < len + _size)
{
_capcity = len + _size;
reserve(_capcity);
}
strcat(_str, s);
_size += len;
}
string& operator+=(const char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
void swap(string& s)
{
size_t temp = s._size;
s._size = _size;
_size = temp;
temp = s._capcity;
s._capcity = _capcity;
_capcity = temp;
char* t = s._str;
s._str = _str;
_str = t;
}
void clear()
{
_size = 0;
_str[0] = '\0';
}
bool empty()const
{
if (_size == 0)
{
return true;
}
else
{
return false;
}
}
bool operator==(const string& s)const
{
if (strcmp(_str, s._str) == 0)
{
return true;
}
else
{
return false;
}
}
bool operator>(const string& s)const
{
if (strcmp(_str, s._str) > 0)
{
return true;
}
else
{
return false;
}
}
bool operator<(const string& s)const
{
if (strcmp(_str, s._str) < 0)
{
return true;
}
else
{
return false;
}
}
bool operator>=(const string& s)const
{
if (strcmp(_str, s._str) >= 0)
{
return true;
}
else
{
return false;
}
}
bool operator<=(const string& s)const
{
if (strcmp(_str, s._str) <= 0)
{
return true;
}
else
{
return false;
}
}
size_t find(char c, size_t pos = 0)const
{
int cur = pos;
while (cur < _size)
{
if (_str[cur] == c)
{
return cur;
}
cur++;
}
return -1;
}
size_t find(const char* s, size_t pos = 0)const
{
char* t = strstr(_str + pos, s);
if (t)
{
return t - _str;
}
return -1;
}
string& insert(size_t pos, char c)
{
assert(pos > _size);
if (_size == _capcity)
{
_capcity = _capcity == 0 ? 4 : _capcity * 2;
reserve(_capcity);
}
int cur = _size + 1;
while (cur > pos)
{
_str[cur] = _str[cur - 1];
--cur;
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, char* s)
{
assert(pos > _size);
size_t len = strlen(s);
if (_size + len < _capcity)
{
_capcity = _size + len;
reserve(_capcity);
}
int cur = _size + len + 1;
while (cur > pos + len)
{
_str[cur - 1] = _str[cur - len - 1];
cur--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
string& erase(size_t pos, size_t len)
{
if (pos + len >= _size)
{
_size = pos;
_str[pos] = '\0';
}
else
{
for (int i = pos + len; i < _size; ++i)
{
_str[i - len] = _str[i];
}
_size -= len;
_str[_size] = '\0';
}
}
private:
char* _str;
size_t _size = 0;
size_t _capcity = 0;
const static size_t npos = -1;
};
//IO流输入输出
ostream& operator<<(ostream& out, const szg::string& s)
{
out << s._str;
return out;
}
istream& operator>>(istream& in, szg::string& s)
{
s.clear();
char c;
in.get(c);
while (c != ' ' && c != '\n')
{
s.push_back(c);
in.get(c);
}
return in;
}
}
其中大部分都是数据结构的内容,但是有些知识还是需要单独讲解。
迭代器
迭代器的出现是为了不同的数据结构可以采用统一的方法去遍历数据,降低了使用成本。
string的迭代器是原生指针,但并不是所有STL结构都采用的是原生指针,还有许多使用的是类来封装迭代器。
让我们先来看看官方的迭代器是怎么使用的。
void test1()
{
string s1;
s1.push_back('a');
s1.append("98176428233");
s1 += "ddddddd";
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
for (auto e : s1)
{
cout << e;
}
cout << endl;
}
我们可以看出两点:
第一点是迭代器的用法,使用*获取数据,使用++遍历迭代器,并且这个过程可以读取也可以修改数据。
第二点:是范围for使用的结果和使用迭代器遍历的结果完全一致,范围for的本质与迭代器完全一样,只是编译器将范围for先转换为上面的样子。
void test2()
{
string s1;
s1.push_back('a');
s1.append("98176428233");
s1 += "ddddddd";
cout << s1 << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
++it;
}
cout << s1 << endl;
for (auto e : s1)
{
--e;
}
cout << s1 << endl;
}
第二个测试有有新的问题了,为什么明明范围for中的数据被改变了,但是打印出来的结果却没有改变?这是由于这个e并不是数据的引用,而是拷贝,e的改变并不会导致s1的改变,如果需要改变内容需要下列的代码:
for (auto& e : s1)
{
--e;
}
string的迭代器非常简单,使用原生指针重命名就可以了。
typedef char* iterator;
void test3()
{
szg::string s1;
s1.push_back('a');
s1.append("98176428233");
s1 += "ddddddd";
cout << s1 << endl;
szg::string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
++it;
}
cout << s1 << endl;
for (auto& e : s1)
{
--e;
}
cout << s1 << endl;
}
上面我们自己模拟实现的string与官方提供的,最后运行结果完全一样。
insert
官方插入函数:
其中npos一般来说是-1,但也不一定。
const static size_t npos = -1;
在模拟实现的string中npos是静态成员变量,而且只有整型变量可以在类中定义,其他静态变量只能在全局域定义。
string& insert(size_t pos, char c)
{
assert(pos > _size);
if (_size == _capcity)
{
_capcity = _capcity == 0 ? 4 : _capcity * 2;
reserve(_capcity);
}
int cur = _size + 1;
while (cur > pos)
{
_str[cur] = _str[cur - 1];
--cur;
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, char* s)
{
assert(pos > _size);
size_t len = strlen(s);
if (_size + len < _capcity)
{
_capcity = _size + len;
reserve(_capcity);
}
int cur = _size + len + 1;
while (cur > pos + len)
{
_str[cur - 1] = _str[cur - len - 1];
cur--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
而且两个插入函数要非常注意边界条件,特别是pos==0的情况。
有些同学一开始可能会写成下列情况:
size_t cur = _size;
while (cur >= pos)
{
_str[cur + 1] = _str[cur];
--cur;
}
size_t cur = _size;
while (cur >= pos)
{
_str[cur + len] = _str[cur];
cur--;
}
然后就死循环了。
除了本文的解决方法,还有一种方法可以解决。
int cur = _size;
while (cur >= (int)pos)
{
_str[cur + 1] = _str[cur];
--cur;
}
int cur = _size;
while (cur >= (int)pos)
{
_str[cur + len] = _str[cur];
cur--;
}
大家可以好好感悟一下。
流输入
istream& operator>>(istream& in, szg::string& s)
{
s.clear();
char c;
in.get(c);
while (c != ' ' && c != '\n')
{
s.push_back(c);
in.get(c);
}
return in;
}
流输入输出都是类的友元函数,而且输入流由于需要提取' '和'\n',需要使用in.get(c)来提取,类似于C语言中的getchar()。
并且流输入还有改进的空间:
istream& operator>>(istream& in, szg::string& s)
{
s.clear();
char buff[128] = { '\0' };
int k = 0;
in.get(buff[k]);
while (buff[k] != ' ' && buff[k] != '\n')
{
if (k == 127)
{
s += buff;
k = -1;
}
in.get(buff[++k]);
}
buff[k + 1] = '\0';
s += buff;
return in;
}
我们可以看出,改进后避免了在小容量区间频繁地插入和扩容,已空间换时间,提高了效率。