目录
一.前言
本文将通过模拟实现c++中的string模板中一些常用的函数功能,具体包含push_back,reserve等函数的模拟,但并不考虑到所有的情况,因此并没有过多的函数重载(因为string的重载实在是太多了...)
本文共两部分,一部分用来实现string,头文件命名string.h,另一部分则为源码实现部分(编译器环境为VS2019)。各个函数的返回值类型和参数类型均与库中的string一样。
二.string.h头文件
1.class构建
string类总共有三个成员,_size表示长度,_capacity表示最大容量,还有最后一个用来表示存储的字符串_str,具体实现如下:
#pragma once
#include <assert.h>
namespace str
{
class string
{
public:
private:
size_t _size;
size_t _capacity;
char* _str;
}
};
2.构造函数和析构函数
说到自定义类,就离不开构造函数和析构函数,那么就简单的实现一下这两种函数吧。
①构造函数
//构造函数(带缺省也可为默认构造)
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
//拷贝_size+1是因为后面还要跟个'\0'
memcpy(_str, str, _size + 1);
}
//深拷贝
string(const string& str)
{
_str = new char(str._capacity + 1);
_size = str._size;
_capacity = str._capacity;
memcpy(_str, str._str, _size + 1);
}
对于常用的构造函数有两种,一种是常量字符串的初始化,另一种是同类型string的初始化,由于是同类型的string初始化,则需要进行深拷贝。
②析构函数
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
3.c_str函数(获取c字符串)
该函数简单易懂,直接返回成员字符串就行。
//c_str函数(返回C字符串)
const char* c_str() const
{
return _str;
}
4.empty函数(判断空)
很简单,直接判断大小是否为0。
//empty函数(判断空)
bool empty() const
{
return _size == 0;
}
5.push_back函数(尾插)
插入元素最简单的函数,但值得注意的是,类中要时刻注意内存开辟的大小,因此在尾插数据时,一定要检测开辟的空间是否够用。
//push_back函数(尾插)
void push_back(char ch)
{
if (_size == _capacity)
//2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size++] = ch;
_str[_size] = '\0';
}
此处使用的reserve函数即为扩容函数,具体扩容大小可根据实际情况调整。
6.reserve函数(扩容)
reserve函数的功能仅为扩容,如果调用该函数时类的大小大于扩容重定义的大小时,则不需要做任何动作,虽然可以销毁并重新构建大小来节省空间,但这样反复的拷贝将使运行的时间大大增加,因此并不建议任何情况都重构。仅需扩大容量即可。实现如下:
//reserve函数(扩容)
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_capacity = n;
_str = tmp;
}
}
7.append函数(合并)
该函数与上面的push_back实现功能差别不大差别不大,不过是在类的最后处加上目标字符串,此处则不多赘述。
//append函数(合并)
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//至少扩容至刚好
reserve(_size + len);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
8.重载‘+=’
实现与库函数string类中+=常用的有两种:+=字符、+=字符串,其实现过程很简单,字符直接调用前面的push_back函数,而字符串则调用append函数。
//重载'+='
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
9.重载‘=’
因为需要深拷贝,所有得重载=,可以将原类大小置空,再使用append函数添加到类中。
//重载'='
string& operator=(const char* ch)
{
_size = 0;
_str[0] = '\0';
append(ch);
return *this;
}
10.重载‘[]’
[]即获取对应下部储存的数据,与数组字符串获取下标无异,直接获取即可。
//获取对应下标
char& operator[](size_t pos)
{
assert(_str[pos]);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(_str[pos]);
return _str[pos];
}
11.insert函数(插入)
insert函数共有三个参数,常用插入的有字符和字符串,因此需要重载一个该函数。具体实现如下,需要的注意点都在以下代码有所标记。
①insert字符
//insert函数(插入字符)
void insert(size_t pos,size_t n,char ch)
{
//排除下标越界
assert(pos <= _size);
//检查扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
//挪动数据
//此处防止整形提升,故使用size_t类型,并使用npos加以区分
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
--end;
}
//插入数据
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = ch;
}
_size += n;
}
②insert字符串
//insert函数(插入字符串)
void insert(size_t pos, const char* str)
{
//排除下标越界
assert(pos <= _size);
//检查扩容
size_t len = strlen(str);
if (pos + len > _capacity)
{
reserve(pos + len);
}
//挪动数据
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
end--;
}
//插入数据
for (int i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
_size += len;
}
其中的npos为我们在该类命名空间中定义的静态区无符号整型,将其在string类中声音,在命名空间中定义,大小为"-1"。
12.find函数(查找)
查找函数与insert类似,常查找分为字符和字符串,另一个参数则为从哪开始,返回值为目标下标,具体实现如下:
①find字符
//find函数(查找字符)
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
size_t end = _size;
while (pos < end)
{
if (_str[pos] == ch)
return pos;
pos++;
}
return npos;
}
②find字符串
//find函数(查找字符串)
size_t find(const char* str, size_t pos = 0)
{
size_t len = strlen(str);
assert(pos + len < _size);
//使用strstr比较字符串
const char* tmp = strstr(_str + pos, str);
if (tmp)
{
return tmp - _str;
}
return npos;
}
13.erase函数(删除)
该函数功能是从目标下标删除指定数量的大小的数据,如果没有指定大小,则默认删除到保存的数据末尾,该函数最需要注意的就是对目标下标的判断和指定删除大小的判断。
//erase函数(删除)
void erase(size_t pos, size_t len = npos)
{
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
14.resize函数(重定义长度)
这个函数与reserve很相似,但不同的是,reserve的功能只有扩容;resize可以不仅可以扩容(当然,减容一样做不到),而且还能更新类中成员字符串的长度哦。
//resize函数(重定义长度)
void resize(size_t n, char s = '\0')
{
if (n >= _size)
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = s;
}
}
_size = n;
_str[n] = '\0';
}
15.substr函数(获取子串)
子字符串的获取需要目标下标和子串大小这两个参数。此处的返回便使用了构造函数的深拷贝,如果没有则会直接报错。
//substr函数(子字符串获取)
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
//特殊情况更改后一起处理
size_t n = len;
if (len == npos || len + pos > _size)
{
n = _size - pos;
}
string retun;
retun.reserve(n);
for (int i = pos; i < pos + n; ++i)
{
//此处使用+=更方便
retun += _str[i];
}
return retun;
}
16.swap函数(交换)
调用swap函数可直接调用库中的字符串swap函数辅以实现,需要注意的是_size和_capacity并不会因此交换。
//swap函数(交换)
void swap(string& str)
{
std::swap(_str, str._str);
size_t tmp = _size;
_size = str._size;
str._size = tmp;
tmp = _capacity;
_capacity = str._capacity;
str._capacity = tmp;
}
17.<<流插入与>>流提取
流插入与流提取中需要注意的有两点:第一个是两者需要定义在类外面的命名空间中,其次两者的第一个参数类型为插入流\提取流的别名,因为祖师爷设置了防拷贝的功能...实现如下:
①流插入
//流插入
ostream& operator<<(ostream& out, const str::string& str)
{
for (auto i : str)
{
out << i;
}
return out;
}
此处的for使用了迭代器,但是我们并没有自己定义,将在下一点设置。
②流提取
流提取与流插入不同,需要考虑扩容的问题,可以直接不断调用reserve函数,但运行效率实在太差,因此以下扩容为优化版本。
//流提取(定义在类外的命名空间内)
istream& operator>>(istream& in, str::string& str)
{
str.clear();
//使用get()防止无法写入' '
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
int i = 0;
char tmp[128];
while (ch != ' ' && ch != '\n')
{
tmp[i++] = ch;
if (i == 127)
{
tmp[i] = '\0';
str += tmp;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
tmp[i] = '\0';
str += tmp;
}
return in;
}
//清空数据(定义在类内)
void clear()
{
_str[0] = '\0';
_size = 0;
}
18.迭代器设置
迭代器没什么好说的,设置好初始位置和末尾位置即可,但命名必须为begin和end等与原先别名一样的,否则祖师爷的迭代器可不认识哦~
//迭代器设置
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
19.大小比较
比较大小的函数重载只需要两个就行了,其他都可以一一推出来,比较如下:
//大小比较
bool operator<(const string& str) const
{
return _size < str._size && memcmp(_str, str._str, _size) < 0;
}
bool operator==(const string& str) const
{
return _size == str._size && memcmp(_str, str._str, _size) == 0;
}
bool operator>(const string& str) const
{
return !(*this < str || *this == str);
}
bool operator>=(const string& str) const
{
return !(*this < str);
}
bool operator<=(const string& str) const
{
return !(*this > str);
}
bool operator!=(const string& str) const
{
return !(*this == str);
}
三.源码
#pragma once
#include <assert.h>
namespace str
{
class string
{
public:
//构造函数(带缺省也可为默认构造)
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
//strcpy(_str, str);
memcpy(_str, str, _size + 1);
}
//深拷贝
string(const string& str)
{
_str = new char(str._capacity + 1);
_size = str._size;
_capacity = str._capacity;
memcpy(_str, str._str, _size + 1);
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//迭代器设置
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//reserve函数(扩容)
void reserve(size_t n)
{
if (n > _capacity)
{
//new的[]是容量大小,()是初始化内容
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_capacity = n;
_str = tmp;
}
}
//push_back函数(尾插)
void push_back(char ch)
{
if (_size == _capacity)
//2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size++] = ch;
_str[_size] = '\0';
}
//append函数(合并)
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//至少扩容至刚好
reserve(_size + len);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
//重载'='
string& operator=(const char* ch)
{
_size = 0;
_str[0] = '\0';
append(ch);
return *this;
}
//重载'+='
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//insert函数(插入)
void insert(size_t pos,size_t n,char ch)
{
//排除下标越界
assert(pos <= _size);
//检查扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
//挪动数据
//此处防止整形提升,故使用size_t类型,并使用npos加以区分
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
--end;
}
//插入数据
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
//排除下标越界
assert(pos <= _size);
//检查扩容
size_t len = strlen(str);
if (pos + len > _capacity)
{
reserve(pos + len);
}
//挪动数据
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
end--;
}
//插入数据
for (int i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
_size += len;
}
//erase函数(删除)
void erase(size_t pos, size_t len = npos)
{
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
//find函数(查找字符或字符串)
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
size_t end = _size;
while (pos < end)
{
if (_str[pos] == ch)
return pos;
pos++;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
size_t len = strlen(str);
assert(pos + len < _size);
//使用strstr比较字符串
const char* tmp = strstr(_str + pos, str);
if (tmp)
{
return tmp - _str;
}
return npos;
}
//substr函数(子字符串获取)
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
//特殊情况更改后一起处理
size_t n = len;
if (len == npos || len + pos > _size)
{
n = _size - pos;
}
string retun;
retun.reserve(n);
for (int i = pos; i < pos + n; ++i)
{
//此处使用+=更方便
retun += _str[i];
}
return retun;
}
//resize函数(重定义长度)
void resize(size_t n, char s = '\0')
{
if (n >= _size)
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = s;
}
}
_size = n;
_str[n] = '\0';
}
//大小比较
bool operator<(const string& str) const
{
return _size < str._size && memcmp(_str, str._str, _size) < 0;
}
bool operator==(const string& str) const
{
return _size == str._size && memcmp(_str, str._str, _size) == 0;
}
bool operator>(const string& str) const
{
return !(*this < str || *this == str);
}
bool operator>=(const string& str) const
{
return !(*this < str);
}
bool operator<=(const string& str) const
{
return !(*this > str);
}
bool operator!=(const string& str) const
{
return !(*this == str);
}
//swap函数(交换)
void swap(string& str)
{
std::swap(_str, str._str);
size_t tmp = _size;
_size = str._size;
str._size = tmp;
tmp = _capacity;
_capacity = str._capacity;
str._capacity = tmp;
}
//empty函数(判断空)
bool empty() const
{
return _size == 0;
}
//清空数据
void clear()
{
_str[0] = '\0';
_size = 0;
}
//c_str函数(返回C字符串)
const char* c_str() const
{
return _str;
}
//获取对应下标
char& operator[](size_t pos)
{
assert(_str[pos]);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(_str[pos]);
return _str[pos];
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
const static size_t npos;
};
const size_t string::npos = -1;
};
//流插入
ostream& operator<<(ostream& out, const str::string& str)
{
for (auto i : str)
{
out << i;
}
return out;
}
//流提取
istream& operator>>(istream& in, str::string& str)
{
str.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
int i = 0;
char tmp[128];
while (ch != ' ' && ch != '\n')
{
tmp[i++] = ch;
if (i == 127)
{
tmp[i] = '\0';
str += tmp;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
tmp[i] = '\0';
str += tmp;
}
return in;
}