昨天学习了string类的基本实现,今天学习了完整的简单版实现,特来社区记录。
目录
前言
一、增
1.reserve扩容
2.push_back()插入
3.append()插入
4.+=重载
二、删
1.erase()
三、查
四、迭代器
五、[]重载
总结
本文主要模拟实现string类的增删查改的成员函数。
由于历史遗留问题,string类实现了push_back(), append()等增加字符或者是字符串的函数,它们或多或少都有一些内容上的重叠功能实现,其中是以+=重载最为实用。
在此类函数实现之前需要判断剩下的capacity够不够放插入的字符串:
1. 插入一个字符,就判断_size+1后与_capacity的大小
2. 插入一个字符串,就判断_size+strlen(str) 后与_capacity的大小
所以需要先实现一个扩容函数来保证没有越界问题。
代码如下(示例):
// 扩容函数
void reserve(size_t n);
// s1.reserve(20)
void qyy::string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (tmp)
{
strcpy(tmp, _str);
_str = tmp;
_capacity = n;
}
}
}
代码如下(示例):
// 声明
void push_back(const char ch);
void push_back(const char* str);
// 定义
void qyy::string::push_back(const char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 指向'\0'
char* end = _str + _size;
*(end + 1) = * end;
_str[_size++] = ch;
}
void qyy::string::push_back(const char* str)
{
size_t len = strlen(str);
// 判断增容
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 指向'\0'
char* end = _str + _size;
*(end + len) = *end;
strncpy(_str+_size, str, len);
_size += len;
_str[_size] = '\0';
}
代码如下(示例):
// 定义
void append(const char ch, size_t pos);
void append(const char* str, size_t pos);
//定义
void qyy::string::append(const char ch, size_t pos)
{
assert(pos <= _size);
// 判断增容
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
char* begin = _str + pos;
char* end = _str + _size;
while (end > begin)
{
*(end + 1) = *end;
end--;
}
_str[pos] = ch;
_str[++_size] = '\0';
}
// 定义
void qyy::string::append(const char* str, size_t pos)
{
assert(pos <= _size);
size_t len = strlen(str);
// 判断增容
while (_size + len > _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
char* begin = _str + pos - 1;
char* end = _str + _size;
while (end > begin)
{
*(end + len) = *end;
end--;
}
strncpy(_str + pos, str, len);
_size += len;
_str[_size] = '\0';
}
直接复用push_back() 和 append()即可。
代码如下(示例):
// 声明
string& operator+=(const char ch);
string& operator+=(const char* str);
// 定义
string& qyy::string::operator+=(const char ch)
{
qyy::string::push_back(ch);
return *this;
}
string& qyy::string::operator+=(const char* str)
{
qyy::string::append(str, _size);
return *this;
}
删除函数分为两种情况:
1. 要删除的长度小于从要删除的位置开始剩余字符的个数,也就是够删
2. 与1相反,不够删的情况,那就是从pos开始所有的字符全部删干净
代码如下(示例):
// 删
string& erase(size_t pos, size_t len);
// 定义
string& qyy::string::erase(size_t pos, size_t len)
{
// 剩下的字符个数不够删
if (_size - pos < len)
{
_str[pos] = '\0';
_size = _capacity = 0;
return *this;
}
// 够删
while (len > 0)
{
char* begin = _str + pos;
char* end = _str + _size;
while (begin < end)
{
*begin = *(begin + 1);
begin++;
}
_size--;
len--;
}
return *this;
}
函数要实现从pos开始查找给定的字符或者字符串,返回查找到的字符或者字符串位置的下标,找不到那就返回npos。
代码如下(示例):
// 查
size_t find(const char ch, size_t pos);
size_t find(const char* str, size_t pos);
// 定义
size_t qyy::string::find(const char ch, size_t pos)
{
int i = 0;
for (i = 0; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
if (i == _size)
{
return npos;
}
}
size_t qyy::string::find(const char* str, size_t pos)
{
const char* ptr = strstr(_str, str);
if (ptr)
return ptr - _str;
else
return npos;
}
迭代器我目前的理解就是一个指针,begin指向空间的开头,end则指向空间的末尾。
代码如下(示例):
typedef char* iterator;
typedef const char* cosnt_iterator;
qyy::string::iterator begin()
{
return _str;
}
qyy::string::iterator end()
{
return _str + _size;
}
此运算符的重载使得字符串能像数组一样能够用下标支持随机访问。
代码如下(示例):
// []重载
char operator[](size_t pos);
// 定义
char qyy::string::operator[](size_t pos)
{
// 读取内容须在字符串中
assert(pos < _size);
return *(_str + pos);
}
">>" 和 "<<"的重载很微妙:
如果需要访问私有,为了方便那就不可避免地需要重载为友元函数(比如在日期类中),但是在string中,因为有[](下标)的重载可以访问到字符串的各个元素,所以只用在全局定义即可。(在全局是为了避免存在隐含的this参数使得cout与string对象的参数顺序符合常识,即cout是<<的第一个参数,string对象是<<的第二个参数)
代码如下(示例):
// 声明
std::ostream& operator<<(std::ostream& out, string& s);
std::istream& operator>>(std::istream& in, string& s);
// 定义
// cout<<
std::ostream& qyy::operator<<(std::ostream& out, string& s)
{
for (auto ch : s)
{
out << ch;
}
out << '\n';
// 不能写:
// 这样会在遇到'\0'时终止
//out << s.c_str();
return out;
}
// cin>>
std::istream& qyy::operator>>(std::istream& in, string& s)
{
// 必须使用匿名对象去调用成员函数
string().clear(s);
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
与C库的strcmp()实现相同,我自己同时也早了一个轮子。
代码如下(示例):
// 定义
bool operator<(string& s);
bool operator>(string& s);
bool operator==(string& s);
bool operator<=(string& s);
bool operator>=(string& s);
// 比较大小
bool qyy::string::operator<(string& s)
{
//char* p1 = _str;
//char* p2 = s._str;
//while (p1 != _str + _size && p2 != s._str + s._size)
//{
// if (*p1 == *p2)
// {
// p1++;
// p2++;
// }
// else if (*p1 < *p2)
// {
// return true;
// }
// else if (*p1 > *p2)
// {
// return false;
// }
//}
两个字符串不一样长
//if (p1 == _str + _size && p2 != s._str+s._size)
//{
// return true;
//}
//else
//{
// return false;
//}
// 或者直接调用库函数
return strcmp(_str, s.c_str()) < 0;
}
bool qyy::string::operator==(string& s)
{
return strcmp(_str, s.c_str()) == 0;
}
bool qyy::string::operator<=(string& s)
{
return (*this < s || *this == s);
}
bool qyy::string::operator>=(string& s)
{
return !(*this < s);
}
bool qyy::string::operator>(string& s)
{
return !(*this <= s);
}
清空与erase中全部删完的情况相同,且为头删。
代码如下(示例):
// 清空
void clear(string& s);
// 定义
void qyy::string::clear(string& s)
{
s._str[0] = '\0';
s._size = 0;
}
今天系统学习了string类成员函数的详细实现,速度并不是很快,对于数组的控制不是很到位,像书上说的一样:“在这件事上,你总是少了一位。”以后要加强学习,加大代码量练习,再接再厉!