C++:使用类模拟 string 柔性数组 运算符重载 写实拷贝

前言

如果在模拟string类时提出这种问题:若多个对象指向同一个字符串,那么会占用很多空间,如何解决?
解决方法之一就是设计一个包含柔性数组和引用计数的结构体

目录

柔性数组结构体设计

设计一个结构体来模拟string

	struct StrNode
	{
		int ref;	 //引用计数
		int len;	 //字符串长度
		int size;	 //空间大小
		char data[]; //柔性数组
	};

类的设计

私有成员

  1. 因为要设计类,所以将柔性数组结构体(StrNode)也设计在类中,保证封装特性。

  2. 定义一个StrNode的指针作为私有成员,旨在构造对象时使用。

    class String
    {
    private:
    struct StrNode
    {
    int ref; //引用计数
    int len; //字符串长度
    int size; //空间大小
    char data[]; //柔性数组
    };

    StrNode* pstr;
    

    }

构造函数

	String(const char* p = NULL) : pstr(NULL)
	{
		if (p != NULL)
		{
			int len = strlen(p);
			pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
			pstr->ref = 1;
			pstr->len = len;
			pstr->size = len * 2;
			strcpy(pstr->data, p);
		}
	}

构造对象示例:

int main(void)
{
	String s1("hello");
	return 0;
}

内存简图:
在这里插入图片描述

在这里插入图片描述

析构函数

	~String()
	{
		if (pstr != NULL && --pstr->ref == 0)
		{
			free(pstr);
		}
		pstr = NULL;
	}
  1. 首先要判断pstr是否为NULL,不为NULL时再对其引用计数 ref -= 1
  2. 若减完 ref为0,说明再没有对象指向该字符串,需要将其空间释放。
  3. 释放完成后将pstr 置为NULL。

在这里插入图片描述

拷贝构造函数

由于设计时有引用计数这个概念,所以拷贝构造时候,在判断源对象pstr不为NULL前提下,只需将

  1. pstr指针指向同一空间后

  2. 引用计数加一即可

    String(const String& s) : pstr(NULL)
    {
    	if (s.pstr != NULL)
    	{
    		pstr = s.pstr;
    		pstr->ref += 1;
    	}
    }
    

示例:

int main(void)
{
	String s1("hello");
	String s2(s1);
	String s3(s2);

	return 0;
}

运行示例:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

赋值运算符重载

赋值运算符流程

  1. 待赋值对象是否和源对象是同一个对象,若不是则

  2. 如果待赋值对象不为空且只有他一个对象指向堆空间,则释放该空间,若对象本身为空,不执行

  3. 将源对象赋值给待赋值对象

  4. 如果源对象不为空,那么赋值过后的待赋值对象也指向堆空间,则需要将引用计数加一

  5. 返回this指针

    // 赋值构造
    String& operator =(const String& s)
    {
    	if (this != &s)
    	{
    		if (pstr != NULL && --pstr->ref == 0)
    		{
    			free(pstr);
    		}
    		pstr = s.pstr;
    		if (pstr != NULL)
    		{
    			pstr->ref += 1;
    		}
    	}
    	return *this;
    }
    

示例:

int main(void)
{
	String s1("hello");
	String s2(s1);

	String s3("world");
	s3 = s2;

	return 0;
}

在没有赋值前,对象结构为:
在这里插入图片描述

赋值后,s3也指向该堆空间,将引用计数加一
在这里插入图片描述

重载[]运算符1

我们希望通过 []运算符来改变对象的字符串的某个元素,但如果是这种情况:

int main(void)
{
	String s1("hello");
	String s2(s1);

	s1[1] = 'a';
	cout << s1 << endl;

	return 0;
}

s1和s2指向同一个字符串,那么如果改变 s1,必然会改变 s2的,为了解决这种问题,需要利用写实拷贝

写实拷贝

考虑条件:

  1. 边界条件,如果为空,程序退出。如果下标不在范围内,程序终止。

  2. 判断引用计数是否大于1,如果不大于,那么可以直接返回该下标元素,如果大于1,说明还有其他对象也指向该字符串,需要拷贝。

  3. 重新申请堆区空间,将源空间内容拷贝进去

  4. 将新空间的引用计数加一,源空间引用计数减一,最后将新空间赋给该对象的指针pstr。

    char& operator [](const int index)
    {
    	if (pstr == NULL) exit(1);
    	assert(index >= 0 && index <= pstr->len - 1);
    	if (pstr->ref > 1)
    	{
    		int total = sizeof(StrNode) + pstr->size + 1;
    		StrNode* newNode = (StrNode*)malloc(total);
    		memmove(newNode, pstr, total);
    		newNode->ref = 1;
    		pstr->ref -= 1;
    		pstr = newNode;
    	}
    
    	return pstr->data[index];
    }
    

示例:

在这里插入图片描述

重载[]运算符2

