文章目录
- 一.string类的常用接口声明
- 二.string类的模拟实现
- string类接口
- 默认成员函数
- 容量相关的函数
- 修改字符串相关函数
- 迭代器相关函数
- 元素遍历相关函数
- String operations
- 非成员函数
- 三.string类的写时拷贝
一.string类的常用接口声明
(1).string类对象的常见构造
(1). string();
功能 : 构造空的string类对象,即空字符串
(2).string (const string& str);
功能 : 拷贝构造函数
(3).string (const string& str, size_t pos, size_t len = npos);
功能 : 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾.
(4).string (const char* s);
功能 : 拷贝s所指的以空结尾的字符序列(C字符串).
(5).string (const char* s, size_t n);
功能 : 拷贝s所指的字符数组中前n个字符.
(6).string (size_t n, char c);
功能 : 用n个连续的字符c填充字符串.
// 1. 空字符串
string s1;
// 2. 拷贝s所指的以空结尾的字符序列
string s2("hello world");
string s3 = "hello world";
// 3. 拷贝构造函数
string s4(s3);
// 4. 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾
string s5(s4,0,3);
string s6(s4,0,string::npos);
// 5. 拷贝s所指的字符数组中前n个字符
string s7("123456",3);
// 6. 用n个连续的字符c填充字符串
string s8(10,'a');
(2).string类对象的访问和遍历方式
构造出string类对象以后,我们想要遍历字符串要怎么做呢?
(1). 下标访问
(2). 迭代器
iterator begin();
const_iterator begin()const;
功能 : 返回指向字符串第一个字符的迭代器.如果string对象是const限定的,则函数返回一个const迭代器
iterator end();
const_iterator end()const;
功能 : 返回指向字符串最后一个字符的下一个位置的迭代器.如果string对象是const限定的,则函数返回一个const迭代器
(3).范围for
int main()
{
string s1("hello world");
// 1. 下标访问
for(size_t i = 0;i < s1.size();i++)
{
cout<<s1[i]<<" ";
// cout<<s1.at(i)<<endl;
}
cout<<endl;
// 2. 迭代器
// begin()返回的是第一个位置,end()返回的是最后一个位置的下一个位置
string::iterator it = s1.begin();
while(it != s1.end())
{
cout<<*it<<" ";
it++;
}
cout<<endl;
// 反向迭代器,逆序遍历
string::reverse_iterator rit = s1.rbegin();
while(rit != s1.rend())
{
cout<<*rit<<" ";
rit++;
}
cout<<endl;
// 3.范围for
for(auto& e : s1)
{
cout<<e<<" ";
}
cout<<endl;
}
(4).string类对象的修改操作
(1).void push_back(char c)
功能 : 将字符c追加到字符串的末尾,使其长度增加1.
(2).string& append (const char* s);
string& append (const string& str);
更多形式可查阅 append
int main()
{
string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.append("world");
string s2("!!");
s1.append(s2);
}
(3).string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
int main()
{
string s1;
s1 += ' ';
s1 += "hello ";
string s2("world");
s1 += s2;
}
(4). string& insert (size_t pos, const string& str);
string& insert (size_t pos, const char* s);
功能 : 在pos位置插入字符串
int main()
{
string s1("hello world");
s1.insert(0,"xx");
string s2("!!!!");
s1.insert(2,s2);
}
(5). string& erase (size_t pos = 0, size_t len = npos);
iterator erase (iterator p);
iterator erase (iterator first, iterator last);
(1). 删除字符串值中从字符位置pos开始并跨越len个字符的部分(如果内容太短或len是basic_string::npos,则删除到字符串末尾)。
请注意,默认参数会删除字符串中的所有字符(如成员函数clear).
(2). 删除p所指的字符.
(3). 删除[first,last]范围内的字符序列。
功能 : 删除基本字符串的一部分,减少其长度.
int main()
{
string s1("hello world");
s1.erase(0,3);
}
(6). const char* c_str() const;
功能 : 获取C字符串
返回一个指向数组的指针,该数组包含一个以null结尾的字符序列(即C字符串),表示string对象的当前值。此数组包含组成string对象值的相同字符序列,以及结尾处的附加终止空字符(‘\0’)。
string s1("hello world");
cout << s1 << endl; // 调用 operator<<(cout,s1)
cout << s1.c_str() << endl; // 调用 operator<<(cout,const char*)
s1.resize(20);
s1 += "!!!";
cout << s1 << endl;
cout << s1.c_str() << endl;
(7). size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
size_t find (const char* s, size_t pos, size_t n) const;
size_t find (char c, size_t pos = 0) const;
功能 : 在字符串中搜索由其参数指定的序列的第一个匹配项 :
(8).string substr (size_t pos = 0, size_t len = npos) const;
功能 : 返回一个新构造的字符串对象,子字符串是对象的一部分,从字符位置pos开始,跨越len个字符(或直到字符串的结尾,以先到者为准).
// 获取域名
string GetDomain(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
size_t start = pos + 3;
size_t end = url.find('/', start);
if (end != string::npos)
{
return url.substr(start,end - start);
}
else
{
return string();
}
}
else
{
return string();
}
}
// 获取协议名
string GetProtocol(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
return url.substr(0,pos);
}
else
{
return string();
}
}
string url = "http://www.cplusplus.com/reference/string/string/find/";
cout << GetDomain(url) << endl;
cout<<GetProtocol(url)<<endl;
(3).string类对象的容量操作
(1).size_t size() const;
功能 : 返回字符串的长度(以字节为单位).
(2).size_t capacity() const;
功能 : 返回当前为字符串分配的存储空间的大小(以字节为单位).
(3).bool empty() const;
功能 : 返回字符串是否为空(即其长度是否为0).
(4).void clear();
功能 : 删除字符串的内容,该字符串将变为空字符串(长度为0).
(5).void resize (size_t n);
void resize (size_t n, char c);
功能 : 将字符串大小调整为n个字符的长度。
如果n小于当前字符串长度,则当前值将缩短为其第一个n个字符,从而删除超过n个的字符。
如果n大于当前字符串长度,则通过在末尾插入尽可能多的字符以达到n的大小,来扩展当前内容。如果指定了c,则新元素将初始化为c,否则,为空字符.
(6).void reserve (size_type n = 0);
功能 : 请求对字符串容量进行调整,以适应计划的大小更改,长度最多为n个字符.
如果n大于当前字符串容量,则函数会使容器将其容量增加到n个字符(或更大)。
在所有其他情况下,它被视为收缩字符串容量的非绑定请求:容器实现可以自由地进行其他优化,并将基本字符串保留为大于n的容量。
string s1;
cout << "s1 size :"<<s1.size() << endl;
cout << "s1 capacity : "<<s1.capacity() << endl;
cout << s1 << endl;
s1.resize(20, 'x');
cout << "s1 size :" << s1.size() << endl;
cout << "s1 capacity : " << s1.capacity() << endl;
cout << s1 << endl;
string s2("hello world");
s2.resize(5);
cout << s2 << endl;
string s3;
cout << "s3 size :" << s3.size() << endl;
cout << "s3 capacity : " << s3.capacity() << endl;
s3.reserve(40);
cout << "s3 size :" << s3.size() << endl;
cout << "s3 capacity : " << s3.capacity() << endl;
二.string类的模拟实现
string类接口
namespace lyp
{
class string
{
public:
// 迭代器
typedef char* iterator;
// const迭代器
typedef const char* const_iterator;
// 默认成员函数
string(const char* str = "");
string(const string& str);
string& operator=(const string& str);
~string();
// 修改操作函数
void push_back(char c);
void append(const char* str);
string& operator+=(char c);
string& operator+=(const char* str);
string& insert(size_t pos,char c);
string& insert(size_t pos, const char* str);
string& erase(size_t pos, size_t len = npos);
void swap(string& str);
// 迭代器相关函数
iterator begin();
const_iterator begin()const;
iterator end();
const_iterator end()const;
// 容量相关函数
size_t size()const;
size_t capacity()const;
void resize(size_t n,char c = '\0');
void reserve(size_t n);
void clear();
bool empty()const;
// 元素遍历函数
char& operator[](size_t index);
const char& operator[](size_t index)const;
// String operations
const char* c_str()const;
size_t find(char c,size_t pos = 0)const;
size_t find(const char* str,size_t pos = 0)const;
size_t rfind(char c, size_t pos = npos)const;
size_t rfind(const char* str, size_t pos = npos)const;
private:
char* _str;
size_t _size; // 有效字符的个数
size_t _capacity; // 能存储有效字符的最大个数
static const size_t npos; // 静态无符号成员变量
};
const size_t string::npos = -1;
// 非成员函数
// relational operators
bool operator<(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
// << >> 运算符重载
istream& operator>>(istream& in,string& str);
ostream& operator<<(ostream& out, const string& str);
// getline
istream& getline(istream& in, string& str);
}
默认成员函数
(1).构造函数
string(const char* str = "");
函数的默认参数为空字符串,当然我们也可以传递自己给定的字符串,创建的对象存储字符串的空间比参数字符串的长度多 1 ,目的是为了存储空字符,并将参数字符串的内容拷贝到对象字符串空间中_size 和 _capcity 都初始化为参数字符串的长度
string(const char* str = "")
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
,_capacity(strlen(str))
{
strcpy(_str,str);
}
(2).拷贝构造函数
在实现我们的拷贝构造函数之前,我们要先了解一下深拷贝和浅拷贝
浅拷贝又称值拷贝,只是单纯的拷贝值而已,浅拷贝在某些情况下是不存在问题的,但是对于string类的构造函数如果使用浅拷贝,就会出现问题,因为浅拷贝之后两个对象的字符串指针指向同一块内存空间,在对象销毁时,这块内存空间就被析构了两次
而编译器默认生成的拷贝构造函数完成的是浅拷贝,所以我们自己需要去实现深拷贝,即拷贝构造出来的对象和源对象拥有不同的字符串空间
深拷贝的实现一般有两种方式,一种是传统方式,一种是现代方式
传统方式的步骤如下
(1).新开一块和源对象字符串空间一样大的空间
(2).将源对象字符串的内容拷贝到新空间
string(const string& str)
:_str(new char[strlen(str._str) + 1])
,_size(str._size)
,_capacity(str._capacity)
{
strcpy(_str, str._str);
}
现代方式的步骤如下
(1).利用我们上面写好的构造函数,用 str 构造出一个临时对象 tmp
(2).分别交换 创建的对象 和 tmp 对象的成员变量即可
注意 : 创建的对象_str 要初始化为空指针,否则创建的对象_str 为随机值,和 tmp 对象进行交换以后,tmp对象的_str为随机值,在tmp对象销毁时,调用析构函数,释放tmp对象的_str所指向的空间会出现问题
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str); // 构造临时对象
std::swap(_str,tmp._str); // 交换
std::swap(_size,tmp._size); // 交换
std::swap(_capacity,tmp._capacity); // 交换
// 后面自己实现的swap函数
//swap(tmp);
}
(3).赋值运算符重载
编译器默认生成的赋值运算符重载完成的也是浅拷贝,因此我们仍需要自己去实现深拷贝,赋值运算符重载同样也有两种写法
传统写法步骤如下
(1).释放掉原空间,新开一块和 str 字符串空间一样大的空间
(2).将 str 的内容拷贝到新空间中
string& operator=(const string& str)
{
// 防止自己给自己赋值
if (this != &str)
{
delete[]_str; // 释放空间
_str = new char[strlen(str._str) + 1]; // 新开一块空间
strcpy(_str, str._str); // 将 str 的内容拷贝到新空间中
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
现代写法的步骤如下 :
注意 : 现代写法的参数并没有传引用,而是传值
(1). 因为是传值,所以会调用拷贝构造函数构造出 str
(2). 分别交换 创建的对象 和 str 对象的成员变量即可
string& operator=(string str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
// 后面自己实现的swap函数
//swap(str);
return *this;
}
但此方法有一个缺陷,就是当自己给自己赋值的时候,自己存储字符串的地址发生了改变,我们可以采取以下写法来避免该情况发生
string& operator=(const string& str)
{
// 防止自己给自己赋值
if (this != &str)
{
string tmp(str._str);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
// 后面自己实现的swap函数
//swap(tmp);
}
return *this;
}
(4).析构函数
析构函数所要做的事情是将我们在堆上申请的空间释放掉,防止内存泄露的发生
~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
容量相关的函数
(1). size_t size()const;
直接返回 _size 即可,加上 const 防止对象被改变
size_t size()const
{
return _size;
}
(2).size_t capacity()const;
直接返回 _capacity 即可,加上 const 防止对象被改变
size_t capacity()const
{
return _capacity;
}
(3).void reserve(size_t n);
实现该函数的步骤
(1).如果 n > _capacity,新开一块容量为n的新空间,将原空间的内容拷贝到新空间中,释放原空间
(2).若 n < _capacity,则不需要发生变动
注意 : 我们将原空间的内容拷贝到新空间时,不要使用 strcpy 函数,因为如果我们的有效字符中存在’\0’,使用 strcpy 函数并不会把 ‘\0’ 拷贝下来,应使用 strncpy 函数
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; // 新开一块空间
strncpy(tmp,_str,_size + 1); // 将原空间的内容拷贝到新空间中
delete[]_str; // 释放原空间
_str = tmp;
_capacity = n;
}
}
(4).void resize(size_t n,char c = ‘\0’);
实现该函数需要注意的三点
(1). 当 n <= _size 时,直接将 _size = n 将有效字符长度缩小即可,注意要在末尾加上’\0’ (_str[_size] = ‘\0’ )
(2).当 _size < n <= _capacity 时,我们将有效字符的长度增加到 n,增加出来的有效字符内容是 c
(3).当 n > _capacity时,先调用上面的 reserve 函数进行增容,再将有效字符的长度增加到 n,增加出来的有效字符内容是 c
void resize(size_t n, char c = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
// n > _size
else
{
if (n > _capacity)
reserve(n);
for (size_t i = _size; i < n; i++)
_str[i] = c;
_str[n] = '\0';
_size = n;
}
}
(5).void clear();
清空字符串的有效字符,直接将 _size = 0 即可,注意将首位置置成 ‘\0’
void clear()
{
_size = 0;
_str[_size] = '\0';
}
(6).bool empty()const;
判断 _size 是否等于 0,等于0返回 true,不等于0返回 false
bool empty()const
{
return _size == 0;
}
修改字符串相关函数
(1).void push_back(char c);
(1).若_size == _capacity ,说明容量不够,需要增容,考虑到字符串有可能为空字符串,因此还需要判断_capacity 是否等于0,若等于0,开4字节空间,若不等于0,开原来容量的2倍即可
(2).在字符串的末尾插入字符 c,实现时将 _size 位置内容改成字符 c,然后在末尾补上’\0’
void push_back(char c)
{
// 增容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;// 字符串的末尾插入字符 c
_size++; // 有效字符数 + 1
_str[_size] = '\0'; // 末尾补上'\0'
}
(2).void append(const char* str);
在字符串的末尾追加一个字符串
(1).计算出原字符串加上一个字符串的新长度,若新长度大于_capacity,则增容到新长度
(2).使用 strcpy 函数将一个字符串拷贝到从原字符串’\0’开始的位置
void append(const char* str)
{
size_t len = _size + strlen(str); // 计算新长度
if (len > _capacity) // 若新长度大于 _capacity,增容
{
reserve(len);
}
strcpy(_str + _size, str); // 拷贝到从原字符串'\0'开始的位置
_size = len; // 改变 _size
}
(3). string& operator+=(char c);
在字符串末尾追加字符 c
我们可以复用上面写过的 push_back 接口,从而实现operator+=函数
string& operator+=(char c)
{
push_back(c);
return *this;
}
(4).string& operator+=(const char* str);
在字符串末尾追加字符串
我们可以复用上面写过的 append 接口,从而实现operator+=函数
string& operator+=(const char* str)
{
append(str);
return *this;
}
(5).string& insert(size_t pos,char c);
在字符串 pos 位置处插入字符 c
(1).若_size == _capacity ,说明容量不够,需要增容,考虑到字符串有可能为空字符串,因此还需要判断_capacity 是否等于0,若等于0,开4字节空间,若不等于0,开原来容量的2倍即可
(2).设置一个end指针,指向字符串的’\0’的位置,将 end 指向的字符往后移动一位,直到 end < _str + pos
(3).在pos位置插入字符c
string& insert(char c, size_t pos)
{
assert(pos <= _size); // 检验pos位置的合法性
if (_size == _capacity) // 增容
reserve(_capacity == 0 ? 4 : _capacity * 2);
char* end = _str + _size; // 指向字符串的'\0'位置
//将pos位置及其之后的字符向后挪动一位
while (end >= _str + pos)
{
*(end + 1) = *(end);
end--;
}
_str[pos] = c; // 在pos位置插入字符c
_size++; // 有效字符 + 1
return *this;
}
(6).string& insert(const char* str, size_t pos)
在字符串 pos 位置插入一个字符串
(1).计算出原字符串加上一个字符串的新长度,若新长度大于_capacity,则增容到新长度
(2).设置一个end指针,指向字符串的’\0’的位置,将 end 指向的字符往后移动 len 位,直到 end < _str + pos
(3).将字符串拷贝到以pos位置开始的位置(使用strncpy函数)
string& insert(const char* str, size_t pos)
{
assert(pos <= _size); // 检验pos位置的合法性
size_t len = _size + strlen(str); // 计算新长度
if (len > _capacity) // // 若新长度大于 _capacity,增容
reserve(len);
char* end = _str + _size;
//将pos位置及其之后的字符向后挪动len位
while (end >= _str + pos)
{
*(end + len) = *(end);
end--;
}
strncpy(_str + pos, str, strlen(str));// 拷贝字符串
_size += strlen(str);// 有效字符 + strlen(str)
return *this;
}
(7).string& erase(size_t pos, size_t len = npos);
删除从pos位置开始直到 len 个字符
(1).计算从pos位置到最后一个字符之间有多少字符(算上pos位置),即 _size - pos
(2).若 _size - pos 大于 len,说明有足够的字符可以删除,使用 strcpy(_str + pos,_str + pos + len)
(3).若 _size - pos 小于等于 len,说明没有足够的字符可以删除,那么只能把从pos位置开始往后的字符全部删除
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size); // 检验pos位置的合法性
size_t leftLen = _size - pos; // 计算从pos位置到最后一个字符之间有多少字符(算上pos位置)
// 若 _size - pos 小于等于 len,说明没有足够的字符可以删除,那么只能把从pos位置开始往后的字符全部删除
if (len >= leftLen)
{
_str[pos] = '\0';
_size = pos;
}
// 若 _size - pos 大于 len,说明有足够的字符可以删除,使用 strcpy(_str + pos,_str + pos + len)
else
{
strcpy(_str + pos,_str + pos + len);
_size -= len;
}
return *this;
}
(8).void swap(string& str);
交换两个string对象的内容,调用全局的swap函数模板即可
实现完swap函数就可以将上面的拷贝构造和赋值运算符重载函数进行一些优化,直接调用我们自己写的swap函数即可
void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
迭代器相关函数
string类中的迭代器实际上就是字符指针,只是给字符指针起了一个别名叫iterator而已。
typedef char* iterator;
typedef const char* const_iterator;
(1). iterator begin();
返回字符串首元素的地址即可
iterator begin()
{
return _str;
}
考虑这样的场景,func 的函数形参是const对象引用(也就是使用者不想去改变对象),如果我们只实现了非const版本,首先编译不会通过,因为const对象是不能去调用非const成员函数的(权限的放大),所以我们需要去实现const版本的begin()函数,end()函数同理
// 为什么要重载 const 版本的 begin()和 end() 函数?
void func(const string& str)
{
string::iterator it1 = str.begin(); // 若没有重载const版本,编译不通过
while (it1 != str.end())
{
cout << *it1;
it1++;
}
}
(2). const_iterator begin()const;
const_iterator begin()const
{
return _str;
}
(3).iterator end();
返回最后一个字符的下一个位置的地址
iterator end()
{
return _str + _size;
}
(4).const_iterator end()const;
const_iterator end()const
{
return _str + _size;
}
之前所讲的使用范围for去遍历字符串中的元素,实际上编译器会将其转换成迭代器去进行遍历,如果去掉 begin 和 end 中的任何一个函数,范围for都不能成立
string s1("hello world");
for(auto& ch : s1)
cout<<ch;
元素遍历相关函数
(1).char& operator[](size_t index);
我们在遍历字符串的元素过程当中,需要访问或修改字符串的内容,因此重载[]运算符,使string对象能够像内置类型一样使用[],返回 _str[index] 处字符的引用,是为了可读可写,若传值返回,不能进行写操作
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
考虑这样的场景,f 的函数形参是const对象引用(也就是使用者不想去改变对象),如果我们只实现了非const版本,首先编译不会通过,因为const对象是不能去调用非const成员函数的(权限的放大),所以我们需要去实现const版本的operator[]
// 为什么要重载 const 版本的 operator[] ?
void f(const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
cout << str[i];
}
}
(2).const char& operator[](size_t index)const;
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
String operations
(1).const char* c_str()const;
返回 C 形式的字符串
const char* c_str()const
{
return _str;
}
(2). size_t find(char c,size_t pos = 0)const;
从pos位置开始查找出现的第一个字符 c ,思路很简单,从pos位置开始遍历字符串,若找到该字符,返回该字符在字符串的位置,若没有找到,则返回 npos
size_t find(char c, size_t pos = 0)const
{
assert(pos < _size); //检测下标的合法性
for (size_t i = pos; i < _size; i++) // 遍历
{
if (_str[i] == c)
return i; // 返回下标
}
return npos; // 没找到,返回npos
}
(3).size_t find(const char* str,size_t pos = 0)const;
从pos位置开始查找第一个目标字符串,若找到目标字符串,则返回目标字符串第一个字符的位置,否则,返回 npos,查找子串的话,我们可以使用 strstr 函数,若 strstr 函数返回值不为空指针,说明找到了目标字符串,返回的是指向目标字符串第一个字符的指针,返回值和字符串的起始地址相减就可以得到目标字符串第一个字符的位置,若 strstr 函数返回值为空指针,说明没有找到,返回 npos
size_t find(const char* str, size_t pos = 0)const
{
// 找到目标字符串
if (strstr(_str + pos, str))
return strstr(_str + pos, str) - _str;
else
return npos;
}
(4).size_t rfind(char c,size_t pos = npos)const;
rfind 和 find 不同的是 rfind 函数是从 pos 位置开始倒着找出现的第一个字符 c ,默认从最后一个字符开始找
查找步骤如下 :
(1).构造出一个临时对象,将临时对象逆置
(2).如果 pos 位置大于等于 _size,让 pos = _size - 1,从最后一个字符开始找
(3).调用find函数正向查找即可
size_t rfind(char c, size_t pos = npos)const
{
string tmp(*this); // 构造出一个临时对象
reverse(tmp.begin(),tmp.end()); // 临时对象逆置
// 如果 pos 位置大于等于 _size,让 pos = _size - 1,从最后一个字符开始找
if (pos >= _size)
{
pos = _size - 1;
}
// 调用find函数正向查找
size_t ret = find(c, _size - 1 - pos);
if (ret != npos)
return size() - 1 - ret;
else
return npos;
}
(5).size_t rfind(const char* str,size_t pos = npos)const;
查找步骤如下 :
(1).构造出一个临时对象,将临时对象逆置,将要查找的字符串也逆置
(2).如果 pos 位置大于等于 _size,让 pos = _size - 1,从最后一个字符开始找
(3).调用find函数正向查找即可
size_t rfind(const char* str, size_t pos = npos)const
{
string tmps(*this); // 构造临时对象
reverse(tmps.begin(), tmps.end()); // 逆置临时对象
// 如果 pos 位置大于等于 _size,让 pos = _size - 1,从最后一个字符开始找
if (pos >= _size)
pos = _size - 1;
int len = strlen(str);
// 构造tmp数组用于拷贝要查找的字符串
char* tmp = new char[len + 1];
// 拷贝
strcpy(tmp, str);
// 逆置拷贝的字符串
size_t left = 0, right = len - 1;
while (left < right)
{
std::swap(tmp[left],tmp[right]);
}
// 调用find函数正向查找即可
size_t ret = find(tmp, _size - 1 - pos);
// 释放掉tmp数组,防止内存泄露
delete[]tmp;
if (ret != npos)
return _size - 1 - ret;
else
return npos;
}
非成员函数
relational operators
(1).bool operator<(const string& s1, const string& s2);
比较两个字符串,使用 strcmp 函数,注意不要直接拿两个字符串相比(比较的是两个字符串的地址)
bool operator<(const string& s1,const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
(2).bool operator==(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
(3).bool operator<=(const string& s1, const string& s2);
bool operator<=(const string& s1,const string& s2)
{
return s1< s2 || s1 == s2;
}
(4).bool operator>(const string& s1, const string& s2);
bool operator>(const string& s1,const string& s2)
{
return !(s1 <= s2);
}
(5).bool operator>=(const string& s1, const string& s2);
bool operator>=(const string& s1,const string& s2)
{
return !(s1 < s2);
}
(6).bool operator!=(const string& s1, const string& s2);
bool operator!=(const string& s1,const string& s2)
{
return !(s1 == s2);
}
<< >> 运算符重载
(1).istream& operator>>(istream& in,string& str);
cin 在读取缓冲区的字符时,遇到空格,换行会结束这一次的读取,且会丢弃缓冲区中的空格,换行
istream类中定义了 get 成员函数,该成员函数的特点是遇到换行会结束此次读取,但不会丢弃缓冲区的换行
istream& operator>>(istream& in,string& str)
{
// 输入前清空已有的内容
str.clear();
char ch;
// 输入字符串
in.get(ch);
// ch 不等于空格或换行时尾插到字符串尾
while (ch != ' ' && ch != '\n')
{
str += ch;
in.get(ch);
}
return in; // 支持连续输入
}
(2).ostream& operator<<(ostream& out, const string& str);
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
out << ch;
return out; // 支持连续输出
}
getline
istream& getline(istream& in, string& str);
getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符。
istream& getline(istream& in, string& str)
{
// 输入前清空已有的内容
str.clear();
char ch;
// 输入字符串
in.get(ch);
// ch 不等于换行时尾插到字符串尾
while (ch != '\n')
{
str += ch;
in.get(ch);
}
return in; // 支持连续输入
}