模拟实现string类
模拟string类,采用声明和定义分离的形式,声明在.h文件中,定义在.cpp文件中
1、成员变量
c
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos; //需要在类外面定义
- _str用来存储字符串,_size用来表示有效字符个数,_capacity用来表示可以存储有效字符的容量
- npos是一个固定的特殊数字 ,默认值为-1,用来表示两种意思:1.查找时没找到 2.一直到最后
- 因为string本质是一个动态顺序表,所以字符串通过在堆上申请空间进行存储
2、成员函数
2.1 默认构造函数
c
namespace MyString
{
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
//memcpy(_str, str, _size);
//strcpy(_str, str); //直接用strcpy也可以
memcpy(_str, str, _size + 1);
}
注意:形参必须加const修饰,这样才能用C语言中的常量字符串初始化string对象,形参的缺省值直接给一个空字符即可,用""表示
2.2 拷贝构造函数
c
//传统写法
tring::string(const string& s)
{
cout << "string::string(const string& s)" << endl;
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//优化后写法
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
注意:优化后写法不需要手动申请空间才初始化,而是调用构造函数去完成,重载swap函数,利用swap函数进行互换,如果string对线中有'\0',只会把'\0'前面的字符拷贝过去
2.3 析构函数
c
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
2.4 operator=
传统写法:
c
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
注意:这种写法需要手动开辟空间tmp,并且释放空间_str,改进后通过重载函数完成这些操作
优化后:
c
//优化后写法
string& string::operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
return *this;
}
//再次改进
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& string::operator=(string tmp)
{
swap(tmp);
return *this;
}
这种写法通过拷贝构造申请空间,利用局部对象出了作用域就会被销毁的特点,通过swap将需要释放的资源交换给这个局部变量,同时可以不用库里面的swap,重载一个swap交换两个string对象
2.5 c_str()
c
const char* string::c_str() const
{
return _str;
}
注意:要加上const,使普通对象和const类型的对象都可以使用
2.6 size()
c
size_t string::size() const
{
return _size;
}
2.7 operator[]
c
//可读可写
char& string::operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
c
//只读
const char& string::operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
两个函数构成函数重载,普通对象调用读写版本,const对象会调用只读版本
2.8 iterator
iterator是string类里面定义的类型,string类中的iterator是通过typedef来实现的
c
typedef char* iterator;
typedef const char* const_iterator;
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
迭代器设置为char*指针类型,所以begin()和end()是获取指针位置
2.9 reserve
c
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve:" << n << endl;
char* tmp = new char[n + 1];
//strcpy(tmp, _str);
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2.10 push_back
c
void string::push_back(char ch)
{
//先检查容量,进行扩容
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
注意:调用reserve函数进行扩容时,需要先进行判断,capacity==0时给一个初始大小
2.11 pop_back
c
void string::pop_back()
{
assert(_size > 0);
--_size;
_str[_size] = '\0';
}
2.12 append
c
oid string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
//strcpy(_str+_size, str);
memcpy(_str + _size, str, len + 1);
_size += len;
}
2.13 operator+=
c
//追加一个字符
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//追加字符串
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
2.14 insert
c
//插入一个字符
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
注意:注意挪动时的判断条件,end不能初始化为int类型,如果end初始化类型为int(有符号),pos和_size是size_t(无符号),例如:
c
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
end最后会减到-1,而-1在无符号数里是4294967295(极大值),永远为真,程序崩溃
整形的静态成员变量在加上const修饰后就可以在声明的地方给默认值,仅限整形
c
static const size_t npos = -1; //正确
static const double a = 1.2; //错误
c
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
// 挪动数据
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
return *this;
}
2.15 erase
c
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
// 要删除的数据,大于pos后面的字符个数
// pos后面全删
if (len == npos || len >= (_size - pos))
{
_size = pos;
_str[_size] = '\0';
}
else
{
size_t i = pos + len;
memmove(_str + pos, _str + i, _size + 1 - i);
_size -= len;
}
return *this;
}
注意 :pos是需要删除数据的起始位置,数组分为两部分,[0,pos-1]和[pos,_size-1] ,[0,pos-1]区域不需要删除 ,[pos,_size-1]是待删除区域 ,需要删除len个元素,当不需要删除的字符数加需要删除的字符数len大于等于全部的有效字符数时,说明待删除区域的所有字符都要删除,即pos+len>=_size 时,需要从pos位置开始删除后面的所有字符,最后需要把pos位置的字符设置成\0
2.16 clear
c
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
清空字符串,将第一个位置改为'\0',把字符串长度改为0
2.17 find
c
//查找一个字符
size_t string::find(char ch, size_t pos) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
//查找一个字符串
size_t string::find(const char* str, size_t pos) const
{
const char* p1 = strstr(_str + pos, str);
if (p1 == nullptr)
{
return npos;
}
else
{
return p1 - _str;
}
}
注意 :查找字符串的位置时,最后应该返回的位置是p1 - _str
2.18 substr
c
string string::substr(size_t pos, size_t len) const
{
if (len == npos || len >= _size - pos)
{
len = _size - pos;
}
string ret;
ret.reserve(len);
for (size_t i = 0; i < len; i++)
{
ret += _str[pos + i];
}
return ret;
}
利用reserve函数给新的字符串分配所需空间,再拷贝字符串
2.19 operator<
c
bool string::operator<(const string& s) const
{
size_t i1 = 0, i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s[i2])
{
return true;
}
else if (_str[i1] > s[i2])
{
return false;
}
else
{
++i1;
++i2;
}
}
return i2 < s._size;
}
注意:string类对象是根据ASCII码值进行比较的,不能用strcmp,strcmp遇到'\0'会终止,不支持带空字符的字符串,也不能用memcmp,memcmp 只管内存内容,只比较长度相等的部分
2.20 operator==
c
bool string::operator==(const string& s) const
{
size_t i1 = 0, i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] != s[i2])
{
return false;
}
else
{
++i1;
++i2;
}
}
return i1 == _size && i2 == s._size;
}
利用<和==,解决剩下的部分
2.21 <=、>、>=、!=
c
bool string::operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator>=(const string& s) const
{
return !(*this < s);
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
2.22 operator<<
c
ostream& operator<<(ostream& out, const string& s)
{
//out << s.c_str();
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
注意 :流插入和流提取运算符重载要写在类外面,不能用str._str或str.c_str()打印,因为字符串的有效字符中可能有\0存在,这两种方法遇到\0会终止打印,无法将一个string对象完整的打印出来,所以要逐个打印
2.23 operator>>
c
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
in >> ch;
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
}
注意 :空格' '和\n作为输入时分割多个string对象的标志,cin >> ch 不能读取到空格和换行,需要借助get()函数才能读到空格和换行符,其次string进行二次流提取时会进行覆盖,插入前要先进行判断
优化版本:
c
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128];
int i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
开辟一个数组,将输入的字符存储在数组中,然后把数组的数据拷贝到string中
注意:istream和ostream必须用引用,因为这两个类中的对象不允许被拷贝或赋值