目录
string类各函数接口总览
namespace russ
{
//模拟实现string类
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//默认成员函数
string(const char* str = ""); //构造函数
string(const string& s); //拷贝构造函数
string& operator=(const string& s); //赋值运算符重载函数
~string(); //析构函数
//迭代器相关函数
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
//容量和大小相关函数
size_t size();
size_t capacity();
void reserve(size_t n);
void resize(size_t n, char ch = '\0');
bool empty()const;
//修改字符串相关函数
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
string& insert(size_t pos, char ch);
string& insert(size_t pos, const char* str);
string& erase(size_t pos, size_t len);
void clear();
void swap(string& s);
const char* c_str()const;
//访问字符串相关函数
char& operator[](size_t i);
const char& operator[](size_t i)const;
size_t find(char ch, size_t pos = 0)const;
size_t find(const char* str, size_t pos = 0)const;
size_t rfind(char ch, size_t pos = npos)const;
size_t rfind(const char* str, size_t pos = 0)const;
//关系运算符重载函数
bool operator>(const string& s)const;
bool operator>=(const string& s)const;
bool operator<(const string& s)const;
bool operator<=(const string& s)const;
bool operator==(const string& s)const;
bool operator!=(const string& s)const;
private:
char* _str; //存储字符串
size_t _size; //记录字符串当前的有效长度
size_t _capacity; //记录字符串当前的容量
static const size_t npos; //静态成员变量(整型最大值)
};
const size_t string::npos = -1;
//<<和>>运算符重载函数
istream& operator>>(istream& in, string& s);
ostream& operator<<(ostream& out, const string& s);
istream& getline(istream& in, string& s);
}
注意:为了防止与标准库当中的string类产生命名冲突,模拟实现时需放在自己的命名空间当中。
默认成员函数
构造函数
构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\0’)
//构造函数
string(const char* str = "")
{
_size = strlen(str); //初始时,字符串大小设置为字符串长度
_capacity = _size; //初始时,字符串容量设置为字符串长度
_str = new char[_capacity + 1]; //为存储字符串开辟空间(多开一个用于存放'\0')
strcpy(_str, str); //将C字符串拷贝到已开好的空间
}
拷贝构造函数
模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝:
浅拷贝(Shallow Copy):
当执行浅拷贝时,如果对象中包含指针变量,新旧对象将共享同一个指针所指向的内存空间。这意味着,如果其中一个对象修改了通过该指针所指向的数据,这种改变对于另一个对象也是可见的,因为他们实际上指向的是同一块内存。这对于只包含基本数据类型(如int、float、char等)的对象没有问题,但对于包含指针(指向动态分配的内存或其他复杂数据结构)的对象,则可能导致意料之外的行为。
深拷贝(Deep Copy):
深拷贝不仅会复制对象本身,还会递归地复制对象中指针所指向的所有内容,为源对象中指针所指向的每个对象分配新的内存空间。这样,即使在拷贝之后修改其中一个对象的数据,也不会影响到另一个对象。它们拥有各自独立的数据副本,确保了对象间的完全独立性。
下面提供深拷贝的两种写法:
传统写法:
步骤概述:
分配内存:在拷贝构造函数或赋值运算符中,首先为新对象(即拷贝对象)动态分配足够的内存来容纳源对象字符串的内容。这是通过
new
操作符完成的,例如:_str = new char[sourceSize]
,其中sourceSize
包括源字符串的长度加上结束符\0
。复制内容:接下来,逐字符地将源对象中的字符串内容复制到新分配的内存空间中。这可以通过循环或使用诸如
strcpy
之类的库函数来实现。例如:strcpy(_str, source->_str)
。复制其他成员变量:除了字符串内容外,还需要将源对象的其他成员变量(如
_size
和_capacity
)复制到新对象中,保证新对象的这些状态信息与源对象一致。处理源对象的特殊状况:在某些情况下(尤其是在赋值运算符重载中),可能还需要考虑源对象自身可能需要释放原有的内存,以防止内存泄漏。
//传统写法
string(const string& s)
:_str(new char[s._capacity + 1])
, _size(0)
, _capacity(0)
{
strcpy(_str, s._str); //将s._str拷贝一份到_str
_size = s._size; //_size赋值
_capacity = s._capacity; //_capacity赋值
}
现代写法:
步骤概述:
构造临时对象(tmp): 在构造函数内部,首先根据传入的C字符串参数,创建一个局部临时的
string
对象(tmp
)。这里,我们会利用现有的构造函数(可能是接受C字符串参数的构造函数),确保深拷贝字符串内容。交换数据: 然后,设计一个
swap
成员函数来交换两个string
对象的数据成员(_str
、_size
、_capacity
等)。在构造函数内部调用这个swap
函数,将临时对象tmp
的数据与正在构造的对象的数据交换。临时对象自动销毁: 构造完成后,局部临时对象
tmp
会自动销毁,由于数据已被交换到新对象中,因此不会导致资源泄露。
//现代写法
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象
swap(tmp); //交换这两个对象
}
赋值运算符重载函数
与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝。
下面也提供深拷贝的两种写法:
传统写法:
步骤概述:
- 自我赋值检测:首先检查是否是自我赋值情况(
this == &s
),如果是,则直接返回*this
,避免无用操作。- 释放资源:释放当前对象(左值)已有的动态分配资源,比如释放
_str
指向的内存。- 资源分配:根据源对象(右值,即赋值操作数)的大小,重新为左值对象分配必要的内存空间。
- 深拷贝内容:将源对象的字符串内容及其他数据成员逐个复制到新分配的内存中。
- 完成:返回左值引用(
*this
),允许链式赋值操作。
//传统写法
string& operator=(const string& s)
{
if (this != &s) //防止自己给自己赋值
{
delete[] _str; //将原来_str指向的空间释放
_str = new char[s._capacity + 1]; //重新申请一块空间
strcpy(_str, s._str); //将s._str拷贝一份到_str
_size = s._size; //_size赋值
_capacity = s._capacity; //_capacity赋值
}
return *this; //返回左值(支持连续赋值)
}
现代写法:
步骤概述: