【C++】模拟String,柔性数组,运算符重载,写实拷贝

柔性数组结构体设计:

Struct Node 用于模拟String

struct StrNode  //均可以在构造对象时使用
	{
		int  ref;  //引用计数
		int len;    //字符串长度
		int size;   //空间大小
		char data[];   //柔性数组
	};  

类的设计

StrNode作为类的私有成员(注,StrNode属于类型)

使用StrNode类型的指针pstr,作为访问结构体中的成员接口。

class String
{
private:
	struct StrNode  //均可以在构造对象时使用
	{
		int  ref;  //引用计数
		int len;    //字符串长度
		int size;   //空间大小
		char data[];   //柔性数组
	};  
	StrNode* pstr;  //通过str来访问StrNode中的成员

构造函数设计

len:是要传入data[]的字符串长度
sizeof(StrNode):计算的是结构体类型总字节的偏移量。

public:
	//构造函数
	String(const char* p = NULL) :pstr(NULL)
	{
		if (p != NULL)
		{
			int len = strlen(p);
			//pstr申请堆空间存放ref,len,size,data[]
			
			pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
			pstr->ref = 1; //引用计数,表示此时有一个对象
			pstr->len = len; //传给data 的字符串长度
			pstr->size = len * 2;//data中的容量大小
			strcpy_s(pstr->data, len+1, p);
		}
		cout << "create construct " << this << endl;
	}

请添加图片描述

析构函数设计

1.先判断pstr是否为空。ptr中有如下四个成员
请添加图片描述
2.

注意:

(如果ref不为1,说明有一个以上的对象指向pstr,不能对pstr指向的堆空间进行释放,只需要置为空,断开当前对象和pstr的联系,只能等只剩下一个对象,在对空间进行释放。见可以防止内存泄漏)
对对象个数减一,确定没有对象指向该字符串。则把该对象内的ptr中申请的堆空间释放,并置为空。

~String()
{     //首先判断pstr是否为空,不为空,就对ref-1
	
	if (pstr != NULL && --pstr->ref == 0)
	{
		free(pstr);
	}
	//否则pstr的指向均为空,或没有对象
	cout << "destory " << this << endl;
	pstr = NULL;
	
}
};
int main()
{
	String s1{ "baiU" };
	//String s2(s1);
}

拷贝构造函数设计

  1. 先将s1中的ptr置为空
  2. 将s1中ptr指向的地址空间传给s2
  3. 然后s2对应的ref+1
    如下图:
//拷贝构造
	String(const String& s) :pstr(NULL)
	{                       //先将pstr置为空
		if (s.pstr != NULL)
		{
			pstr = s.pstr;
			pstr->ref += 1;
		}
		cout << "copy consruct" << this << endl;
	}

请添加图片描述
所以s1和s2中pstr指向同一空间。

赋值运算符重载

1.首先判断对象s1等于对象s2
2.不相等,则在判断s1中ptr是否为空,不为空则将对象减一,然后将ptr指向的堆空间释放。
3.让s1中的ptr指向s2中的ptr。
4.如果s1ptr不为空了,在对引用计数加一
5.返回this指针。

String& operator=(const String& s)
	{   //先判断this指向的pstr中的data是否和s的字符串中的内容相等
		if (this != &s)
		{
			//再次判断pst是否为空和对象个数
			if (pstr != NULL && --pstr->ref == 0)
			{
				free(pstr);
			}
			pstr = s.pstr;
			if (pstr != NULL)
			{
				pstr->ref += 1;
			}
		}
		cout << "operator =" << this << endl;
		return *this;
	}
int main()
{
	String s1{ "baiU" };
	//String s2(s1);
	String s3("shiqianyu");
	s3 = s1;
}

String s1{ "baiU" };
String s3("shiqianyu");

请添加图片描述

s3 = s1;

请添加图片描述

括号运算符重载

使用原因: 希望可以通过[]运算符来改变对象字符串的某个元素

int main()
{
	String s1{ "baiU" };
	String s2(s1);
	s1[1] = 's';
}

如果有多个对象指向pstr,也就是指向同一个字符串,那么改变s1,就会改变s2的值,这个时候,我们就需要进行写时拷贝

写时拷贝

