使用柔性数组重写MyString

hello,各位宝子,今天阿崽将使用c++和柔性数组的方式重新去写String类

在开始本次知识前,首先给大家介绍下柔性数组这个buff特点:

结构中的柔性数组成员前面至少要包含一个其他成员 sizeof返回的这种结构大小不包括柔性数组的内存 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以便于适应柔性数组预期的实际大小

感兴趣的宝子可以自己去看看柔性数组详细的内容。

目录

一.MyStirng 结构体设计

二.MyString类

2.1私有属性

2.2构造函数

2.3析构函数

2.4赋值函数

 2.5加法运算符重载(对象)

2.6+=运算符重载

2.7返回某下标元素

2.8改变某下标所对应的元素值


一.MyStirng 结构体设计

struct StrNode
    {
        int ref; // 标识该字符串被几个对象所持有
        int capa;//当前空间大小
        int len;//当前字符串大小
        char data[0]; //柔性数组,存储字符串,因为字符串长度未知所以使用柔型数组的方式
    };

二.MyString类

2.1私有属性

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) {}//该函数在后面加法运算中详细介绍

2.2构造函数

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);
        }
    }

 

结果我们通过调试的方式,监视该过程

 

2.3析构函数

~MyString()
    {
        if (pstr != nullptr && --pstr->ref == 0)
        {
            free(pstr);
        }
        pstr = nullptr;
    }

在调用析构函数前有一个非常重要的点就是:当前字符串调用的对象个数,如果只有一个对象持有该字符串,可以直接调用析构函数,但是如果不为1,就说明有多个对象持有该资源,因为无法知道是否析构其他对象,所以我们只需要将ref这个指标减一,因为这些对象所指空间都是一样的,所以最后统一释放该字符串即可

 

2.4赋值函数

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有两个字符串

 

 2.5加法运算符重载(对象)

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;
}

2.6+=运算符重载

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"这个功能。

2.7返回某下标元素

char& operator[](const int index)const {
		if (index<0 || index>this->pstr->len) {
			exit(EXIT_FAILURE);
		}
		return pstr->data[index];
	}

2.8改变某下标所对应的元素值

	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类的所有方式全部写完再与各位宝子分享。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值