如果不希望这个运算符是双向的作用:即即可利用它取值,又可对其赋值,那么就不需要返回一个引用,也不用写实拷贝。

	char operator [](const int index)const
	{
		assert(index >= 0 && index <= pstr->size - 1);

		return pstr->data[index];
	}

修改某个元素 modify

当我们考虑完需求,发现既然想通过[]运算符来修改某个值,那写实拷贝也没有存在的必要,直接写一个修改函数即可。代码和写实拷贝相同。

bool modify(const int index, const char ch)	{
	assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);

	if (pstr->ref > 1)	{
		int total = sizeof(StrNode) + pstr->size + 1;
		StrNode* newNode = (StrNode*)malloc(total);
		memmove(newNode, pstr, total);
		newNode->ref = 1;
		pstr->ref -= 1;
		pstr = newNode;		
	}
	pstr->data[index] = ch;
	return true;
}

移动构造与移动赋值函数

  1. 移动构造,就是将自己的资源进行转移,转移到要构造的对象里。

  2. 移动赋值,将自己的资源赋值给目标对象,自身置为NULL。

    // 移动构造
    String(String&& s) : pstr(NULL)
    {
    pstr = s.pstr;
    s.pstr = NULL;
    }

    // 移动赋值
    String& operator =(String&& s)
    {
    if (this == &s) return *this;
    if (pstr != NULL && --pstr->ref == 0)
    {
    free(pstr);
    }
    pstr = s.pstr;
    s.pstr = NULL;
    return *this;
    }

示例:

String fun()
{
	String s2("hello");
	return s2;
}
int main(void)
{
	String s1;
	s1 = fun();

	return 0;
}

在fun()函数对 s2构造时:

在这里插入图片描述

在执行到返回s2这条语句时,将移动构造一个将亡值,再将 s2本身置为NULL。
在这里插入图片描述

在返回主函数执行移动赋值,用刚才的将亡值赋值给s1,图示中的this指针就是 s1,对 s1进行了移动赋值。
在这里插入图片描述

重载(+)运算符

要完成这个函数,需要考虑这些情况:

  1. 如果两个对象都为空,则直接返回空(String())。

  2. 如果this指针不为空,被调用对象为空,则返回this指针

  3. 如果this指针为空,被调用对象不为空,返回该对象。

  4. 如果上述情况都不满足,则进行编写。

  5. 在最后返回时需要调用构造函数,为了防止调用最初所写的构造,所以重载一个特殊的构造函数。

    // operator+
    String operator+(const String& s)const
    {
    	if (pstr == NULL && s.pstr == NULL)
    	{
    		return String();
    	}
    	else if (pstr != NULL && s.pstr == NULL)
    	{
    		return *this;
    	}
    	else if (pstr == NULL && s.pstr != NULL)
    	{
    		return s;
    	}
    	else
    	{
    		int total = (pstr->len + s.pstr->len) * 2;
    		StrNode* newNode = (StrNode*)malloc(total + 1);
    		strcpy(newNode->data, pstr->data);
    		strcat(newNode->data, s.pstr->data);
    		newNode->ref = 1;
    		newNode->len = pstr->len + s.pstr->len;
    		newNode->size = total;
    		return String(newNode);
    	}
    }
    

    // 特殊的构造函数
    private:
    String(StrNode* p) : pstr§ {}

重载(+=)运算符

考虑这些情况:
当两个对象都不为空时:

  1. 如果this指针所指对象的引用计数大于1,则开辟新空间

  2. 如果引用计数等于1,那么如果size小于两个字符串长度之和,就对其扩容,否则直接拼接。

  3. 如果this指针对象为空,另一个对象不为空,则直接赋值给this指针所指对象,引用计数加一。

  4. 不满足上述条件,那么什么都不做,直接返回this

    // operator+=()
    String& operator +=(const String& s)
    {
    	if (pstr != NULL && s.pstr != NULL)
    	{
    		if (pstr->ref > 1)
    		{
    			int total = pstr->len + s.pstr->len;
    			pstr->ref -= 1;
    			char* tmp = pstr->data; //
    			pstr = (StrNode*)malloc(sizeof(StrNode) + total * 2 + 1);
    			strcpy(pstr->data, tmp);
    			strcat(pstr->data, s.pstr->data);
    			pstr->ref = 1;
    			pstr->len = total;
    			pstr->size = total * 2;
    		}
    		else
    		{
    			int total = pstr->len + s.pstr->len;
    			if (pstr->size < total)
    			{
    				pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1);
    				pstr->size = total * 2;
    			}
    			strcat(pstr->data, s.pstr->data);
    			pstr->len = total;
    		}
    	}
    	else if (this->pstr == NULL && s.pstr != NULL)
    	{
    		pstr = s.pstr;
    		pstr->ref += 1;
    	}
    	return *this;
    }
    

示例:

int main(void)
{
	String s1("Hello");
	String s2("world");

	s1 += s2;
	return 0;
}

