namespace llh
{
class string
{
private:
size_t size;//string类的大小
size_t capacity;//string类的存储容量
char* _str;
};
}
我们定义
string类
的成员变量有size
、capacity
、_str
,配合我们实现插入、删除、扩容等修改操作函数,并且把我们自己实现的string类
成员函数封装在命名空间里面,避免和标准库里面的string类
类型、函数冲突。
//默认构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
我们在实现默认构造函数时,①使用
""
空字符串作为缺省参数,常量字符串末尾有'\0'
,是跟库里面string类
默认构造函数无参调用以及string类其他变量初始化配合,_str = new char[_capacity + 1];
中capacity+1
是不传参调用时存储'\0'
;②构造函数使用初始化列表进行初始化,初始化列表中成员的初始化顺序与成员变量声明顺序应保持一致。
//拷贝构造函数
//拷贝构造函数
string(const string& s)
{
if (this != &s)
{
_str = new char[s._capacity + 1];
_size = s._size;
_capacity = s._capacity;
//strcpy(_str, s._str);
//拷贝构造的string类中包含'\0',且'\0'后还有字符
//strcpy只能拷贝到'\0'位置,剩余字符不能拷贝
memcpy(_str, s._str, _size + 1);
}
}
使用string类对象s进行拷贝构造时(1)如果进行浅拷贝(即值拷贝),编译器只是将对象中的值拷贝过来,这时候两个string类对象会使用同一块空间,此时发生的危害:①相互影响。一个string类对象修改空间的数据会影响另一个string类对象的数据内容②同一块空间会被析构两次,程序崩溃;(2)所以string类要进行深拷贝,
_str = new char[s._capacity + 1];
开辟相同的空间,memcpy(_str, s._str, _size + 1);把数据拷贝到新开辟得到空间中,内置类型变量_size
、_capacity
直接进行赋值,这时候两个对象使用不同的空间,其他数据相同。
//析构函数
~string()
{
_size = _capacity = 0;
delete[] _str;
_str = nullptr;
}
使用delete操作符释放string类对象指向的空间,
_size = _capacity = 0
使用string类大小和存储容量清除为0。
//string类返回字符串指针的函数
const char* c_str()const
{
return _str;
}
_str
为私有成员变量,需要通过函数调用才能进行访问。
size_t size()const
{
return _size;
}
_size
为私有成员变量,需要通过函数调用才能进行访问。
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
普通string类对象使用
operator[]
运算符重载,获取string类pos
位置的字符,可以进行string类修改;const string类
对象调用const修饰operator[]
运算符,只能读取string类pos`位置的字符,不可以进行string类修改。
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类
begin()
迭代器为指针,指向string类第一个字符,end()
迭代器为指针,指向string类
最后一个有效字符的下一个位置②const
类型的string
类对象对数据内容进行访问,需要使用const
类型的迭代器③迭代器封装在类域里面,使用迭代器需要在前面hhl::string::const
对string类的数据才能进行访问。
void reserve(size_t n = 0)
{
//只允许进行扩容
if (n > _capacity)
{
cout << "reserve->" << _capacity << endl;
char* tmp = new char[n + 1];
_capacity = n;
//strcpy(tmp, _str);
//拷贝构造的string类中包含'\0',且'\0'后还有字符
//strcpy只能拷贝到'\0'位置,剩余字符不能拷贝
memcpy(tmp, _str, n + 1);
delete[] _str;
_str = tmp;
}
}
实现
reserve
函数的过程:①对n
进行判断,只进行扩容不进行缩容②开辟n+1
大小的空间,多出的一个位置存储'\0'
③把原有的数据拷贝到新的空间,释放掉原来的空间,让_str
指向新的空间。
void push_back(char c)
{
if (_size == _capacity)
{
//二倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
①在插入字符之前,判断
string类
字符个数_size
是否和存储容量相等,如果相等需要扩容②在扩容的时候,无参构造对象有效字符数为0
,需要指定开辟的空间大小,其余情况进行原有容量的二倍扩容③插入字符后,++_size;
并且_str[_size] = '\0';
在最后添加'\0'
进行限制。
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
①判断插入
str
字符串是否需要扩容,这次我们进行按需扩容②使用strcpy
函数将字符串str
拷贝string
类对象末尾,strcpy
函数会将str字符串中的'\0'
一起拷贝过去,不需要单独进行限制,最后更新string
类数据个数。
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
实现
operator+=
函数的原理push_back
函数、append
函数的原理是一样的,我们在这里直接进行对push_back
函数、append
函数进行复用。
void insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
//扩容到_size+n
reserve(_size + n);
}
size_t end = _size;
//挪动数据
//当插入0位置时,end在结束时为-1,
//由于end为无符号整型,会整型提升为整数的最大值,会发生越界访问的错误,程序崩溃
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
//插入n个字符c
for (int i = 0; i < n; i++)
{
_str[i + pos] = c;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容到_size+len
reserve(_size + len);
}
size_t end = _size;
//挪动数据
//当插入0位置时,end在结束时为-1,
//由于end为无符号整型,会整型提升为整数的最大值,会发生越界访问的错误,程序崩溃
while (end >= pos&&end!=npos)
{
_str[end + len] = _str[end];
end--;
}
//插入字符串str
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
增加的成员变量:
//在string里面定义静态共有成员变量
public:
static size_t npos;
//在类外面进行初始化
size_t string::npos = -1;
①判断插入字符数据是否需要扩容,以及pos位置是否合法②在挪动数据的时候,
pos
位置为0时,end
在结束的时候为-1
,因为end
为size_t
类型会整型提升为整型最大值,程序因越界访问直接崩溃。解决办法:①设置end
变量类型为int
类型,while循环判断框里的pos
强转为0
(库里面pos
变量为无符号整型,同标准库里面的保持一致)③设置一个公有静态成员无符号整型变量npos
,并且初始化为-1
,附加一个判断条件end!=npos
③进行字符数据的插入,_size += len;
更新string对象
数据个数。
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//要删除的长度很长,或字符串很短
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end<=_size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
判断
pos
位置是否合法,如果pos
位置之后要删除的数据个数很大,直接在pos
位置加'\0'
进行限制;或者正常进行挪动数据覆盖进行删除(把‘'\0'
一起挪动)。
size_t find(char c, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t find( const char* s,size_t pos = 0)
{
assert(pos < _size);
//pos位置开始
const char* ptr = strstr(_str+pos, s);
if (ptr)
{
//指针减指针为之间的字符个数,也为子串的起始位置
return ptr-_str;
}
else
{
return npos;
}
}
查找字符串使用
strstr
子串查找函数进行查找,若查找成功,返回字符串第一次出现的位置,ptr-_str
得到string
类的下标位置,若查找失败返回空指针。
string substr(size_t pos = 0, size_t len=npos)
{
assert(pos < _size);
string tmp;
//判断拷贝长度
size_t n = len;
//if (pos + len > _capacity)
if (len==npos||pos + len > _size)
{
n = _size - pos;
}
//把子串的数据拷贝string类tmp
for (size_t i = 0; i < n; i++)
{
tmp += _str[pos + i];
}
return tmp;
}
①判断需要拷贝的
string
类字符串长度,如果过长,从pos
位置拷贝到string
类末尾②把子串的数据拷贝string类tmp
,并且返回tmp
void resize(size_t n, char ch = '\0')
{
if (n < _capacity)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i <n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
判断是减少
string
类数据个数,还是增加数据个数。①若是减少string
类数据个数,直接在对应的位置添加'\0'
(不用进行缩容,考虑到后面还需要插入数据,反复进行扩容缩容,代价太大),_size = n;
更新数据个数②若是增加string
类数据个数,先进行扩容(若是存储容量足够,不需要扩容,否则就进行扩容,已在reserve
函数中实现),插入字符c
,_size = n;_str_size] = '\0';
进行字符串限制和数据更新。
ostream& operator<<(ostream& out, string& s)
{
for (int i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
①把
operator<<
函数实现成非成员函数,是为了保证第一个参数ostream& out
②在实现该函数时,没有直接out << s.c_str() << endl;
输出,string
类不看'\0'
而终止,而是以_size
数据个数为终止输出。
//定义为类成员函数
void clear()
{
_str[0] = '\0';
_size = 0;
}
//定义为非成员函数
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' ||ch == '\n')
{
ch = in.get();
}
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
//使用一个128字节大小的数组,把读取到的字符存放数组
//如果buff数组到127字符,一次性加载到string类对象
//此时还有字符没有读取完,继续存放在buff数组,直到读取完毕
//最后再把字符串加载到string类末尾
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i != 0)
{
//当读取到buff数组的字符时,大于0小于127时
//进行最后一次追加拷贝
buff[i] = '\0';//进行字符串拷贝限制
s += buff;
}
}
①对
string类
对象输入的情况,需要使用clear
函数清除原有string类
数据,重新对string类对象输入②istream
流不能读取空格和'\n'
,需要使用istream
流中的get
函数③考虑到拷贝数据个数太多,一个一个进行对string
类拷贝需要,需要进行多次扩容;而不使用reserve
函数进行开辟足够的空间,若空间开小了,达不到减少扩容的效果,若空间开大了,提取数据个数较小时,会造成空间浪费,所以选择128字节大小buff
字符数组存储,进行适时拷贝。
传统写法实现:
//传统写法
string& operator=(const string& s)
{
if (this != &s)
{
_str = new char[s._capacity + 1];
_size = s._size;
_capacity = s._capacity;
memcpy(_str, s._str, _size + 1);
}
return *this;
}
现代写法版本一:
//现代写法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
现代写法版本二:
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
使用拷贝构造函数构造一个临时对象
tmp
,然后*this
和tmp
进行私有变量值交换,因为tmp
是临时对象,在函数销毁时会自动调用析构函数,对tmp
现在(this
原来指向)的空间进行销毁!
//版本一
bool operator<(const string& s)
{
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1++] < s._str[i2++])
{
return true;
}
else
{
return false;
}
}
return _size < s._size;
}
//版本二简洁写法
bool operator<(const string& s)const
{
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
版本一和版本二原理一样,先比较数据个数较小的string类的部分字符串,若是比较的数据内容相同,则判断数据个数大小,返回真值。
bool operator==(const string& s)const
{
return _size == s._size &&
memcpy(_str, s._str, _size);
}
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);
}
#pragma once
#include
#include
#include
#include
using namespace std;
namespace hhl
{
class string
{
//friend ostream& operator<<(ostream& out, string& s);
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 = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
//memcpy(_str, str, _size + 1);
}
//拷贝构造函数
string(const string& s)
{
if (this != &s)
{
_str = new char[s._capacity + 1];
_size = s._size;
_capacity = s._capacity;
//strcpy(_str, s._str);
//拷贝构造的string类中包含'\0',且'\0'后还有字符
//strcpy只能拷贝到'\0'位置,剩余字符不能拷贝
memcpy(_str, s._str, _size + 1);
}
}
//传统写法
string& operator=(const string& s)
{
if (this != &s)
{
_str = new char[s._capacity + 1];
_size = s._size;
_capacity = s._capacity;
memcpy(_str, s._str, _size + 1);
}
return *this;
}
//现代写法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
//string类返回字符串指针的函数
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
//析构函数
~string()
{
_size = _capacity = 0;
delete[] _str;
_str = nullptr;
}
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 reserve(size_t n = 0)
{
//只允许进行扩容
if (n > _capacity)
{
cout << "reserve->" << _capacity << endl;
char* tmp = new char[n + 1];
_capacity = n;
//strcpy(tmp, _str);
//拷贝构造的string类中包含'\0',且'\0'后还有字符
//strcpy只能拷贝到'\0'位置,剩余字符不能拷贝
memcpy(tmp, _str, n + 1);
delete[] _str;
_str = tmp;
}
}
void push_back(char c)
{
if (_size == _capacity)
{
//二倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//for (size_t i = 0; i < len; i++)
//{
// _str[_size + i] = str[i];
//}
//strcpy(_str + _size, str);
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
//扩容到_size+n
reserve(_size + n);
}
size_t end = _size;
//挪动数据
//当插入0位置时,end在结束时为-1,
//由于end为无符号整型,会整型提升为整数的最大值,会发生越界访问的错误,程序崩溃
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
//插入n个字符c
for (int i = 0; i < n; i++)
{
_str[i + pos] = c;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容到_size+len
reserve(_size + len);
}
size_t end = _size;
//挪动数据
//当插入0位置时,end在结束时为-1,
//由于end为无符号整型,会整型提升为整数的最大值,会发生越界访问的错误,程序崩溃
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
end--;
}
//插入字符串str
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
//void erase(size_t pos, size_t len = npos)
//{
// assert(pos < _size);
// //要删除的长度很长,或字符串很短
// if (len == npos || pos + len >= _size)
// {
// _str[pos] = '\0';
// //_str[_size] = '\0';
// _size=pos;
// }
// else
// {
// //进行覆盖移动
// for (size_t i = 0; i < _size-(pos+len); i++)
// {
// _str[pos+i] = _str[pos + i+len];
// }
// _str[_size - len] = '\0';
// _size -= len;
// }
//}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//要删除的长度很长,或字符串很短
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
//_str[_size] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char c, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t find(const char* s, size_t pos = 0)
{
assert(pos < _size);
//pos位置开始
const char* ptr = strstr(_str + pos, s);
if (ptr)
{
//指针减指针为之间的字符个数,也为子串的起始位置
return ptr - _str;
}
else
{
return npos;
}
}
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
string tmp;
//判断长度
size_t n = len;
//if (pos + len > _capacity)
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
//把需要的数据拷贝string类tmp
for (size_t i = 0; i < n; i++)
{
tmp += _str[pos + i];
}
return tmp;
}
void resize(size_t n, char ch = '\0')
{
if (n < _capacity)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i <n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
// // "hello" "hello" false
// // "helloxx" "hello" false
// // "hello" "helloxx" true
//版本一
//bool operator<(const string& s)
//{
// size_t i1 = 0;
// size_t i2 = 0;
// while (i1 < _size && i2 < s._size)
// {
// if (_str[i1++] < s._str[i2++])
// {
// return true;
// }
// else
// {
// return false;
// }
// }
// return _size < s._size;
//}
//版本二简洁写法
bool operator<(const string& s)const
{
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
bool operator==(const string& s)const
{
return _size == s._size &&
memcpy(_str, s._str, _size);
}
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);
}
private:
char* _str;
size_t _capacity;//string类的存储容量
size_t _size;//string类的大小
public:
static size_t npos;
//const static size_t npos = -1;
//整型可以这样使用,其他类型不可以,不建议这样使用
};
size_t string::npos = -1;
ostream& operator<<(ostream& out, string& s)
{
//out << s.c_str() << endl;
for (int i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' ||ch == '\n')
{
ch = in.get();
}
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
//使用一个128字节大小的数组,把读取到的字符存放数组
//如果buff数组到127字符,一次性加载到string类对象
//此时还有字符没有读取完,继续存放在buff数组,直到读取完毕
//最后再把字符串加载到string类末尾
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
}
}