1.先判断pstr是否为空,如果为空,说明没有申请空间,直接退出。
2.判断输入的下标,是否在下标范围内。
3.判断引用计数是否大于一,如果不大于直接返回下标元素,大于,说明还有其他对象,指向该字符串,需要进行拷贝。
4.重新申请堆空间,将内容拷贝到新的堆区
5.将新空间的引用计数加一,原来空间的引用计数减一,最后将新空间赋给该对象的指针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);
			memcpy(newNode, pstr, total);
			newNode->ref = 1;
			pstr->ref = 1;
			pstr = newNode;
		}
		return pstr->data[index];
		}

只有一个对象指向pstr

return pstr->data[index];
int main()
{
	String s1{ "baiU" };
	s1[1] = 's';
}

请添加图片描述

重载[]运算符2

如果我们不希望这个运算符是双向的,既可以利用它来取值,又可以利用它来赋值,那么就不需要返回一个引用,也不用写实拷贝。

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

		return pstr->data[index];
	}

修改某个下标的元素

int main()
{
	String s1{ "baiU" };
	//String s2(s1);
	//String s3("shiqianyu");
	//s3 = s1;
	//s1[1] = 's';
	s1.modify(0, 'a');
}

当我们只需要数据,而不需要获取,我们直接写一个修改函数就可。
原理和写实拷贝基本相同。

bool modify(const int index,const char ch)
{
	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);
		memcpy(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置为空
			pstr = s.pstr;
			s.pstr =NULL;
		
		cout << "move consruct" << this << endl;
	}
//移动赋值
	String& operator=(String&& s)  //对对象本身进行操作
	{   //先判断this指向的pstr中的data是否和s的字符串中的内容相等
		if (this == &s) return *this;
		
			//再次判断pst是否为空和对象个数
			if (pstr != NULL && --pstr->ref == 0)
			{
				free(pstr);
			}
			pstr = s.pstr;  //让s1的ptr指向s2指向的ptr
			s.pstr = NULL; //让s2断开
		cout << "operator =" << this << endl;
		return *this;
	}
String fun()
{
	String s2("shiqinayu");
	return s2;
}
int main()
{
	String s1{ "baiU" };
	s1 = fun();
}

1.对s1初始化
请添加图片描述
2. 调用构造函数对s2初始化
请添加图片描述
3. return s2,优先调用移动构造函数,构造将亡值对象,让将亡值对象的ptr指向s2的ptr指向的空间,然后将s2中的ptr置为空。
请添加图片描述
4. 析构s2
请添加图片描述

  1. 接下来返回到调用点出,调用移动赋值,直接将s2的堆区空间释放,然后让s1的ptr指向,将亡值对象ptr指向的空间。然后将不具名对象ptr置为空即可完成赋值。
    请添加图片描述

加法运算符重载

  1. 如果两个对象都空,则直接返回空String()/
  2. 如果s1为空,s2不为空,则返回s2.
  3. 如果s1不为空,s2为空,则返回s1
  4. 如果s1和s2都不为空,则需要申请一个新的指针,开辟一段新的堆空间,将两个字符串放进去。
    5.最后返回时,需要调用构造函数,创建一个新对象。(因为是将对象赋值给对象),为了防止调用上面的构造函数,所以需要创建一个特殊的的构造函数。
private:  //这样才能访问strNode
String(StrNode* p):pstr(p){}
//加法运算符重载
	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_s(newNode->data, pstr->len+1, pstr->data);
			strcat_s(newNode->data, total, s.pstr->data);
			newNode->ref = 1;
			newNode->len = pstr->len + s.pstr->len;
			newNode->size = total;	
			return String(newNode);
		 }
	
		}
int main()
{
	String s1{ "baiU" };
	String s3("shiqianyu");
	s1 = s3 + s1;
}

s3+s1
调用了operator+完成
newNode =
请添加图片描述
return String(StrNode *p):pstr§
调用特殊的构造函数,创建一个将亡值对象,让该将亡值对象中的ptr将newNode指向的空间
请添加图片描述
请添加图片描述

最后:s1 = s3 + s1 ==》 s1->ptr = ptr;
调用移动赋值让s1对象中的ptr指向将亡值对象中的ptr;
请添加图片描述

+=运算符重载

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()
{
	String s1{ "baiU" };

	String s3("shiqianyu");

	s1 += s3;
}
  1. 判断thsi指针所指对象的引用计数大于1,则开辟空间。
  2. 如果引用计数等于1,那么如果size小于两个字符串长度之和,就对其扩容,否则直接拼接
  3. 如果this为空,另外一个对象不为空,则直接赋值给this所指的对象,引数加一
  4. 不满足上述条件什么都不做,直接返回this。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值