引言:string类包含的接口和涉及的内容相当多,小编只挑其中较为重要的接口简单介绍其用法,具体内容还需要读者自己去读文档,亲自切实体会
一.知识点及相关接口简单介绍
1.auto:最大的作用是用来简化代码,替换长类型
for:用法:遍历数组和容器的对象,底层就是替换为迭代器
//for实现遍历,可以自动赋值,自动迭代,自动判断结束
for (auto ch : s2)
{
cout << ch;
}
cout << endl;
//迭代器是实现遍历
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it;
++it;
}
cout << endl;
2.string 类对象的构造
函数名称 | 功能 |
string() | 构造空的string类对象,即空字符 |
string(const char* s) | 用常量字符串构造string类对象 |
string(const string& s) | 拷贝构造 |
3.string类对象的容量操作
函数名称 | 功能 |
size | 返回字符串的有效字符长度(不包括'\0') |
length | 返回字符串的有效字符长度(不包括'\0') |
capacity | 返回空间的总大小 |
empty | 判断字符串是否为空,是返回true,否则返回false |
clear | 清理有效字符 |
reserve | 为字符串预留出一定大小的空间 |
resize | 将有效字符的个数该成n个 |
1>size和length的用法及意义相同,不过size的适用情况更广泛,所以多用size
2>capacity中不包括'\0'
3>clear不改变capacity的大小
4>reserve不改变有效元素个数,当reserve的参数小于capacity时,在VS下是不会改变capacity的
5>resize若是增加元素,会改变capacity,若是消除元素,不会改变capacity
6>resize(size_t n)与resize(size_t n,char c)都是将元素个数改为n个,不同的是,若是增加元素,前者用0填充,后者用字符c填充
4.string类对象的访问
函数名称 | 功能 |
operator[] | 返回pos位置的字符 |
begin/end | begin获取第一个字符的迭代器,end获取最后一个字符的下一个位置的迭代器 |
rbegin/rend | rbegin获取最后一个字符的迭代器,rend获取第一个字符的下一个位置的迭代器,在反向迭代器中多用 |
5.string类对象的修改操作
函数名称 | 功能 |
push_back | 在字符串后插入一个字符 |
append | 在字符串后插入一个字符串 |
operator+= | 在字符串后插入一个字符串 |
c_str | 返回C格式的字符串,相当于返回成员变量中的字符数组指针 |
find | 从pos位置开始往后查找字符c是否存在 |
substr | 从pos位置开始往后截取len个字符构成新的字符串并返回 |
find_first_of | 在第一个字符串的pos位置开始往后查找第二个字符串出现的位置 |
6.string类的非成员函数
函数名称 | 功能 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串(默认遇到'\0'才停止) |
二.模拟实现
1.string类的成员变量:char* _str; size_t _size; size_t _capacity;
private:
char* _str;
size_t _size;
size_t _capacity;
2.构造函数
1>对于无参构造函数,为了保证程序可以正常运行,需要给_str开一块一个字符大小的空间,存储'\0',让编译器可以停下来
2>对于含参构造函数,计算_size和_capacity的大小,由于_capacity不包含'\0',所以初始的_size和_capacity的大小相同,用new为_str开一块(_capacity+1)个字符大小的空间,注意此处要多开一块空间存储'\0',再用strcpy将str中的内容拷贝到_str中
3>代码优化:使用全缺省可将无参和含参的构造函数合二为一
4>代码实现
String(const char* str=" ")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
3.析构函数
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.operator[]
//给普通对象使用
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//给const对象使用
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
5.迭代器
1>根据迭代器的底层实现可知,简化来说,迭代器是依托begin(),end()函数实现的,iterator最简单情况下可认为是指针,但不可一概而论
2>代码实现
//普通迭代器的实现
typedef char* iterator;
//给const对象使用的迭代器
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;
}
6.增添字符/字符串
补:扩容
//预留空间
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
1>push_back和operator+=
(1)push_back:检查是否需要扩容,直接将字符插入到'\0'的位置即可,注意需要为_str重新增添一个'\0'
(2)operatot+=直接调用push_back即可
(3)代码实现
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
2>append和operator+=
(1)append:检查是否需要扩容(若是原数组的新数组的长度大于原空间的二倍可以选择直接给所需要的大小空间,否则可以直接给原空间的二倍),直接将str插入到原'\0'所在位置即可
(2)operatot+=直接调用append即可
(3)代码实现
void string::append(const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity )
{
reserve((_size + len) > _capacity * 2 ? (_size + len) : _capacity * 2);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
3>insert
(1)判断是否需要扩容,从后往前将字符依次向后挪动一位,此过程中不要使用end<=pos的写法,因为size_t为无符号整型,若pos=0,则end=-1=INT_MAX,导致-1不小于0,从而陷入死循环,可以让end指向'\0'的后一个位置,避免end=-1的情况
(2)在指定位插入字符串与在指定位置插入字符思路类似,只是要从后往前将字符依次向后挪动待插入字符串长度位
(3)代码实现
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
int end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve((_size + len) > _capacity * 2 ? (_size + len) : _capacity * 2);
}
int end = _size + len+1;
while (end > pos)
{
_str[end] = _str[end - len];
--end;
}
for (int i = 0; i < len; i++)
{
_str[pos++] = str[i];
}
_size += len;
}
7.删
1>若剩余字符没有len个,则直接在pos位置放个'\0',否则从后往前将pos+len往后的所有内容向前挪动
2>代码实现
//删
void string::erase(size_t pos, size_t len)
{
assert(pos <= _size);
if (_size - pos <= len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (int i = pos+len; i <= _size; i++)
{
_str[i-len] = _str[i];
}
_size -= len;
}
}
8.查
//查
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
9.substr:在指定位置读取len个字符构成一个新的字符串
1>判断自pos位置后所剩的字符个数是否小于len,若小于则更新len为_size
2>若想让程序可以正常执行,需要显示实现拷贝构造,因为若无无显示拷贝构造,则编译器会自动调用默认拷贝构造,是个浅拷贝,此时要是在调用substr时是直接将结果拷贝给一个新定义的string,则会出现两次析构导致新的string指向的空间无效
3>代码实现
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
string s;
if (len > _size-pos)
{
len = _size-pos;
}
s.reserve(len);
for (int i = 0; i < len; i++)
{
s += _str[i+pos];
}
return s;
}
4>拷贝构造的实现
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
{
//方法一:
/*_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;*/
//方法二:
string tmp(s._str);
swap(tmp);
}
10.operator=
string& string::operator=(string& s)
{
//考虑自己给自己赋值的情况
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
//swap(s);
}
return *this;
}
11.比较大小
//比较大小
bool string::operator<(const string& s)
{
return strcmp(_str, s.c_str()) < 0;
}
bool string::operator==(const string& s)
{
return strcmp(_str, s.c_str()) == 0;
}
bool string::operator<=(const string& s)
{
return (*this < s )|| (*this == s);
}
bool string::operator>(const string& s)
{
return !(*this <= s);
}
bool string::operator>=(const string& s)
{
return !(*this < s);
}
bool string::operator!=(const string& s)
{
return !(*this == s);
}
12.流插入
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
13.流提取
1>由于>>遇到空格时默认它是分隔符会自动忽略,所以我们应该换取get()函数一个字符一个字符读取,才能保证程序正常运行
2>代码实现
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch=in.get();
}
return in;
}