hello,各位宝子,今天阿崽将使用c++和柔性数组的方式重新去写String类
在开始本次知识前,首先给大家介绍下柔性数组这个buff特点:
结构中的柔性数组成员前面至少要包含一个其他成员 sizeof返回的这种结构大小不包括柔性数组的内存 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以便于适应柔性数组预期的实际大小
感兴趣的宝子可以自己去看看柔性数组详细的内容。
目录
一.MyStirng 结构体设计
二.MyString类
2.1私有属性
2.2构造函数
2.3析构函数
2.4赋值函数
2.5加法运算符重载(对象)
2.6+=运算符重载
2.7返回某下标元素
2.8改变某下标所对应的元素值
struct StrNode
{
int ref; // 标识该字符串被几个对象所持有
int capa;//当前空间大小
int len;//当前字符串大小
char data[0]; //柔性数组,存储字符串,因为字符串长度未知所以使用柔型数组的方式
};
private:
struct StrNode
{
int ref;
int capa;
int len;
char data[0];
};
StrNode* pstr;//用该指针去指向我们构造的结构体,因为指针只要四个字节大小(x86)省空间
StrNode* GetNode(int total)
{
StrNode* s = (StrNode*)malloc(sizeof(StrNode) + sizeof(char) * total);
if (nullptr == s) exit(EXIT_FAILURE);
return s;
}//为该结构体开辟空间
MyString(StrNode* p) :pstr(p) {}//该函数在后面加法运算中详细介绍
MyString(const char* p = nullptr) :pstr(nullptr)
{
if (p != nullptr)
{
int len = strlen(p);//字符串长度,因为后面开辟的空间比字符串本身空间大所以不需要+1
int total = len * 2;
pstr = GetNode(total);
pstr->ref = 1;
pstr->len = len;
pstr->capa = total - 1;
strcpy_s(pstr->data,len+1, p);
}
}
结果我们通过调试的方式,监视该过程
~MyString()
{
if (pstr != nullptr && --pstr->ref == 0)
{
free(pstr);
}
pstr = nullptr;
}
在调用析构函数前有一个非常重要的点就是:当前字符串调用的对象个数,如果只有一个对象持有该字符串,可以直接调用析构函数,但是如果不为1,就说明有多个对象持有该资源,因为无法知道是否析构其他对象,所以我们只需要将ref这个指标减一,因为这些对象所指空间都是一样的,所以最后统一释放该字符串即可
MyString& operator=(const MyString& s) {
if (this != &s) {
if (pstr!=nullptr&&--pstr->ref==0) {
delete[]pstr;
}
pstr = s.pstr;
if (pstr != nullptr) {
this->pstr->ref++;
}
}
return *this;
}
在使用赋值函数时(例:MyString s2;s2=s1;)首先要判断自身给自身赋值这个情况,其次要判断s2这个对象本身原来是否有指向字符串空间,并且指向该字符串的对象只有一个时释放该空间,将新的空间通过移动赋值方式直接赋值,然后指标加一。否则让s1中的ptr指向s2中的ptr。如果s2不为空那么指标加一,表示该s2有两个字符串
MyString operator+(const MyString& s) const{
if (s.pstr == nullptr && this->pstr == nullptr) { return MyString(); }
if (s.pstr == nullptr && this->pstr != nullptr) return *this;
if (s.pstr != nullptr && this->pstr == nullptr) return s;
int len = s.pstr->len + this->pstr->len;
int total = len * 2;
StrNode* n = (StrNode*)malloc(sizeof(StrNode) + sizeof(char) * total);
if (nullptr == n) exit(EXIT_FAILURE);
n->ref = 1;
n->len = len;
n->capa = total - 1;
strcpy_s(n->data,this->pstr->len+1, this->pstr->data);
strcat_s(n->data, len+2, s.pstr->data);
return MyString(n);
}
加法是一个双目运算符,因为该方法是类中的方法,会有一个this指针所以形参这块只需要一个参数,其次我们要判断我们传进来的两个对象是否为空,对其他三种情况(s为空,this为空;s不为空,this为空;s为空,this不为空)依次进行判断。就到了两者都不为空,开辟一个新的空间依次保存这个两个对象所存储的字符串,注意最后的返回,因为n是结构体指针类型,而我们的构造函数没有这种构造方式,所以在私有属性中再添加一种构造方法:MyString(StrNode* p) :pstr(p) {}这样我们的加法才能运行。
会了这个函数,那么如果参数变为一个对象加一个字符串,一个字符串加一个对象都是同理,我们可以将字符串通过构造函数的方式然后加上对象就行(就是这个函数)
MyString operator+(const char* p, const MyString& s) {
return MyString(p) + s;
}
MyString& operator+=(const char* s) {
if (this->pstr != nullptr && s != nullptr) {
if (pstr->ref > 1) {
int total = pstr->len + strlen(s);
int le = pstr->len;
pstr->ref -= 1;
char* tmp = pstr->data;
pstr = GetNode(total);
pstr->ref = 1;
pstr->len = total;
pstr->capa = total * 2;
strcpy_s(pstr->data, le, tmp);
strcat_s(pstr->data, total + 1, s);
}
else {
int total = pstr->len + strlen(s);
if (pstr->capa< total)
{
pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1);
pstr->capa = total * 2;
}
pstr->len = total;
strcat_s(pstr->data,pstr->len+1,s);
}
}
else if (this->pstr == NULL && s == NULL)
{
pstr = NULL;
pstr->ref += 1;
}
return *this;
}
这个函数比较复杂,我们一点点分析。
首先我们需要判断传进来的两个对象的字符串是否都为空,如果都为空格,空加空还是空的,只要把空字符串的ref指标加一。如果都不为空,我们需要先判断加等对象的字符串是不是只有一个对象所持有,有可能是两个对象都指向“hello”字符串,而加等的对象只有一个,如果直接改变,另外一个也就会随之改变。所以我们使用ref这个指标-1,开辟一个新的空间,将原有的字符串拷贝进去,与他相等的对象不会产生混乱。如果只有一个就很简单,直接开辟新的空间,ref--,将相加的结果赋值给新空间,该对象直接指向新空间。
加等后
考虑到加等后将该对象赋值给另外一个对象,为了不重新调用赋值函数,我们使用引用的方式返回。就可以实现s2=s1+="hello"这个功能。
char& operator[](const int index)const {
if (index<0 || index>this->pstr->len) {
exit(EXIT_FAILURE);
}
return pstr->data[index];
}
void revise(const int index, char p) {
if (index<0 || index>this->pstr->len||this->pstr==nullptr) {
exit(EXIT_FAILURE);
}
pstr->data[index] = p;
}
这里我想的比较简单就是先判断下标元素是否正确后,然后直接改变,并没有考虑到原先持有该空间的对象个数,所以感兴趣的宝子们可以自行写下。
今天就更到这里啦,等我把这个MyString类的所有方式全部写完再与各位宝子分享。