string模拟实现代码
#pragma once
#include <string>
#include <assert.h>
#include <iostream>
namespace TCH
{
//先实现一个简单的string,只考虑从资源管理和深浅拷贝问题
//暂且不考虑增栓查改的问题
class string
{
public:
friend bool operator< (const string& s1, const string& s2);
friend bool operator== (const string& s1, const string& s2);
friend bool operator<= (const string& s1, const string& s2);
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;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
string& insert(size_t pos, const char ch)
{
//assert(pos < _size);//--------其实是可以“=”,是“=”的话就是 尾插push_back;
assert(pos <= _size);//--------然后可以改造push_back使其“复用”insert()函数;
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : 2 * _capacity);
/*for (int i = _size - 1; i >= pos; i--)
{
_str[i + 1] = _str[i];
}
_str[pos] = ch;*/
//size_t end = _size;
size_t end = _size + 1;
while (end >= pos)//-------此处头插会有大问题,因为无符号整形为-1时会变成最大整数所以end不可以给为_size
//而是_size + 1;
{
_str[end] = _str[end - 1];
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;//-------应该是+len而不是+1;
while(end > pos + len -1)
//while(end - len >= pos)//怕极端场景len=0,pos = 0;
//while (end >= pos)//-----错了!这里是挪了len个 所以条件应该也变了,多画图!
{
_str[end] = _str[end - len];
end--;
}
size_t begin = 0;
while (begin < len)
{
_str[end] = str[begin];
end++;
begin++;
}
}
string& erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
return *this;
}
string(const char* str)//------→注意加上“const”
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_size] + 1;
strcpy(_str, str);//------'\0'也会被拷贝进去
}
string(const string& s)//------一定要用上“&(引用)”
:_size(strlen(s.c_str()))
,_capacity(_size)
{
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
//传统写法:老老实实干活,该开空间就开空间,该拷贝数据就自己拷贝数据
string(const string& s)//------一定要用上“&(引用)”
:_size(strlen(s.c_str()))
,_capacity(_size)
{
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
//剥削,要完成深拷贝,自己不干活,安排给别人干然后窃取劳动成果
//现代写法--------其实也是贯彻复用的原则
string(const string& s)
:_str(nullptr)//-------很有必要不然下面交换后,局部对象会析构错误空间
,_capacity(0)
,_size(0)
{
string tmp(s.c_str());//--------由于这是局部对象出了作用域就会调用析构函数,所以应该先将this的_str,_capacity,_size走初始化列表初始化了才行,不然会析构错误的空间
//std::swap(_str, tmp._str);
//std::swap(_capacity, tmp._capacity);//--------可以直接访问私有成员,因为这是类内定义的函数,类内的成员是相通的
//std::swap(_size, tmp._size);
swap(tmp);
}
//s1 = s3 ---------→s1.operator=(&s1,s3);
//传统赋值写法
string& operator= (const string& s)
{
if (this == &s) //------排除自己给自己赋值的情况
return *this;
_size = strlen(s.c_str());
_capacity = _size;
if (strlen(_str) < _size)
{
delete[] _str;
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
else
{
strcpy(_str, s.c_str());
}
return *this;
}
//现代赋值写法一:
string& operator= (const string& s)
{
string tmp(s.c_str());
swap(tmp);
return *this;
}
//现代赋值写法二:
string& operator= (string s)
{
swap(s);
return *this;
}
//s1 = s3 ---------→s1.operator=(&s1,s3);
string& operator= (const string& s)
{
if (this == &s) //------排除自己给自己赋值的情况
return *this;
_size = strlen(s.c_str());
_capacity = _size;
if (strlen(_str) < _size)
{
delete[] _str;
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
else
{
strcpy(_str, s.c_str());
}
return *this;
}
char& operator[] (size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[] (size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
void resize(size_t n, char ch)
{
if (n > _capacity)//-------分情况:大于_capacity
{
reserve(n);
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';//-------一定不要忘记给‘\0’;
_size = n;
}
else if (n > _size)//-------分情况:小于_capacity但大于_size
{
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else//------分情况:小于size
{
_size = n;
_str[n] = '\0';
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//-------一定不要忘了多开一个空间给‘\0’用
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(const char ch)
{
/*if (_size == _capacity)
{
//char* tmp = new char[_capaci ty * 2 + 1];//----每次都要多开出一个空间给‘\0’;
//strcpy(tmp, _str);//--------旧空间拷贝给新空间!
_capacity = _capacity * 2 + 1;-------❌:根本不需要+1,自己好好想想,实际空间就是能给存储的有效字符不能包括‘、0’
//_capacity *= 2;
//delete[] _str;
//_str = tmp;
reserve(_capacity == 0 ? 4 : 2 * _capacity);//-------要懂得“复用”
//有bug:当一开始为空串的时候,capacity=0,则应该先给具体的数字
//否则会一直传0
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';//---------→‘\0’不能忘记处理!*/
insert(_size, ch);//--------“复用”insert函数
}
string& operator+= (const char ch)
{
push_back(ch);//-------复用直接不需要自己重新写了
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& append(const char* str)
{
size_t new_size=strlen(str) + _size;
if (new_size > _capacity)
{
/*char* tmp = new char[new_size * 2 + 1];
strcpy(tmp, _str);
delete[] _str;
_capacity = new_size * 2; */
reserve(new_size);
}
//for()-----不需要一个一个尾插,还是用strcpy就行了
strcpy(_str + _size, str);//---------记住是 _str+_size的位置开始拷贝;
_size = new_size;
}
~string()
{
if (_str)
delete[] _str;
}
const char* c_str()const
{
return _str;
}
char& operator[] (size_t pos)
{
assert(pos < strlen(_str));
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos;
};
const size_t string:: npos = -1;//--------静态成员变量定义
bool operator< (const string& s1, const string& s2)
{
return std::strcmp(s1.c_str(), s2.c_str()) < 0;//---------不要老是忘了c_str()是函数不能少了()知道吗!
}
bool operator== (const string& s1, const string& s2)
{
return std::strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<= (const string& s1, const string& s2)
{
return (s1 < s2 || s1 == s2);//--------记得“复用”;
}
std::ostream& operator<< (std::ostream& out, const string& s)
{
for (auto ch : s)
//out << s.c_str() << std::endl;//-------流插入和流提取不一定要友元函数,要看是否访问了私有成员
//此写法不算对,因为当string里面有’\0‘的时候就会打错!
{
out << ch;//--------这样才能保证每一个都能打印出来
}
return out;
}
std::istream& operator>> (std::istream& in, string& s)
{
char ch;
char buff[128] = { '\0' };//----全部初始化为'\0';
size_t i = 0;
ch = in.get();
while (ch != ' ' && ch != '/n')
{
buff[i++] = ch;
if (i == 127)//-----说明buff有效字符已满
{
s += buff;
memset(buff, 0, sizeof(buff));
}
ch = in.get();
/*in >> ch;
s += ch;*/
}
s += buff;
return in;
}
}
string模拟实现的问题和细节
1.
string(const char* str)//------→注意加上“const”
:_size(strlen(str))
, _capacity(_size)
, _str(new char[_size])------❌:走初始化列表的话定义的顺序是按声明的顺序来的,所以会先执行_str(.....),
所以可以将_str(.....)放在{}大括号里面定义便可以避免;
private:
char* _str;------声明的顺序
size_t _size;
size_t _capacity;
2.
string s1;---------➡️库里面的无参的构造
string(const char* str = “”)-------在有参的构造函数下给一个全缺省“空串 空串默认是个’\0';-------➡️“\0”(也可以),但是‘\0’是不可以的,他会转化成Acciall码值0,是个空指针;
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_size] + 1;
strcpy(_str, str);//------'\0'也会被拷贝进去
}
3.string的迭代器(原生指针)
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string的迭代器是原生指针,不需要重载++。
4.
iterator begin()const--------❌:这样写是错误的,这样能够支持写(修改)!
因为你返回的是普通迭代器,与自己是const不相吻合
{
return _str;
}
const iterator begin()const-----✅:返回const的对象则是符合的!
{
return _str;
}
5.✳️值得搞清楚!insert()头插问题还是比较典型
size_t end = _size;--------✳️只要对此处做出变动就行
while (end >= pos)//-------此处头插会有大问题,因为无符号整形为-1时会变成最大整数(npo s)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
解决方案:我们将end规定为\0的后面,则当pos和end都指向第一个位置的时候就停止,则end就不会减到-1转为最大整形数字了
size_t end = _size + 1;--------✳️只要对此处做出变动就行
while (end >= pos)
{
_str[end] = _str[end -1 ];
end--;
}
_str[pos] = ch;
_size++;
移动多个字符
while(end > pos + len -1)
//while(end - len >= pos)//怕极端场景len=0,pos = 0;
//while (end >= pos)//-----错了!这里是挪了len个 所以条件应该也变了,多画图!
{
_str[end] = _str[end - len];
end--;
}
6.
流插入和流提取不一定要友元!只有访问了私有成员才需要友元!
std::ostream& operator<< (std::ostream& out, const string s)
{
for (auto ch : s)
//out << s.c_str() << std::endl;//-------流插入和流提取不一定要友元函数,要看是否访问了私有成员
//此写法不算对,因为当string里面有’\0‘的时候就会打错!
{
out << ch;//--------这样才能保证每一个都能打印出来
}
return out;
}
7.流提取遇到问题
cin >> ch1 >> ch2;
能够得出cin认为空格和换行是多个值的间隔,则ch永远都不能拿到空格和换行符;
while (ch != ' ' && ch != '/n')----❌:因为ch永远拿不到空格和换行符
{
in >> ch;
s += ch;
}
需要用in.get()函数,能够到缓冲区一个个都取字符
char ch;
ch = in.get();
while (ch != ' ' && ch != '/n')
{
s += ch;
ch = in.get();
}
提高效率的写法,不用频繁扩空间
ch = in.get();
while (ch != ' ' && ch != '/n')
{
buff[i++] = ch;
if (i == 127)//-----说明buff有效字符已满
{
s += buff;
memset(buff, 0, sizeof(buff));
}
ch = in.get();
}
s += buff;
8.实现getline()就是上面改造而来
ch = in.get();
while (ch != '/n')----就是吧空格条件抹掉
{
buff[i++] = ch;
if (i == 127)//-----说明buff有效字符已满
{
s += buff;
memset(buff, 0, sizeof(buff));
}
ch = in.get();
}
s += buff;
9.现代和传统写法
//传统写法:老老实实干活,该开空间就开空间,该拷贝数据就自己拷贝数据
string(const string& s)//------一定要用上“&(引用)”
:_size(strlen(s.c_str()))
,_capacity(_size)
{
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
//剥削,要完成深拷贝,自己不干活,安排给别人干然后窃取劳动成果
//现代写法--------其实也是贯彻复用的原则
string(const string& s)
:_str(nullptr)//-------很有必要不然下面交换后,局部对象会析构错误空间
,_capacity(0)
,_size(0)
{
string tmp(s.c_str());//--------由于这是局部对象出了作用域就会调用析构函数,所以应该先将this的_str,_capacity,_size走初始化列表初始化了才行,不然会析构错误的空间
//std::swap(_str, tmp._str);
//std::swap(_capacity, tmp._capacity);//--------可以直接访问私有成员,因为这是类内定义的函数,类内的成员是相通的
//std::swap(_size, tmp._size);
swap(tmp);
}
//传统赋值写法
string& operator= (const string& s)
{
if (this == &s) //------排除自己给自己赋值的情况
return *this;
_size = strlen(s.c_str());
_capacity = _size;
if (strlen(_str) < _size)
{
delete[] _str;
_str = new char[_size + 1];
strcpy(_str, s.c_str());
}
else
{
strcpy(_str, s.c_str());
}
return *this;
}
//现代赋值写法一:
string& operator= (const string& s)
{
string tmp(s.c_str());
swap(tmp);
return *this;
}
//现代赋值写法二:
string& operator= (string s)
{
swap(s);
return *this;
}