namespace swx
{
// string需要考虑完善的增删查改
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//默认构造函数是将_str指向一个空字符串,用全缺省的默认构造函数更灵活
//String(const char* str = "\0") 错误示范,这表示字符串有0,并不是空字符串
//String(const char* str = nullptr) 错误示范,nullptr并不表示空字符串
string(const char* str = "")
: _size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//传统写法的拷贝构造和赋值运算符重载,老老实实干活,该开空间自己开空间,该拷贝数据就自己拷贝数据
string(const string& s)
:_size(strlen(s._str))
, _capacity(_size)
{
// 拷贝构造函数,对_str的初始化一步完成不了,我们在函数体中进行初始化
//这是深拷贝
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
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;
//此处返回的是引用,返回引用的好处是:1.减少拷贝 2.可以对返回对象进行修改
}
//现代写法的拷贝构造和赋值运算符重载
//一样要完成深拷贝,但是自己不想干活,安排别人干活,然后窃取劳动成果去,资本行为,剥削行为!
//先写一个swap函数
void swap(string& s)
{
std::swap(_str,s._str);
std::swap(_size,s._size);
std::swap(_capacity,s._capacity);
}
string(const string& s)
//一定要记得初始化,不初始化,换了个随机值,析构的时候会报错
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);//调用默认构造函数来完成深拷贝
//这里不要用库里的swap函数,库里的swap函数是一种深拷贝,效率太低,用String类域的swap函数,直接换底层即可
swap(tmp);
}
string& operator=(const string& s)
{
if(this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}
//更简洁的赋值运算符现代版写法
string& operator=(string s)
{
//传参的时候就进行了拷贝构造,然后直接进行交换即可
//在传参的时候已经完成了深拷贝了,所以这里判不判段是否是给自己赋值也无所谓
swap(s);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
const char* c_str() const
{
return _str;
}
/*普通对象可以调用const成员函数,因为这是权限的缩小,但是const对象不能调用普通成员函数,因为这是权限的放大,对于[]运算符我们需要些const和非const版本,因为对于普通对象而言它会调用非const的运算符重载函数获取可以修改的返回值(会调用更加匹配的),对于const对象,会调用const的运算符重载函数获取不可修改的返回值(会调用更加匹配的)。*/
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//一般const成员函数对应的返回值类型也是const的,错误实现导致const对象也可以被修改
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
/*如果使用该运算符的string是const的那么就需要使用这个const版本的运算符重载。值得注意的是,原来返回引用,是因为函
数结束后,变量还存在,那么我们直接返回它本身,可以减少拷贝,这里返回引用的确也可以减少拷贝,但主要的是为了使返回的变
量能够被修改,如果返回的不是引用,那么返回的就是拷贝的临时变量,临时变量具有常性,是不可以被修改的。*/
//这里我们还需要注意一个问题,[]与at的功能是一样的。
//s[3]与s.at(3)的功能都是一样的
//但是它们处理错误的方式不同。
//s[100]越界,那么[]会直接报错
//s.at(100)越界,at函数会抛出一个异常
/*对于size函数,我们只需要写一个const函数即可,因为const对象和非const对象都可以调用该函数。*/
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
/*size_t end = _size;
while(end >= pos)
{
_sttr[end+1] = _str[end];
--end;
这段代码是有问题的,因为end是一个无符号数,如果pos为0,当它减为-1时相当于达到了整形的最大值,此时访问就越界了*/
/* int end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
这样做也是不行的,因为end是int,pos是unsigned int,二者发生关系运算的时候会发生整形提升,那么int类型的end又提升为了 unsigned int的end,那么又会发生上面一样的问题
*/
/*int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
我们可以把pos又强转为int,这样头插就进去了,但是库里面就是unsigned int,模拟实现要和库里的参数类型一致*/
size_t end = _size+1;
//这样我等于0的时候就终止了,就没有了以前的问题
while (end > pos)
{
_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 (len == 0)
{
return *this;
}*/
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 往后挪动len个位置
size_t end = _size + len;
while (end > pos+len-1)
//while (end >= pos + len)怕极端情况pos和len都为0,但是可以通过上面的if进行一个提前判断进行解决
{
_str[end] = _str[end -len];
--end;
}
strncpy(_str + pos, str, len);//strcpy遇到0才停止
_size += len;
return *this;
}
void push_back(char ch)
{
insert(_size, ch);
}
void append(const char* str)
{
insert(_size, str);
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& earse(size_t pos, size_t len = npos)
{
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;
}
size_t find(char ch, size_t pos = 0)
{
for (; pos < _size; ++pos)
{
if (_str[pos] == ch)
{
return pos;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
const char* p = strstr(_str + pos, str);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;//返回下标
}
}
private:
char* _str;
size_t _size; // 有效字符个数
size_t _capacity; // 实际存储有效字符的空间
//const static size_t npos = -1;
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
/*out<<s.c_str()<<endl;
return out;
这段代码不可行,因为\0打印不出来,这是按C的字符串进行识别的,而C打印字符串遇到0就终止*/
for (auto ch : s)
{
out << ch;
}
return out;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator>>(istream& in, string& s)
{
//方案1
//char ch;
in >> ch;
//cin是识别不了空格或者换行的,你输入多个字符时,它会认为空格或者换行为多个字符之间的间隔,所以它永远不会识别空格或换行
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// //in >> ch;
//}
//方案2
//char ch;
//ch = in.get();
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// ch = in.get();
//}
//return in;
//方案3
//读满我一次性加到string后面,比多次调用+=效率更高。
s.clear();//先把已经存在的数据给清掉
char ch;
ch = in.get();
char buff[128] = {'\0'};
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
s += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
s += buff;//避免string频繁扩容
return in;
}
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
}