第一种情况,两个数据相加没超过源对象的size ,直接拼接即可
在这里插入图片描述

第二种情况,加等时数据超过size:

int main(void)
{
	String s1("Hello");
	String s2("world2022_3_12");

	s1 += s2;
	return 0;
}

执行完毕后, s1的size扩容到 38了。
在这里插入图片描述

end源代码

class String
{
private:
	struct StrNode
	{
		int ref;	 //引用计数
		int len;	 //字符串长度
		int size;	 //空间大小
		char data[]; //柔性数组
	};
	StrNode* pstr;
	String(StrNode* p) : pstr(p) {}
public:
	String(const char* p = NULL) : pstr(NULL)
	{
		if (p != NULL)
		{
			int len = strlen(p);
			pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
			pstr->ref = 1;
			pstr->len = len;
			pstr->size = len * 2;
			strcpy(pstr->data, p);
		}
	}
	~String()
	{
		if (pstr != NULL && --pstr->ref == 0)
		{
			free(pstr);
		}
		pstr = NULL;
	}

	// 拷贝构造
	String(const String& s) : pstr(NULL)
	{
		if (s.pstr != NULL)
		{
			pstr = s.pstr;
			pstr->ref += 1;
		}
	}
	
	// 赋值构造
	String& operator =(const String& s)
	{
		if (this != &s)
		{
			if (pstr != NULL && --pstr->ref == 0)
			{
				free(pstr);
			}
			pstr = s.pstr;
			if (pstr != NULL)
			{
				pstr->ref += 1;
			}
		}
		return *this;
	}

	//写实拷贝
	char& operator [](const int index)
	{
		if (pstr == NULL) exit(1);
		assert(index >= 0 && index <= pstr->len - 1);
		if (pstr->ref > 1)
		{
			int total = sizeof(StrNode) + pstr->size + 1;
			StrNode* newNode = (StrNode*)malloc(total);
			memmove(newNode, pstr, total);
			newNode->ref = 1;
			pstr->ref -= 1;
			pstr = newNode;
		}

		return pstr->data[index];
	}

	char operator [](const int index)const
	{
		assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);

		return pstr->data[index];
	}

	// 修改
	bool modify(const int index, const char ch)
	{
		assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);

		if (pstr->ref > 1)
		{
			int total = sizeof(StrNode) + pstr->size + 1;
			StrNode* newNode = (StrNode*)malloc(total);
			memmove(newNode, pstr, total);
			newNode->ref = 1;
			pstr->ref -= 1;
			pstr = newNode;
		}
		pstr->data[index] = ch;
		return true;
	}

	// 移动构造
	String(String&& s) : pstr(NULL)
	{
		pstr = s.pstr;
		s.pstr = NULL;
	}
	// 移动赋值
	String& operator =(String&& s)
	{
		if (this == &s) return *this;
		if (pstr != NULL && --pstr->ref == 0)
		{
			free(pstr);
		}
		pstr = s.pstr;
		s.pstr = NULL;
		return *this;
	}

	// operator+
	String operator+(const String& s)const
	{
		if (pstr == NULL && s.pstr == NULL)
		{
			return String();
		}
		else if (pstr != NULL && s.pstr == NULL)
		{
			return *this;
		}
		else if (pstr == NULL && s.pstr != NULL)
		{
			return s;
		}
		else
		{
			int total = (pstr->len + s.pstr->len) * 2;
			StrNode* newNode = (StrNode*)malloc(total + 1);
			strcpy(newNode->data, pstr->data);
			strcat(newNode->data, s.pstr->data);
			newNode->ref = 1;
			newNode->len = pstr->len + s.pstr->len;
			newNode->size = total;
			return String(newNode);
		}
	}

	// operator+=()
	String& operator +=(const String& s)
	{
		if (pstr != NULL && s.pstr != NULL)
		{
			if (pstr->ref > 1)
			{
				int total = pstr->len + s.pstr->len;
				pstr->ref -= 1;
				char* tmp = pstr->data; //
				pstr = (StrNode*)malloc(sizeof(StrNode) + total * 2 + 1);
				strcpy(pstr->data, tmp);
				strcat(pstr->data, s.pstr->data);
				pstr->ref = 1;
				pstr->len = total;
				pstr->size = total * 2;
			}
			else
			{
				int total = pstr->len + s.pstr->len;
				if (pstr->size < total)
				{
					pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1);
					pstr->size = total * 2;
				}
				strcat(pstr->data, s.pstr->data);
				pstr->len = total;
			}
		}
		else if (this->pstr == NULL && s.pstr != NULL)
		{
			pstr = s.pstr;
			pstr->ref += 1;
		}
		return *this;
	}
	// 输出运算符
	ostream& operator <<(ostream& out) const
	{
		if (pstr != NULL)
		{
			out << pstr->data;
		}
		return out;
	}

};
ostream& operator<<(ostream& out, const String& s)
{
	s << out;
	return out;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值