前言
C++引入了面向对象的思想,相比于C语言,一个类能更好地对一些数据结构进行管理和操作。
在C语言中,我们使用字符数组和string.h中的库函数来维护一个字符串,数据与方法分离不说,由于底层的空间是自己维护的,稍不留神就可能造成越界
在C++中,基于面向对象的思想,用来管理字符串的string类便应运而生,从本质上讲,string类就是一个被封装了的字符数组
目录
- 1.string的简介
-
2.string的常见接口及模拟实现
2.1 string类对象的常见构造
2.2 string类对象的容量操作
2.3 string类对象获取元素和迭代器的接口
2.4 string类对象修改元素接口
2.5 string类字符串操作接口
2.6 成员常量
2.7 非成员函数重载
2.8 string类对象比较运算符的重载
2.9 string类对象的三种遍历方式 - 3.如何熟悉接口——刷题
- 4.还想说的话
1.string的简介
我们学习STL时,文档是我们的利器,学会查文档会让学习事半功倍,以下是两个C++文档网站:- 官网:www.cppreference.com
- 常用网站(更新至C++11):www.cplusplus.com
string的文档介绍:
- 字符串是表示字符序列的类。
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
typedef basic_string<char, char_traits, allocator> string
也就是说,我们常提到的string其实是basic_string类模板的使用单字节char的实例化并typedef成了string
,basic_sring亦可以用双字节的wchar_t实例化,用以处理ascii码无法表示的其他文字,如中文等
2.string的常见接口及模拟实现
2.1 string类对象的常见构造
函数名 | 功能 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用常量字符串来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
~string() | 析构函数 |
operator= | 赋值重载,将一个string对象赋值给另一个string对象 |
多种构造函数的使用:
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
接口的模拟实现:
- 构造函数
//全缺省带参默认构造函数
basic_string(const T* s = "")
:_size(strlen(s))
,_capacity(strlen(s))
{
_str = new T[_size + 1];
strcpy(_str, s);
}
- 析构函数
//析构函数
~basic_string() {
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
- 拷贝构造
//拷贝构造(现代写法)
basic_string(const basic_string& str)
: _str(nullptr)
{
if (this != &str) {
basic_string tmp(str._str);
swap(tmp);
}
}
- 赋值重载
//赋值重载(现代写法2)
basic_string& operator=(basic_string str) {
swap(str);
return *this;
}
2.2 string类对象的容量操作
接口名称 | 接口作用 |
---|---|
size() | 返回字符串有效字符长度(’\0’之前的字符长度) |
length() | 同size() |
capacity() | 返回当前分配给字符串的空间大小 |
empty() | 判断字符串是否为空字符串 |
reserve(n) | 重置capacity的大小,为字符串预留空间 |
resize(n, c) | 重置有效字符,将有效字符的个数该成n个,多出的空间用字符c填充 |
clear() | 清空有效字符,将字符串变为空字符串 |
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
接口的模拟实现:
- size()
//size()接口
size_t size() const{
return _size;
}
- length()
//length()接口
size_t length() const{
return _size;
}
- capacity()
//capacity()接口
size_t capacity() const {
return _capacity;
}
- empty()
// 判空
bool empty() const {
return _size == 0;
}
- reserve(n)
void reserve(int new_capacity) {
T* tmp = new T[new_capacity + 1];//多开的一个T的大小用来存'\0'
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
tmp = nullptr;
_capacity = new_capacity;
}
- resize(n, c)
void resize(size_t n, T c = '\0') {
if (n <= _size) {
_str[n] = '\0';
_size = n;
}
else {
if (n > _capacity) {
reserve(n);
}
for (size_t i = _size; i < n; i++) {
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
}
- clear()
// 清除
void clear() {
_str[0] = '\0';
_size = 0;
}
2.3 string类对象获取元素和迭代器的接口
接口名称 | 接口作用 |
---|---|
operator[ ] | 返回pos位置的字符 |
begin() | 返回第一个有效字符的迭代器 |
end() | 返回最后一个字符下一个位置的迭代器 |
rbegin() | 返回最后一个有效字符的迭代器 |
rend() | 返回第一个字符的前一个位置的迭代器 |
注意:在string类里,迭代器就是字符指针
注意:auto it = rbegin(); it++是让it指向前面一个位置的地址
接口的模拟实现:
- operator[]
//重载[]
T& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
const T& operator[](size_t pos) const{
assert(pos < _size);
return _str[pos];
}
- begin() 和 end()
typedef T* iterator;
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
2.4 string类对象修改元素接口
接口名称 | 接口作用 |
---|---|
push_back() | 在字符串后尾插字符c |
append() | 在字符串后尾插字符串s |
operator+= | 在字符串后尾插字符c/字符串s |
insert() | 在字符串下标为pos的位置开始插入n个字符c/字符串s |
erase() | 在字符串下标为pos的位置开始删除n个字符c/字符串s |
swap() | 交换两个类对象 |
接口的模拟实现:
- push_back() / +=
//push_back尾插一个字符
void push_back(const T c) {
if (_size == _capacity) {
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(new_capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
// 复用push_back 重载+=字符
void operator+=(const T c) {
push_back(c);
}
- qppend() / +=
//append尾插字符串
void append(const T* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, str);
_str[_size + len] = '\0';
_size += len;
}
//复用append,重载+=字符串
void operator+=(const T* str) {
append(str);
}
- insert()
//插入字符
basic_string& insert(size_t pos, const T c) {
assert(pos <= _size);
if (_size == _capacity) {
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(new_capacity);
}
_size++;
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
return *this;
}
//插入字符串
basic_string& insert(size_t pos, const T* s) {
assert(pos <= _size);
size_t len = strlen(s);
if (len + _size > _capacity) {
reserve(len + _size);
}
size_t end = _size + len;
while (end - len + 1 > pos) {
_str[end] = _str[end - len];
end--;
}
_size += len;
memcpy(begin() + pos, s, len * sizeof(T));
return *this;
}
- erase()
iterator erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (pos + len >= _size) {
_str[pos] = '\0';
}
else {
strcpy(begin() + pos, begin() + pos + len);
}
_size = strlen(_str);
return begin() + pos;
}
- swap()
void swap(basic_string& str) {
::swap(_size, str._size);
::swap(_capacity, str._capacity);
::swap(_str, str._str);
}
2.5 string类字符串操作接口
接口名称 | 接口作用 |
---|---|
find() | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,找不到返回npos |
rfind() | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置,找不到返回npos |
c_str() | 以常量字符串的形式返回string类对象中的字符指针 |
substr() | 从pos位置开始往后截取长度为n的子串s,返回新的子串的类;n默认为npos |
接口的模拟实现:
- find()
size_t find(const T 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;
}
size_t find(const T* s, size_t pos = 0) const {
assert(pos < _size);
T* ret = strstr(_str + pos, s);
if (ret == nullptr) {
return npos;
}
else {
while (*ret != _str[pos]) {
pos++;
}
return pos;
}
}
- c_str()
const T* c_str() const {
return _str;
}
- substr()
basic_string substr(size_t pos, size_t n = npos) {
assert(pos < _size);
basic_string tmp;
tmp.resize(_size);
if (pos + n >= _size || n == npos) {
strcpy(tmp._str, _str + pos);
_size -= pos + 1;
}
else {
strcpy(tmp._str, _str + pos);
*(tmp._str + n) = '\0';
_size = n;
}
cout << _size << endl;
return tmp;
}
2.6 成员常量
接口名称 | 接口作用 |
---|---|
npos | static const size_t npos = -1; |
接口的模拟实现:
- npos
static const size_t npos = -1;
2.7 非成员函数重载
接口名称 | 接口作用 |
---|---|
operator+ | 返回一个2个字符串相加的新构造的字符串对象 |
operator<< | 输出所有有效字符 |
operator>> | 输入,遇到空格或换行终止 |
getline() | 输入,遇到换行终止 |
接口的模拟实现:
- operator+
//operator+
basic_string operator+(const basic_string& str) {
basic_string ret(*this);
ret += str._str;
return ret;
}
basic_string operator+(const T* s) {
basic_string ret(*this);
ret += s;
return ret;
}
- operator<<
ostream& operator<<(ostream& out, const basic_string<char>& str) {
for (size_t i = 0; i < str.size(); i++) {
out << str[i];
}
return out;
}
- operator>>
istream& operator>>(istream& in, basic_string<char>& str) {
str.clear();
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
str += ch;
ch = in.get();
}
return in;
}
- getline()
istream& getline(istream& in, basic_string<char>& str) {
str.clear();
char ch;
ch = in.get();
while (ch != '\n') {
str += ch;
ch = in.get();
}
return in;
}
2.8 string类对象比较运算符的重载
接口名称 | 接口作用 |
---|---|
< | 小于 |
== | 等于 |
!= | 不等于 |
> | 大于 |
>= | 大于等于 |
<= | 小于等于 |
接口模拟实现:
- <
bool operator<(const basic_string& str) {
size_t end = 0;
while (end < _size && end < str._size) {
if (_str[end] > str._str[end]) {
return false;
}
else if(_str[end] < str._str[end]){
return true;
}
end++;
}
if (end == str._size) {
return false;
}
else {
return true;
}
}
- ==
bool operator==(const basic_string& str) {
if (_size != str._size) {
return false;
}
size_t end = 0;
while (end < _size && end < str._size) {
if (_str[end] != str._str[end]) {
return false;
}
end++;
}
return true;
}
- !=
bool operator!=(const basic_string& str) {
return !(*this == str);
}
- >
bool operator>(const basic_string& str) {
return !((*this < str) || (*this == str));
}
- >=
bool operator>=(const basic_string& str) {
return !(*this < str);
}
- <=
bool operator<=(const basic_string& str) {
return !(*this > str);
}
2.9 string类对象的三种遍历方式
void Teststring()
{
string s("hello world");
// 3种遍历方式:
// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
// 另外以下三种方式对于string而言,第一种使用最多
// 1. for+operator[]
for(size_t i = 0; i < s.size(); ++i)
cout<<s[i]<<endl;
// 2.迭代器
string::iterator it = s.begin();
while(it != s.end())
{
cout<<*it<<endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while(rit != s.rend())
{
cout<<*rit<<endl;
rit++;
}
// 3.范围for
for(auto ch : s)
cout << ch << endl;
}
3.如何熟悉接口——刷题
刷题必是初学者掌握STL的最佳方法。以题代学,事半功倍。奉上若干string的习题牛客网:
leetcode:
4.还想说的话
-
模拟实现stl是个无聊、耗时的过程,但是能够帮助我们很深刻地理解
指针
、数据结构
、面向对象编程
以及逻辑思维能力
、代码能力
-
《 STL源码剖析》------侯捷,打算读一读