为了跟库里面的进行区分,可以定义一个命名空间。
#pragma once
namespace Zy
{
class string
{
};
}
string 的底层定义有好几种方式。在这我给一个 _str 的指针指向它那个动态开辟的空间,再给一个 _size 和 _capacity
class string
{
public:
private:
size_t _size;
size_t _capacity;
char* _str;
};
构造
capacity 不包含 \0 它存的是有效字符的空间容量
string()
: _size(0)
, _capacity(0)
, _str(new char[1])
{
_str[0] = '\0'; //空对象里面也要有一个 \0
}
string(const char* str)
: _size(strlen(str))
, _capacity(_size)
, _str(new char[_capacity + 1])
{
strcpy(_str, str);
}
但是不能直接给一个空对象
string() : _size(0) , _capacity(0) , _str(nullptr) //不能这么初始化 {}
因为如果 _str 是空再去调用 c_str 会崩溃。第二个给的是个空指针但是类型是 char* ,它会直解引用去访问。
当然也可以用一个全缺省的构造
string(const char* str = "") //常量字符串后面默认会带个 \0
: _size(strlen(str))
, _capacity(_size)
, _str(new char[_capacity + 1])
{
strcpy(_str, str);
}
但是这边最好不用初始化列表,假设有一次这个初始化顺序不太符合我的想象,想把它的顺序换一下就出问题了,因为初始化列表的初始化顺序是按照声明顺序去初始化的。所以基于这些原因这一块更推荐不去用初始化列表。
string(const char* str = "") //常量字符串后面默认会带个 \0
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
size
size_t size()const
{
return _size;
}
operator[]
string 的 operator[] 它要返回 pos 那个位置字符的引用。同时需要检查越界,它这边要求 pos 一定小于 _size
char& operator[](size_t pos) //普通对象调它
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const //const 对象调它
{
assert(pos < _size);
return _str[pos];
}
迭代器
迭代器有可能是指针也有可能不是指针。对于 string 而言它的迭代器就是原生指针。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;
}
拷贝构造
如果自己不写拷贝构造,这边用 s1 去构造 s2 就会出现崩溃。这里崩溃的原因主要是两个对象指向了同一块空间,因为这边不写,编译器会生成默认的拷贝构造,默认的拷贝构造针对内置类型进行浅拷贝(值拷贝),就是它只会把 s1 对象(总共 12 个字节)依次拷贝下来对于 _size 和 _capacity 来说没什么问题,但是对于 _str 就有问题,他俩一起指向那块空间后析构的时候会析构两次,同一块空间不允许析构两次。
所以这边需要做深拷贝
string(const string& s)
:_str(new char[s._capacity + 1])
, _size(s._size)
,_capacity(s._capacity)
{
strcpy(_str, s._str);
}
还有种现代点的写法,用 tmp 去调之前写好的构造然后和 this 换一下,但是 this 可能是一堆随机值,把随机值换给了 tmp 之后 tmp 出了作用域调用析构函数,析构不能释放随机空间所以会崩掉。
所以基于这样的问题在换的时候不能把随机值换给它,最好初始化一下。顺便再提供一个 swap.
void swap(string& tmp)
{
::swap(_str, tmp._str); //调用全局的 swap
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
赋值
假设这边有一个 s1 和 s2 各自存了几个字符,现在我想把 s2 赋值 s1 这边它会默认的赋值结果也出现了崩溃,因为如果自己这边不写赋值默认生成的也是一个浅拷贝和拷贝构造一样。这个时候导致了两个问题,第一这个地方发生了内存泄漏,原来 s2 指向的那块空间泄露了,因为 s2 指向了新空间。第二,它两指向同一块空间会被释放两次导致崩溃。
所以这块也需要自己写。这边要分清楚,s2 = s1,那么 this 就是 s2,s 就是 s1 ,那这边要先释放 s2 的,再让它指向新空间。
string& operator=(const string& s)
{
if (this != &s) //如果是自己给自己赋值就不进去了,直接返回
{
delete _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
同样可以利用 swap
string& operator=(string s)
{
if (this != &s)
swap(s);
return *this;
}
尾增
在这之前可以顺势增加一个 reserve 函数,这个函数可以帮助我完成这边的扩容顺便还能完成一些其它功能。直接开一个新空间再把数据拷过去后把旧的释放掉再去指向新的,数据量没变所以 _size 不需要动。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
有了 reserve 后 push_back 就方便很多直接判断有没有满,满了就扩,还要注意如果 _capacity 是初始的缺省值那就可以先给个 4 个否则就二倍扩,再在 _size 这个位置插进去,++ 完 _size 之后不能忘记还要把 \0 放进去。
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
+= 也可以直接复用 push_back
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
append
append 是进一个字符串,这个字符串有可能长有可能短所以不能直接判断扩容。首先应该先算一下插入的这个字符串的长度,_size 是我现有的字符数量 len 是要插入的,它两加起来大于 _capacity 就要扩容,这边直接扩二倍有时候不一定够有可能会超过二倍,所以直接扩 _size + len 大小。
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
void append(const string& s)
{
append(s._str);
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
插入
向后挪数据
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1]; //前一个挪后一个,防止 pos 传 0 导致的死循环
--end;
}
_str[pos] = ch;
++_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);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str, str, len);
return *this;
}
有了 insert 之后 push_back 和 append 都能直接去复用它
void push_back(char ch)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity * 2);
//}
//_str[_size] = ch;
//++_size;
//_str[_size] = '\0';
insert(_size, ch);
}
void append(const char* str)
{
//size_t len = strlen(str);
//if (_size + len > _capacity)
//{
// reserve(_size + len);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
删除
erase 在 pos 位置删除 len 个字符,这个 len 有个缺省值 npos ,npos是个静态的成员变量在类里面声明类外面定义,但是如果是 const 的就可以在类里面定义。
class string
{
......
private:
size_t _size;
size_t _capacity;
char* _str;
public:
//const static C++ 语法特殊处理
//直接可以当成定义初始化
const static size_t npos = -1;
};
//size_t string::npos = -1;
如果 len 等于 npos 或者 pos + len 大于等于 _size 说明后面的都删了。
void 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;
}
}
流提取和流插入
可以直接输入输出这个 string。要实现这两个东西它不能实现成成员函数,它要实现成全局函数因为会涉及到和 this 指针抢第一个位置的问题。
要支持连续的流插入需要一个返回值,流提取不一定必须是友元,因为它不需要访问私有,可以直接遍历它的数据。
ostream& operator<<(ostream& out,const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
流插入这边可以用一个数组,如果每次来的这个字符不等于空格或者是换行就先往这个数组里面放,当 i 走到 N - 1 的时候说明就满了然后把这个数组 += 给 s ,如果输入字符不足 N 个就把 i 位置给个 \0 后再 += 给 s 。
istream& operator<<(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 64;
char str[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
str[i] = ch;
++i;
if (i == N - 1)
{
str[i] = '\0';
s += str;
i = 0;
}
ch = in.get();
}
str[i] = '\0';
s += str;
return in;
}
查找
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub); //子串匹配
if (ptr == nullptr)
return npos;
return ptr - _str;
}
substr
substr 就是区当前串的一部分构造一个对象返回
string substr(size_t pos, size_t len = npos)const
{
assert(pos < _size);
size_t realLen = len;
if (len == npos || pos + len > _size) //后面有多少取多少
{
realLen = _size - pos; //实际长度
}
string sub;
for (size_t i = 0; i < len; ++i)
{
sub += _str[pos + i];
}
return sub;
}
比较
它不是比较长度,比的是 Ascll 码。
bool operator>(const string& s)const
{
return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s)const
{
return *this > s || *this == s;
}
bool operator<=(const string& s)const
{
return !(*this > s);
}
bool operator<(const string& s)const
{
return !(*this >= s);
}
bool operator!=(const string& s)const
{
return !(*this == s);
}
resize
resize 是开空间 + 初始化
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n); //先扩容
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else
{
_str[n] = '\0';
_size = n;
}
}
vs 下它针对自身的场景做了一些优化,vs 下自带的 string 多加了 16 个字节的的 char 数组,也就是说如果小于 16 那么字符串存在这个数组中,大于 16 就存 _str 里,这样做虽然本身对象变大了但是对于一些小对象来说不需要去动态开辟空间,效率会更高一些,是一种以空间换时间的方案。
private:
//小于 16 字符串存在 _buf 数组中
//大于等于 16 存在 _str 里
//char _buf[16];
size_t _size;
size_t _capacity;
char* _str;
}
namespace Zy
{
class string
{
public:
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;
}
string(const char* str = "") //常量字符串后面默认会带个 \0
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& tmp)
{
::swap(_str, tmp._str); //调用全局的 swap
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
string& operator=(string s)
{
if (this != &s)
swap(s);
return *this;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
insert(_size, ch);
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
void append(const char* str)
{
insert(_size, str);
}
void append(const string& s)
{
append(s._str);
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1]; //前一个挪后一个,防止 pos 传 0 导致的死循环
--end;
}
_str[pos] = ch;
++_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);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str, str, len);
return *this;
}
void 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;
}
}
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub); //子串匹配
if (ptr == nullptr)
return npos;
return ptr - _str;
}
string substr(size_t pos, size_t len = npos)const
{
assert(pos < _size);
size_t realLen = len;
if (len == npos || pos + len > _size) //后面有多少取多少
{
realLen = _size - pos; //实际长度
}
string sub;
for (size_t i = 0; i < len; ++i)
{
sub += _str[pos + i];
}
return sub;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n); //先扩容
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else
{
_str[n] = '\0';
_size = n;
}
}
bool operator>(const string& s)const
{
return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s)const
{
return *this > s || *this == s;
}
bool operator<=(const string& s)const
{
return !(*this > s);
}
bool operator<(const string& s)const
{
return !(*this >= s);
}
bool operator!=(const string& s)const
{
return !(*this == s);
}
const char* c_str() const
{
return _str;
}
size_t size()const
{
return _size;
}
char& operator[](size_t pos) //普通对象调它
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const //const 对象调它
{
assert(pos < _size);
return _str[pos];
}
~string()
{
delete[] _str;
_size = 0;
_capacity = 0;
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
//const static C++ 语法特殊处理
//直接可以当成定义初始化
const static size_t npos = -1;
};
ostream& operator<<(ostream& out,const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator<<(istream& in, string& s)
{
s.clear(); //清理数据
char ch;
ch = in.get();
const size_t N = 64;
char str[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
str[i] = ch;
++i;
if (i == N - 1)
{
str[i] = '\0';
s += str;
i = 0;
}
ch = in.get();
}
str[i] = '\0';
s += str;
return in;
}
}