开始必须重新定义一个命名空间,把我们自定义的string放进命名空间里。
假如不考虑增删查改,我们先可以做一个简单的string,几乎下意识的就写出了这样的构造函数
string(char* str)
:_str(str)
{
}
这是非常不合理的,假如外面传入的是一个常量字符串。
string s("1234");
假如要修改常量字符串的话,会直接中断。
那我们就开一个和他同样大小的空间,再把字符拷贝进去。使用new开辟后面还可以动态增长。
namespace zjn
{
class string
{
public:
string(char* str)
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
}
private:
char* _str;
};
}
有时我们也会定义无参的string。那么这样写可以吗,我们配合一个接口c_str进行验证
string()
:_str(nullptr)
{
;
}
const char* c_str()
{
return _str;
}
int main()
{
zjn::string s1("1234");
zjn::string s2;
cout << s1.c_str() << endl;
cout << s2.c_str ()<< endl;
return 0;
}
cout是自动识别类型,是因为他重载了各种类型的函数,打印的原理无非就是遇到‘\0’停止,可是在s2由于我们构造的时候使用nullptr构造的,所以cout会解引用进行访问,就会崩溃。(析构函数时delet[]所包含的free(NULL)并不会报错)
所以正确的做法应该是
string()
:_str(new char[1])
{
_str[0] = '\0';
}
其实只要写成默认构造函数就好了。这样就不用写两个构造函数
string(char* str="")
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
在学习拷贝构造的时候,我们讲到一个stack对象的拷贝,自动生成的构造函数只是浅拷贝,两个指针指向的是同一块区域,所以执行析构函数的时候会将同一块资源释放两次。
所以默认生成的满足不了需求,必须自己写一个拷贝构造函数来实现深拷贝。
#include
#include
#pragma warning(disable:4996)
using namespace std;
namespace zjn
{
class string
{
public:
string(char* str="")
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
const char* c_str()
{
return _str;
}
string(const string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str,s._str);
}
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//释放原空间,然后深拷贝
string& operator=(const string& s)
{
if (this!=&s)
{
delete[] _str;
//不能直接strcpy因为空间可能不够
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
private:
char* _str;
};
}
int main()
{
zjn::string s1("1234");
zjn::string s2;
zjn::string s3(s1);
s3[1] = 'x';
cout << s1.c_str() << endl;
cout << s2.c_str ()<< endl;
return 0;
}
一个真正的string是要支持增删查改的,所以必须要有size和capcacity。
size和capacity是内置类型几乎不需要注意什么地方
/*string(char* str)
:_str(new char[strlen(str)+1])
,_size(strlen( str))
, _capacity(_size)
{}*/
string(char* str="")
{
_str=new char[strlen(str)+1];
_size = strlen(str);
_capacity = _size;
strcpy(_str, str);
}
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
string& operator=(const string& s)
{
if (this != &s){
delete[] str;
_str = new char[strlen(s._str) + 1];
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size=0;
}
一开始是这么写的
string(const string& s)
{
.
//局部对象拥有相同的str,用于和this._str进行交换
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
很明显是有错误的,因为_str没有初始化是一个野指针。当与temp._str进行交换时,_str指向temp._str那块空间。temp._str指向那块随机空间,当出了函数体,调用析构函数,清理资源_str,free掉temp._str,就会报错,即free了一个野指针。
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
那么我们将它初始化成nullptr就好了。
string& operator=(const string& s)
{
if (this != &s)
{
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
return *this;
}
交换了之后,出了函数体,调用析构,刚好也会清理掉不要的"worldx"那块资源。
其实还有一种更简洁的写法
//参数不能加const因为我们会修改s,不能传引用因为我们修改了s,传引用外面的也会修改
string& operator=( string s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
return *this;
}
s就代替了之前的temp对象,且s也是一个局部对象,也会调用析构函数。
可以看到存在了大量的swap代码复用,我们可以将swap写成一个函数。然后对所有代码进行修改。
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
// std中的swap并不好
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size,s._size);
std::swap(_capacity, s._capacity);
}
//拷贝构造
//string s1(s2);
string(const string& s)
:_str(nullptr)
, _capacity(0)
, _size(0)
{
string temp(s._str);
swap(temp);
}
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size=0;
}
string& operator=( string s)
{
swap(s);
return *this;
}
这就是一个有资源管理的string的现代写法。
那么为什么不直接调用库中的swap交换两个对象,而是用库中的swap一个个交换属性,最后将他们封装成一个专门的swap呢?
swap(s1,s2);
s1.swap(s2);
这两种差异是特别大的。
std中的swap是一个由函数模板推演而来的模板函数。其中经历了一次拷贝构造,两次赋值。代价太大,效率会大打折扣。
//[]重载
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//[]重载(const对象调用)
const char& operator[](size_t i)const
{
assert(i < _size);
return _str[i];
}
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
//迭代器(const对象)
typedef const char* const_iterator;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//范围for
for (auto& e : s)
{
e += 1;
}
struct ListIterator
{
operator*();
operator++();
Node* node;
};
重载他的*和++来达到目的。
void resize(size_t n, char ch='\0')
{
//_size变小,capacity不变,直接在n处放'\0'
if (n<_size)
{
_str[n] ='\0';
_size = n;
}
else
{
if (n>_capacity)
{
reserve(n);
}
//1 2 3 4
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//增加就可能会引起扩容
void reserve(int n)
{
if (n > _capacity)
{
//需要temp不然直接delete的话字符串的内容会丢失
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
//字符尾插
string& push_back(char ch)
{
if (_size == _capacity)
{
reserve(2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
//字符串拼接
void append(const char* str)
{
int len = strlen(str);
if (len + _size> _capacity)
{
reserve(len + _size);
}
//忘记strcpy,参数忘记+_size
strcpy(_str+_size, str);
_size += len;
}
//字符+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
//字符串+=
string& operator+=(const char* str)
{
append(str);
return *this;
}
//string对象+=
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
//插入字符
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
reserve(newcapacity);
}
/* 逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。
假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。
那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。
size_t end = _size;
while(end>=pos)
{
_str[end + 1] = _str[end];
end--;
}*/
//虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。
//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。
size_t end = _size + 1;
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);
int len = strlen(str);
if (_size + strlen(str) > _capacity)
{
reserve(_size+len);
}
int end = _size + len;
//画图分析
//避免类型提升
while (end >= (int)pos+len)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
//删除,和库类似,我们给一个npos缺省值
void erase(size_t pos, size_t len = npos)
{
//从pos起,删除len个字符。
assert(pos < _size);
//len为npos的时候会溢出
//if (pos+len>_size)
//
//假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//直接拷过去,然后覆盖,'\0'也会拷贝过去
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
//查找字符,缺省默认从第一个位置开始
size_t find(char ch, size_t pos = 0)
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t find(const char* sub, size_t pos = 0)
{
//在_str+pos这个字符串里找sub
const char* ret = strstr(_str + pos, sub);
if (nullptr == ret)
{
return npos;
}
else
{
return ret - _str;
}
}
//大于重载
bool operator>(const string& s1, const string& s2)
{
size_t l1=0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
if (s1[l1]>s2[l2])
{
return true;
}
else
{
return false;
}
}
}
//假如有一个长一个短
if (l1 < s1.size())
{
return true;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return false;
}
}
//==重载
bool operator==(const string& s1, const string& s2)
{
size_t l1 = 0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
return false;
}
}
//假如有一个长一个短
if (l1 < s1.size())
{
return false;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return true;
}
}
//输出
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i] ;
}
return out;
}
//输入
std::istream& operator>>(std::istream& in, string& s)
{
char ch;
while (1)
{
//这样不行,默认空格为下一个,不能123这样输,只能1 2 3
//in >> ch;
in.get(ch);
if ( ch==' '||ch=='\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}
#include
#include
#pragma warning(disable:4996)
namespace zjn
{
class string
{
public:
//传统写法
构造
//string(const char* str = "")
//{
// _size = strlen(str);
// _capacity = _size;
// _str = new char[_capacity + 1];
// strcpy(_str, str);
//}
析构
//~string()
//{
// delete[] _str;
// _str = nullptr;
// _capacity = _size = 0;
//}
拷贝构造
//string(const string& s)
//{
// _str = new char[strlen(s._str) + 1];
// strcpy(_str, s._str);
// _capacity = s._capacity;
// _size = s._size;
//}
赋值重载
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// delete[] _str;
// _str = new char[strlen(s._str) + 1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//构造
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
//std中的swap并不好
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷贝构造
//string s1(s2);
string(const string& s)
:_str(nullptr)
, _capacity(0)
, _size(0)
{
string temp(s._str);
swap(temp);
}
//析构
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
//赋值
string& operator=(string s)
{
swap(s);
return *this;
}
size_t size()const
{
return _size;
}
//重载[]
//返回值忘记引用了
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i)const
{
assert(i < _size);
return _str[i];
}
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//增
//扩容+初始化。
//当前ncapacity,改变size改变capacity。
void resize(size_t n, char ch='\0')
{
//_size变小,capacity不变,直接在n处放'\0'
if (n<_size)
{
_str[n] ='\0';
_size = n;
}
else
{
if (n>_capacity)
{
reserve(n);
}
//1 2 3 4
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//扩容
void reserve(size_t n)
{
if (n > _capacity)
{
//需要temp不然直接delete的话字符串的内容会丢失
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
//字符尾插
void push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';*/
insert(_size, ch);
}
void append(const char* str)
{
//int len = strlen(str);
//if (len + _size> _capacity)
//{
// reserve(len + _size);
//}
忘记strcpy,参数忘记+_size
//strcpy(_str+_size, str);
//_size += len;
insert(_size, str);
}
//字符+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
//字符串+=,参数忘记const
string& operator+=(const char* str)
{
append(str);
return *this;
}
//string对象+=
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
//插入字符
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
//假如capacity为0一开始开8个对象类型(char)空间
size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
reserve(newcapacity);
}
/* 逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。
假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。
那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。
size_t end = _size;
while(end>=pos)
{
_str[end + 1] = _str[end];
end--;
}*/
//虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。
//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
//插入字符串
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + strlen(str) > _capacity)
{
reserve(_size + len);
}
int end = _size + len;
//画图分析
//避免整形提升
while (end >= (int)pos + len)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
//删除,和库类似,我们给一个npos缺省值
void erase(size_t pos, size_t len = npos)
{
//从pos起,删除len个字符。
assert(pos < _size);
//len为npos的时候会溢出
//if (pos+len>_size)
//
//假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//直接拷过去,然后覆盖,'\0'也会拷贝过去
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
//查找字符,缺省默认从第一个位置开始
size_t find(char ch, size_t pos = 0)
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t find(const char* sub, size_t pos = 0)
{
//在_str+pos这个字符串里找sub
const char* ret = strstr(_str + pos, sub);
if (nullptr == ret)
{
return npos;
}
else
{
return ret - _str;
}
}
private:
char* _str;
size_t _capacity;
size_t _size;
//声明
static const size_t npos;
};
//定义初始化
const size_t string::npos = -1;
//大于重载
bool operator>(const string& s1, const string& s2)
{
size_t l1=0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
if (s1[l1]>s2[l2])
{
return true;
}
else
{
return false;
}
}
}
//假如有一个长一个短
if (l1 < s1.size())
{
return true;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return false;
}
}
//==重载
bool operator==(const string& s1, const string& s2)
{
size_t l1 = 0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
return false;
}
}
//假如有一个长一个短
if (l1 < s1.size())
{
return false;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return true;
}
}
//大于等于
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);
}
//输出
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i] ;
}
return out;
}
//输入
std::istream& operator>>(std::istream& in, string& s)
{
//防止对一个原本就有数据的对象输入
s.resize(0);
char ch;
while (1)
{
//这样不行,默认空格为下一个,不能123这样输,只能1 2 3
//in >> ch;
in.get(ch);
if ( ch==' '||ch=='\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}
}