C++之 String 类的模拟实现
本文只简述string类模拟实现的重点,其余不再过多赘述
一、模拟实现string类的构造函数
本文主要实现下图两个构造函数,即string()和string(const string& str)
而关于string的底层,其实就是数组,在物理逻辑上是连续的空间:
//string.h文件
namespace mxj
{
class string
{
public:
//不带参的构造函数,编译器默认生成的已经满足使用需求
//无参的构造,就是字符串'\0',所以在string的模拟实现里,带参的构造函数包含了无参构造函数
//string();
//带参构造函数
string(const char* str = "");//给缺省值"",
//这样我们的mxj::string s1就不会报错,不然得写成mxj::string s1()
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
带参构造函数如下,以strlen为基础,计算开辟新空间的大小,通过开辟新的内存空间并将str字符串长度的下一个位置赋值'\0'
这样也很巧妙的解决了无参构造函数。
//string.cpp文件
namespace mxj
{
string::string(const char* str)
:_str(new char[strlen(str) + 1])//这里为什么要加1?因为strlen不会计算\0
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
}
二、模拟实现string类的拷贝构造和赋值拷贝
拷贝构造和赋值拷贝可以通过交换函数来实现,拷贝构造和赋值拷贝都是深拷贝!!!
/ s2(s1),拷贝构造
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s3=s2=s1=s,赋值拷贝,
string& string::operator=(const string& s)
{
if (this != &s)
{
//先把原来的空间释放了,如果s1=s,s1原本的空间非常大,s的空间非常小,就非常容易造成空间浪费
delete[] _str;
//为什么要+1,因为还有\0,_size和_capacity都是不计算\0的
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
当然,上面有关拷贝构造和赋值拷贝虽然直观,但是很啰嗦 ,通过swap函数我们也能实现
两者均不改变当前对象的资源,都是通过临时对象进行资源的交换
//string.cpp
namespace mxj
{
string::string(const string& s)
{
string tmp(s._str);//将s中的资源构造对象tmp
swap(tmp);
}
string& string::operator=(string s)
{
swap(s);//临时对象s和*this进行交换资源
return *this;
}
void swap(string& s1, string& s2)
{
s1.swap(s2);//通过库函数中的swap来实现
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
}
三、模拟实现string类的析构函数
析构函数需要清理类对象的资源
string.cpp
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
四、赋值运算符重载[ ]
赋值运算符重载[ ],使得string类有通过下标来访问字符串数据的功能
char& operator[](size_t i)
{
return _str[i];
}
五、获取字符串数据
string类底层是数组,接口直接返回字符串即可
const char* c_str() const
{
return _str;
}
六、获取当前对象元素个数
size_t size() const
{
return _size;
}
七、清理当前string类对象的数据置空
字符串的终止符号为'\0',所以将字符串第一个元素置为字符'\0',字符串有效个数置零即可
void clear()
{
_str[0] = '\0';
_size = 0;
}
八、普通迭代器
迭代器是指针或者是像指针一样的东西,但在string类中,迭代器的底层是通过指针实现的
using iterator = char*;//using类似于typedef的作用
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
九、const修饰的迭代器
using const_iterator = const char*;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
十、string类一些常用接口的实现
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//保留n个位置实际上要保留n+1个,要给'\0'留位置
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;//_capacity是不计算'\0'的
}
}
void string::push_back(char ch)
{
if (_size == _capacity) {
reserve(_capacity = 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
size_t newcapacity = 2 * _capacity;
//如果扩了两倍还是不满足,那就按串的大小来扩
if (len + _size >newcapacity)
{
newcapacity = len + _size;
}
reserve(newcapacity);
}
strcpy(_str+_size, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
//判断pos位置是否合法
assert(pos <= _size);
//判断是否需要扩容
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//pos+1位置往后挪
size_t end = _size;
while (end > pos) {
_str[end] = _str[end-1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
size_t len = strlen(str);
//判断是否需要扩容
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len)
{
newCapacity = _size + len;
}
reserve(newCapacity);
}
//数据往后挪:str的长度为len,pos位置每一个字符都需要向后挪len个,
size_t end = _size+len;
while (end > pos + len-1) {
_str[end] = _str[end-len];
--end;
}
//插入
for (size_t i = 0; i < pos; i++)
{
_str[pos+i] = str[i];
}
//长度更新
_size += len;
}
//删除
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos) {
_str[pos] = '\0';
_size = pos;
}
//挪数据
else {
size_t end = pos + len;
while (end > pos) {
_str[end-len] = _str[end];
++end;
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
for (size_t 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(pos+_str,str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
if (len > (_size - pos)) {
len = _size - pos;
}
mxj::string sub;
sub.reserve(len);
for (size_t i = pos; i < len; i++)
{
sub += _str[pos + i];//尾插
}
return sub;
}
//通过复用
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) > 0;
}
bool operator< (const string& lhs, const string& rhs)
{
return !(lhs >=rhs);
}
bool operator>= (const string& lhs, const string& rhs)
{
return (lhs > rhs)||(lhs==rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return !(lhs > rhs);
}
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
istream& operator>>(istream& is, string& str)
{
str.clear();
char ch;
//is >> ch;
ch = is.get();
while (ch != ' ' && ch != '\n')
//如果想实现getline的效果while( ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}