创建自己的命名空间以免到时候和库里的string冲突
namespace xxx
{
//string类
class string
{
//...
};
}
string类它首先得是个类,那么我们就可以用到类的实例化
正常情况下string类需要以下几个成员变量
namespace test
{
class string
{
//成员函数
public:
//成员变量
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos = -1;
};
}
前三个好理解,分别是指向字符数组首元素的指针、实际大小以及容量
那这个npos是什么意思呢?
npos是一个常数,可以用来充当某些函数的返回值
也可参考库的string类对其的解释
确定完框架以及创建好成员变量之后我们就要对类的成员函数下手了。当然我这只是简单的实现一些常用的成员函数,毕竟库里string的成员函数这么多,全部实现也太肝了
首先是构造函数
库里面有这么多
实现一下前俩个
(1)默认构造
给缺省值的原因是初始化string类的时候不能让其为空值,当然'\0'和""都行
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
(2)拷贝构造
拷贝构造的不同之处就是在于它是基于同一个类所创建的不同对象而构造的对象
所以要拷贝所传对象的字符串、容量和实际大小
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char* c_str()const
{
return _str;
}
普通的运算符。然不适用于类,所以我们要重载一下,让string类也可以用
思路和前面的拷贝构造差不多
同样是基于传过来的对象创建一个对象,也有一些区别
赋值可以是对象以及存在,只是把传过来对象拷贝到需要赋值的那个对象里去
那么就有一个前提:这个对象存在且和传过来的对象不相等(当然这个!=也要重载,后面会说)
存在且相等那还赋个毛的值啊(bushi)
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp,s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
也可以用swap搞(string的)
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
既然是数组就可以用[]去访问
所以我们也要搞一个
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
既然讲到了swap那就顺便也把swap的string版本搞一下
调库里的嘎嘎拷就完事了
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
clear是让字符串变为空字符串
改下size然后加个\0就行了
void clear()
{
_size = 0;
_str[_size] = '\0';
}
empty判空
bool empty()const
{
if (_size == 0)
{
return true;
}
return false;
}
直接return好吧
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
尾插嘛,自然是要先判断要不要扩容了(实际大小等于容量就该扩liao)
void push_back(char c)
{
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
判断完就直接插了,尾插吗扩不扩都要插的嘛
至于这个扩容,扩多少要先说嘛(参数n),你不说我很难为你办事啊
n一定要大于容量,你n小于或等于这个容量,我怎么扩,哪有越扩越小的道理
我这写的是异地扩(原地扩很麻烦搞不了一点)
开个空间拷一拷,哦对了,那个+1是留给\0的
拷完把之前的空间删删,让指针指向刚开好的空间
然后容量改为n就行了
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
push_back都来了那怎么少的了append(尾插字符串)
思路和push_back一样
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
就是实现需要注意它拷的是字符串,尤其是在长度和位置这两方面
+=它来啦(直接调append)
string& operator+=(const char* str)
{
append(str);
return *this;
}
将字符串大小调整为 n 个字符的长度
如果 n 大于当前字符串长度,则通过在末尾插入任意数量的字符来扩展当前内容,以达到 n 的大小。如果指定了 c,则新元素将初始化为 c 的副本,否则,它们是值初始化的字符。
如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除第 n 个字符以外的字符。
void resize(size_t n, char c = '\0')
{
if (n >= _size)
{
if (n > _capacity)
{
reserve(n);
}
while (_size < n)
{
_str[_size] = c;
_size++;
}
_str[_size] = '\0';
}
else
{
_size = n;
_str[_size] = '\0';
}
}
insert
在pos插入一个字符c
pos铁定要小于等于_size滴
然后就判断要不要扩容
往pos位置插就要挪一下pos+1位置到后面的字符
往pos位置放c,然后_size++
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
插字符串也差不多
只是需要注意一下长度
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
erase
要告诉我在哪(pos)删,删多少(len)
npos缺省值,如果没给默认从pos位置全删
如果给了,那要分两种情况:全删或者删一点
全删是len超出_size时或者pos+len超出_size
不然就是删一点,strcpy覆盖就完事啦
注意_size减去删掉的len长度
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
从pos位置开始找c,pos缺省值是0
for循环遍历嘛,如果i位置的字符等于c的话就返回i
找不到就返回npos
size_t find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
从pos位置开始取,取len个字符
pos要小于等于_size
要看长度来分是否要从pos位置截取字串
创建对象str
扩和字串一样大的容
将母串pos开始len个字符一点点拷到str里
返回str就行了
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
size_t end = pos + len;
if (len == npos || pos + len > _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}