从构造,析构以及拷贝构造的方面理解string类的底层实现。
源代码获取:
https://github.com/akh5/C-/blob/master/STL/Mystring.cpp
string的底层通过字符指针char*通过构造函数申请对应大小的空间,并将指针指向其空间。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
namespace my
{
class string
{
public:
string(char* str = "")
{
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str)+1]; //“\0”
strcpy(_str, str);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
}
构造时需要先给一个默认为空的字符粗,对应string str没=没有初始化的情况。构造函数中,需要人为申请一段比str多1的空间,可以放置str中的内容和一个’\0’。将_str指向申请的空间,用strcpy函数将str的内容赋值给_str。
析构函数将开始申请的空间释放掉,并将_str指向空指针就完成析构
拷贝构造
深拷贝和浅拷贝概念的引入:https://blog.csdn.net/MPF1230/article/details/104023364
浅拷贝的实现相对简单,编译器默认的拷贝构造也是浅拷贝,但是浅拷贝的危害是,拷贝的对象结束调用析构函数时,会导致空间的二次释放
namespace my
{
class string
{
public:
string(char* str = "")
{
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str)+1]; //“\0”
strcpy(_str, str);
}
//浅拷贝
string(const string& s)
:_str(s._str)
{ }
string &operator=(const string& s)
{
_str = s._str;
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
}
浅拷贝的拷贝构造,只需要将当前_str的指针指向需要拷贝的s._str的指针。
对应的=运算符的重载,也是一样将当前_str的指针指向需要拷贝的s._str的指针,并返回this指针。
深拷贝
深拷贝大致分为4个步骤
- 申请新空间
- 在新空间中拷贝需要的内容
- 将指针指向新空间
- 释放旧空间
namespace my
{
class string
{
public:
string(char* str = "")
{
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str)+1]; //“\0”
strcpy(_str, str);
}
//深拷贝
string(const string& s)
:_str(new char[strlen(s._str)])
{
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
char* temp = new char[strlen(s._str)];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
}
通过swap函数改进深拷贝
string(const string& s)
:_str(nullptr)
{
string strtemp(s._str);
swap(_str, strtemp._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
string strtemp(s);
swap(_str, strtemp);
}
}
先将原指针置为空,编译器就会自动释放空的旧空间,并且在构造临时空间时,不能直接构造s,会导致无线递归构造,最后在用swap函数与临时空间交换。
改进浅拷贝多次释放同一资源的问题
最简单的方法就是在类总添加一个,当前资源使用情况的计数器,每当有指向当前资源的对象时,计数器加一,有对象释放资源时,计数器减一。只有当最后一个释放资源时,才能将资源释放掉。
class string
{
public:
string(char* str = "")
:_pCount(new int(1))//初始化计数
{
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str)+1]; //“\0”
strcpy(_str, str);
}
//浅拷贝
string(const string& s)
:_str(s._str)
,_pCount(s._pCount) //拷贝资源的同时,也拷贝计数
{
++_count;
}
//在拷贝前,当前对象可能有旧资源,所以需要先将旧资源释放,指向新资源
string &operator=(const string& s)
{
if (this != &s)
{
//需要将当前对象的旧资源释放掉
if (0 == --*_pCount)
{
delete[] _str;
delete _pCount;
}
_str = s._str;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
~string()
{
if (_str && --*_pCount==0 )
{
delete[] _str;
_str = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
private:
char* _str;
int* _pCount;
};
需要新引入一个指向计数器的指针,每一个对象指向同一资源的同时,同样也指向同一个计数器,每当发生拷贝构造时 _pCount加1,释放时_pCount减1.只有当_pCount等于0时,才能释放该资源。
最后是[]重载,可以访问固定位置的内容
[]运算符重载,访问某一位置元素
char& operator[](size_t index)
{
return _str[index];
}