admin管理员组文章数量:1611972
文章目录
- string类简介
- string类成员变量
- string类构造与析构
- 构造函数
- 拷贝构造
- 赋值重载
- 析构函数
- string类迭代器
- string类容量操作
- reserve函数
- resize函数
- string类插入
- push_back
- append
- +=
- insert
- string类删除
- string类查找
- []运算符重载
- 流插入<<和流提取>>运算符重载
- 其他函数
string类简介
在C语言中,C标准库为了操作字符串(以’\0’结尾的一些字符的集合)更加方便,提供了一些str系列的库函数。
在C++中,则提供了更加高效与方便的String类,类成员函数更加全面,可以帮助我们对字符串进行增删查改等一系列操作。
在使用string类时,必须包含#include 以及using namespace std;
string类成员变量
模拟实现的string类主要包含三个成员变量:
- 字符串首地址
- 字符串有效字符个数
- 字符串容量
因为字符串在内存地址空间是连续的,只需要首地址+下标便可以找到每个位置上的值;
字符串有效字符个数表示当前字符数组存储的字符个数(不包括‘\0’)
字符串容量表示为字符数组开辟的空间(不包括‘\0’)
private:
char* _str;
size_t _size;
size_t _capacity;
string类构造与析构
构造函数
库函数
库函数中构造函数重点需要掌握的有三个:
函数名称 | 功能说明 |
---|---|
string() | 默认构造 |
string(const char* str) | 字符串构造 |
string(size_t n, char ch | n个字符构造 |
默认构造:
string s1;
字符串构造:
string s2("sherry");
n个字符构造:
string s3(10, 'x');
模拟实现
构造一个全缺省的构造函数,当实例化对象时,不传参则构造出空字符串;传参时,构造出指定字符类。初始化有效字符个数_size
和容量_capacity
为字符串长度(不包括’\0’),开辟一个_capacity+1的空间保存字符串,并将开辟的地址拷贝给字符串首地址_str
。
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
构造一个n个字符的构造函数,通过memset函数按字节把内存空间设置为ch,最后还需在_size位置设置字符串结束标志’\0’。
string(size_t n,char ch)
:_size(n)
, _capacity(_size)
{
_str = new char[_capacity + 1];
memset(_str, ch, _size);
_str[_size] = '\0';
}
拷贝构造
库函数
当用已存在string类初始化一个正在创建的string类,调用拷贝构造函数。
string s1("hello world");
string s2(s1);
模拟实现
如果string类调用默认的拷贝构造函数,只会进行浅拷贝,s1和s2会指向同一块地址空间,如果不调用析构函数,并不会出现问题,但只要调用析构函数,同一块内存空间被析构两次,程序崩溃。
浅拷贝也称位拷贝,只是讲对象的值拷贝过来,会导致多个对象共享一份资源,当一个对象销毁时,该资源就会被销毁掉,其他对象再调用这份资源时,就会发生访问越界的问题。
为了解决此类问题,我们需要显示给出构造函数,进行深拷贝。深拷贝是给每个对象分配独立的资源,保证每个对象之间不会因共享资源而造成多次释放引起的程序崩溃。
- 传统写法
// 拷贝构造(深拷贝)
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
- 现代写法
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
//std::swap(_str, tmp._str);
//std::swap(_size, tmp._size);
//std::swap(_capacity, tmp._capacity);
swap(tmp);
}
现代写法调用构造函数,创建了一个临时对象tmp
,交换tmp
和*this
即可。
赋值重载
库函数
string s1("hello world");
string s3("sherry");
s1 = s3;
s3 = s3;
模拟实现
- 传统写法
// 传统写法
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
- 现代写法
string& operator=(const string& s)
{
if (this != &s)
{
//string tmp(s); // 调用拷贝构造
string tmp(s._str); // 调用构造函数
//std::swap(_str, tmp._str);
//std::swap(_size, tmp._size);
//std::swap(_capacity, tmp._capacity);
swap(tmp);
}
return *this;
}
在现代写法的基础上进一步改进,在传参的时候不传引用,便会调用拷贝构造初始化形参s
,将s
和*this
进行交换即可,出了作用域,形参s自动调用析构函数进行销毁。
string& operator=(string s)
{
//std::swap(_str, s._str);
//std::swap(_size, tmp._size);
//std::swap(_capacity, tmp._capacity);
swap(s);
return *this;
}
析构函数
析构函数只需要把申请的空间销毁即可
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
string类迭代器
迭代器按功能分为:
- iterator
- const_iterator
- reverse_iterator
- const_reverse_iterator
typedef char* iterator;
typedef const char* const_iterator;
typedef reverse_iterator<const_iterator, const char&, const char*>const_reverse_iterator;
typedef reverse_iterator<iterator, char&, char*> reverse_iterator;
反向迭代器库函数实现:
int main()
{
string s1("sherry");
string::iterator it = s1.begin();
while (it != s1.end())
{
*it -= 1;
++it;
}
cout << endl;
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;
}
模拟实现
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
string类容量操作
string类的容量改变使用reserve和resize函数。
reserve函数
reserve函数是为string预留n个字节空间,不改变有效元素(_size)的个数。
注意:当reserve的参数小于string底层空间大小时,reserve不会改变容量(_capacity)大小。
模拟实现
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
resize函数
std库中的resize函数有两种方式,第一种是将字符串中有效字符个数改变到n个,用0来填充多余的元素空间;第二种是用字符c来填充多余的元素空间。
注意:如果改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小(_capacity)是不变的。
模拟实现
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, ch, n - _size);
_str[n] = '\0';
_size = n;
}
}
string类插入
push_back
pusk_back尾插函数,每次向string类字符串尾插一个字符。
string s1;
s1.push_back('x');
s1.push_back('y');
s1.push_back('z');
模拟实现
void push_back(char ch)
{
if (_size == _capacity)
{
// 增容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
//insert(_size, ch);
}
append
append函数可以在string类字符串后增加字符串。
string s1("hello");
s1.append(" world");
模拟实现
void append(const char* str)
{
// 增容
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
//insert(_size, str);
}
+=
string中重载了+= 运算符,因为+=更加人性化。
+=函数服用了push_back和append函数。
string s1;
s1 += 'x';
s1 += " ";
s1 += "yyy";
模拟实现
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
insert
string s1("hello");
s1.insert(0, 1, ' ');
s1.insert(6, "world");
模拟实现
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while(end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = len + _size;
while(end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string类删除
第一种erase()函数有两个参数,第一个是删除位置,第二个是删除长度,缺省值代表将后面的字符串全部删除;第二种erase()函数的参数是迭代器,可以传入迭代器,删除当前迭代器位置的字符;
string s2("hello world");
s2.erase(0, 1);
s2.erase(s2.begin());
删除分为两种情况,第二种删除当前位置及其之后的所有字符,这时只需要把pos位置置为’\0’即可;第二种删除某一段字符,这时候涉及到字符的挪动。
模拟实现
string& erase(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
string类查找
string类的查找函数find,可以查找一个字符,也可以在指定位置开始查找字符串。
size_t find(char ch);
size_t find(const char* str, size_t pos = 0)
模拟实现
size_t find(char ch)
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
[]运算符重载
string类最方便的在于可以随机访问,而可以想数组一样随机访问字符串中的任意位置,是因为重载了[]运算符。
模拟实现
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类的流插入和流提取,我们在string类外定义流插入和流提取函数。如果在类内实现,类的成员函数第一个参数默认是this,就只能实现成s1 >> cin(s1 << cout),不符合我们的使用习惯。
// 流插入流提取
ostream& operator<<(ostream& out,const string& s)
{
//for (auto ch : s)
//{
// out << ch;
//}
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
// 不能使用out << s.c_str();
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != '\n' && ch != ' ')
{
s += ch;
ch = in.get();
}
return in;
}
其他函数
// 类成员函数
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
// 类外的函数
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(),s2.c_str()) == 0;
}
bool operator<=(const string& s1, const string& s2)
{
return (s1 < s2) || (s1 == s2);
}
bool operator>(const string& s1, const string& s2)
{
return !((s1 < s2) || (s1 == s2));
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
版权声明:本文标题:C++基础篇之STL库(一)——string的使用及其模拟实现 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1728622370a1166515.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论