目录
前言
上一篇文章我们介绍了string类中常用接口的使用方法,但是string底层是如何实现这些接口函数的呢?下面我们将开始探讨string类的模拟实现,希望通过本篇文章能够加深您对string类的理解。
string常用接口的用法如下:
string常用接口的模拟实现
string类是支持声明和定义分离的,所以我们先创建一个string.h文件,将我们要实现的接口在里面声明以下,如下所示:
class string
{
public:
//迭代器
typedef char* iterator;
iterator begin();
iterator end();
//const迭代器
typedef const char* const_iterator;
const_iterator begin()const;
const_iterator end()const;
string(const char* str = "");
string(const string& str);
string& operator=(const string& str);
~string();
//字符串有效长度
size_t size()const;
char& operator[](size_t pos);
const char& operator[](size_t pos)const;
//获取字符串
char* c_str()const;
//预留n个大小的空间
void reserve(size_t n);
//尾插一个字符
void push_back(char ch);
//尾插一个字符串
void append(const char* str);
//字符串追加
string& operator+=(char ch);
string& operator+=(const char*str);
//字符串插入
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
//字符串的删除
void erase(size_t pos = 0, size_t len = npos);
//字符串查找
size_t find(char ch, size_t pos = 0);
size_t find(const char*str, size_t pos = 0);
//交换
void swap(string& s);
//清除
void clear();
//截取一段字符串
string substr(size_t pos, size_t len);
//字符串比较
bool operator>(const string& str)const;
bool operator>=(const string& str)const;
bool operator<(const string& str)const;
bool operator<=(const string& str)const;
bool operator==(const string& str)const;
bool operator!=(const string& str)const;
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos;
};
我们在.h文件中定义好了之后,创建一个string.cpp文件,我们将在这个文件中实现上述定义的接口,需要注意的是:在.cpp文件中要指定类域。
1.成员函数的实现
首先是string的构造函数,string支持无参的构造和带参构造,为了方便,我们可以将带参构造和无参构造写成同一个函数,我们可以传一个缺省值,代码如下所示:
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
分析上述代码,我们为_str开辟了一个新的空间,再将str的值拷贝放进_str中,其中我们多开了一个空间,这是为什么呢?实际上多开的一个空间是为\0预留的空间,因为字符串后面隐藏了一个\0作为字符串结束的标志。
回到构造函数的定义,我们发现,我们给构造函数传了一个缺省值“”,这是为什么呢?为了支持无参构造,我们默认给字符串传了一个\0,这样就支持了无参构造。
接下来是拷贝构造函数,代码如下所示:
string::string(const string& str)
{
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
分析上述代码,我们发现与构造函数类似,都是通过新开一段空间,再将要构造的内容拷贝进新的空间里。
析构函数如下所示:
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
在介绍完构造 拷贝构造以及析构函数之后,需要重点介绍的就是赋值重载,其代码如下所示:
string& string::operator=(const string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
赋值重载不同于上述,它是通过开辟一段新的空间,将内容拷贝到新的空间,释放旧空间,新空间指向旧空间,从而实现赋值。
值得注意的是,赋值重载是有返回值的,为了提高效率以及连续赋值我们通过引用接收。
2.与容量操作有关的接口的实现
首先就是size函数的实现,由于size函数是获取字符串的有效长度,所以它也是有返回值的,返回值用size_t接收,其代码如下所示:
size_t string::size()const
{
return _size;
}
接下来就是下标访问操作符的重载,下标访问操作符的访问需要支持普通成员和const成员,代码如下所示:
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];
}
接下来是字符串的访问,代码如下所示:
char* string::c_str()const
{
return _str;
}
在介绍完上述接口之后,我们如果要访问字符串,则需要配合迭代器使用,下面是迭代器的简单实现:
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
3.string对象的修改接口的实现
string的修改大致分为增、删、查、改,下面我将逐个介绍他们的模拟实现。
在实现上述接口之前,我们首先来实现reserve这个函数,有了这个函数,我们可以实现string的扩容,代码如下所示:
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 (_size == _capacity)
{
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 + _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
其逻辑基本相似,空间足够就插入,空间不够就扩容。
有了上述两个接口之后,我们可以通过复用,很轻松就可以写出operator+=函数,代码如下所示:
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
接下来就是insert在任意位置插入函数的代码
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
size_t end = _size + 1;
for (size_t i = end; i > pos; i--)
{
_str[i] = _str[i - 1];
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size + len;
for (size_t i = end; i > pos + len - 1; i--)
{
_str[i] = _str[i - 1];
}
memcpy(_str + pos, str, len);
_size += len;
}
分析上述代码,insert函数也分为插入一个字符串和一个字符两种,其中的逻辑与前面类似,都是空间足够就插入,空间不够就扩容,但是值得注意的是,我们在插入之前需要挪动数据,其逻辑就是,将前一个字符向后挪动,直到pos位置停止,再将目标字符或字符串插入pos位置。
字符串的删除,代码如下所示:
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (_size - pos <= len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
我们将字符串的删除分为两种情况,如果要删除的长度大于字符串剩余长度,则直接在pos位置补\0,如果小于剩余长度,则将要删除以后位置的字符串拷贝即可。
回到erase函数的定义里面,我们发现是给了缺省值npos的,这个npos是static修饰的,因此我们需要在类外面定义,传这个缺省值的目的就是,如果我们没有指定要删除多少,那么直接就从pos位置删到结束为止。
const size_t string::npos = -1;
截取字符串函数substr函数的实现:
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 (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return _str;
}
}
分析上述代码,由于sbstr需要返回一个新的字符串,所以我们需要创建一个新的字符串接收截取的字符串,当需要截取的长度大于目标位置之后的长度,则直接返回剩余字符串;如果小于,则需要一个一个插入新创建的字符串。
最后则是字符串查找函数,代码如下所示:
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
char* p = strstr(_str + pos, str);
return p - _str;
}
字符串查找也分为两种,其中查找字符串我们使用了strstr字符串查找函数,由于find函数的返回值是下标位置,但是strstr函数返回的是指针,为了找到下标,我们使用了指针减指针得到下标。与删除类似,我们在定义的时候也传了缺省值npos。
4.string的一些其他接口
库里面的string是支持字符串比较的,其模拟实现如下所示:
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 strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
我们只需要实现其中任意两个接口,其他复用即可
库里的string实现了字符串的交换,其代码如下所示:
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
最后则是流插入和流提取函数的实现:
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
string的模拟实现代码汇总如下:
const size_t string::npos = -1;
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
string::string(const string& str)
{
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
string& string::operator=(const string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
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];
}
char* string::c_str()const
{
return _str;
}
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 (_size == _capacity)
{
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 + _capacity);
}
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 newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
size_t end = _size + 1;
for (size_t i = end; i > pos; i--)
{
_str[i] = _str[i - 1];
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size + len;
for (size_t i = end; i > pos + len - 1; i--)
{
_str[i] = _str[i - 1];
}
memcpy(_str + pos, str, len);
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (_size - pos <= len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
char* p = strstr(_str + pos, str);
return p - _str;
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._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 (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return _str;
}
}
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 strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
以上就是string常见函数的模拟实现,感谢您能够看完,如果我的文章对于您有所帮助的话希望留下宝贵的点赞收藏加关注,您的支持就是对我创作的最大鼓励。