一. string类的构造和析构函数
在进行模拟实现string类的时候,重中之重就是它的构造函数和析构函数,这两个函数就像是地基,首先要构建好我们才能往后继续搭建。第一我们要确认string类的成员有哪些,首先最基本的就是底层的一个char指针:
cpp
private:
char* _str;
然后我们再来实现一下构造函数:
cpp
class MyString
{
public:
MyString(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
cout << "MyString()" << endl;
}
void string_print()
{
cout << _str << endl;
}
~MyString()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
cout << "~MyString()" << endl;
}
private:
char* _str;
};
int main()
{
MyString s1("abcd");
s1.string_print();
MyString s2(s1); //调用默认拷贝构造函数
s2.string_print();
return 0;
}
这里细心的同学可能就发现了,虽然程序是正常的输出,但是到最后还是崩溃了,而且只析构了一次,这里到底出了什么问题?
1.1 浅拷贝和深拷贝
上图就分析了为什么我们的程序会崩溃,原因就是在一个类中会有默认的构造函数和拷贝构造函数,上述的MyString类中没有显式的定义其拷贝构造函数以及赋值运算重载符,这就导致了编译器自己去调用默认的拷贝构造函数,致使s1在拷贝构造s2的时候调用了同一块空间,而同一块空间的多次销毁引起程序的崩溃,这种拷贝方式就称之为浅拷贝。
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
与此对应的就是深拷贝,即每个对象都有单独的一份空间资源,不会于其他对象共处一块资源。在一个类中如果有资源管理的需求,一般来说不管是构造函数还是拷贝构造函数亦或者是赋值运算重载符都需要显式的去定义,而且都是按深拷贝的方式去提供。
1.2 string类的两种写法
cpp
MyString(const MyString& str) //拷贝构造函数
: _str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
MyString& operator=(const MyString& s)
{
if (this != &s)
{
char* Ptemp = new char[strlen(s._str) + 1];
strcpy(Ptemp, s._str);
delete[] _str;
_str = Ptemp;
}
return *this;
}
上面的是比较传统的写法,思路和写法也是比较清楚易懂的。但是现代写法更适用于在实际项目中进行开发, 下面就是更现代的写法:
cpp
MyString(const MyString& s)
: _str(nullptr)
{
MyString strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值那个实现比较好?
MyString& operator=(MyString s)
{
swap(_str, s._str);
return *this;
}
MyString& operator=(const MyString& s)
{
if(this != &s)
{
MyString strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
对于拷贝构造来说先将_str
初始化为nullptr
,通过临时对象strTmp
间接分配内存,再通过swap
转移资源所有权,复用了已有的构造函数的逻辑,增强了代码的使用性,在实际的项目中用的更多。然后再来说两种赋值运算符重载,第一个赋值运算符重载(String& operator=(String s)
)通过值传递参数,利用拷贝 / 移动构造函数自动创建临时对象,再通过swap
交换资源,无需手动检查自赋值,代码更简洁且健壮;第二个版本(String& operator=(const String& s)
)采用引用传递,需显式检查自赋值,且需额外实现移动赋值重载才能支持右值,更适用于性能敏感且频繁处理大型对象,引用传递可避免一次拷贝构造(但需手动实现移动赋值运算符)。
二.string类的模拟实现
在了解完string类中各个函数的作用,我们就来用自己的方法去一个个实现这些函数,下面就是我的实现代码:
cpp
//string.h
using namespace std;
namespace zda
{
class string
{
public:
string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}
string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
using iterator = char*;
using const_iterator = const char*;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
const char* c_str() const
{
return _str;
}
string substr(size_t pos, size_t len=-1); //截取一部分字符
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string & lhs, const string & rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>=(const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
ostream& operator<< (ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
}
cpp
#include "string.h"
namespace zda
{
// s2(s1)
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s2 = s1 = s3
// s1 = s1;
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
void string::reserve(size_t n) //扩容
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str; //释放之前的内存
_str = tmp;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity = 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
}
void string::append(const char* str)
{
size_t len = strlen(str);
size_t newCapacity = 2 * _capacity; //如果二倍不够,则需要多少
if (newCapacity < _size + len)
newCapacity = _size + len;
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)
{
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++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = 2 * _capacity;
if (newcapacity < _size + len)
{
newcapacity = _size + len;
}
reserve(newcapacity);
}
size_t end = _size + len;
while (end>pos+len-1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
if (len >= _size - pos) //全删
{
_str[pos] = '\0';
_size = pos;
}
else //只删除一部分
{
size_t end = pos + len;
while (end<=_size)
{
_str[end-len] = _str[end];
++end;
}
}
}
size_t string::find(char ch, size_t pos = 0) //找一个字符
{
assert(pos < _size);
for (size_t i = 1; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return -1;
}
size_t string::find(const char* str, size_t pos = 0) //找一个字符串
{
assert(pos<_size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return -1;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len) //在pos位置截取len长度的字符串
{
assert(pos < _size);
if (len > (_size - pos))
{
len = _size - pos;
}
zda::string sub;
sub.reserve(len);
for (int i = pos + i; i < len; i++)//从_str的pos位置开始赋值给sub
{
sub+= _str[pos + i];
}
return sub;
}
bool zda::operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool zda::operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool zda::operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool zda::operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool zda::operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool zda::operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
ostream& zda::operator<<(ostream& os, const string& str)
{
//os<<'"';
//os << "xx\"xx";
for (size_t i = 0; i < str.size(); i++)
{
//os << str[i];
os << str[i];
}
return os;
}
istream& zda::operator>>(istream& is, string& str)
{
str.clear();
char ch;
//is >> ch;
ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
}