深浅拷贝及写实拷贝

浅拷贝

对于String类的拷贝构造早函数及operator=函数来说,当用一个string对象拷贝构造或复制给另一个String对象时,就是讲这个对象里的指针的值赋值给另一个对象里的指针。讲一个帧赋值给另一个指针,就会使得两个指针指向同一块空间,就产生了浅拷贝。

class String
{
public:
	String(const char* str)
		:_str(new char[strlen(str) + 1])
	{
		strcpy(_str, str);
	}

	String(const String& s)
		:_str(s._str)
	{}

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			_str = s._str;
		}
		return *this;
	}

	~String()
	{
		if (_str)
		{
			delete[] _str;
		}
		_str = NULL;
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
    String s2(s1);

	return 0;
}

浅拷贝存在的问题:
1.两个及以上指针指向同一块空间,这个内存就会被释放多次;
例子:定义了一个Sting对象s1,以浅拷贝的方式拷贝构造了一个String对象s2,则s1和s2中的指针_str就会指向同一块空间;出了作用域,s2先调用析构函数进行空间的释放,也就是所指向的空间会被s2释放,接下来s1也回去调用构函数去释放这块空间,但是这块空间已经被释放了,所以就会出错。
在这里插入图片描述
s2析构之后,s1再析构会出错。
在这里插入图片描述
2.因为两个指针指向同一块空间,所以一旦一个指针修改了这块空间,另一个指针指向的空间的值也会被修改。
在这里插入图片描述

浅拷贝的解决方法

1.深拷贝
2.引用数的写实拷贝

深拷贝

传统写法
若用一个s1对象拷贝构造或赋值给s2对象,当涉及到浅拷贝问题的时候:
对于拷贝构造函数来说,s2先开辟一块和s1一样大的空间;而对于赋值运算符重载函数来说s2已经存在,则必须先释放s2的空间然后让s2开辟与s1一样大的空间,否则就会导致s2里面的指针没有释放。

然后让s2指向这块新开的空间,最后将s1里面的数据拷贝至s2指向的空间(自己开空间自己拷数据)。

String(const String& s)
	{
		_str = new char[strlen(s._str) + 1];  //开空间
		strcpy(_str, s._str);   //拷数据
	}

	//赋值的传统写法
	String& operator=(const String& s)   //必须要返回引用,为了连续的赋值
	{
		if (this != &s)
		{
			delete[] _str;
			_str = NULL;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);
		}
		return *this;
	}

现代写法:
特点:让别人去开空间,去拷贝数据,而我将你的空间与我交换就可以了。
实现:比如用s1拷贝构造一个s2对象,可以通过构造函数将s1里的指针_str构造一个临时对象tmp(构造函数不仅会开空间还会将数据拷贝至tmp),此时tmp就是我们想要的那个对象,然后将新tmp的指针_ptr与自己的指针进行交换。
对于构造函数来说,因为String有一个带参数的构造函数,则用现在写法写拷贝构造时可以调用构造函数,而对于无参的构造函数的类只能采用传统写法。

String(const String& s)
		:_str(NULL)
	{
		String tmp(s._str);   //调用构造函数,则tmp就是我们需要的

		swap(_str, tmp._str);   //将_str与tmp的_str指向的空间进行交换,tmp._str就会指向_str的空间,出了这个作用域,tmp就会调用析构函数,但是tmp里面的_str值可能不确定,所以在初始化列表中将_str置空,这样tmp._str=NULL
	}

	//赋值的现代写法
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			String tmp(s._str);    //调用构造函数
			swap(_str, tmp._str);   //tmp是局部对象,出了这个作用域就会调用析构函数,就会将tmp里面的指针指向的空间释放掉,
		}
		return *this;
	}
	

带引用计数的写实拷贝

为什么会出现带引用计数的写实拷贝?
1.有时会多次调用拷贝构造函数,但是拷贝构造的对象并不会修改这块空间的值。如果采用深拷贝,每次都会重复的考空间,然后拷数据,最后再释放这块空间,这样会花费很大的精力。
2.若是使用浅拷贝不用重复的开空间,但是析构会有问题,且改变一个会改变另一个。
为了解决上述问题,可以引进一个引用计数,当有新的指针指向这块空间的时候,增加引用计数,当这个指针需要销毁时,就将引用计数的值减1,当引用计数的值为1是才去释放这块空间;当有一个指针需要修改其指向空间的值的时候,才去开一块新的空间。

综上:引用计数解决了空间被多次释放的问题,写实拷贝解决了多个指针指向同一块空间会修改的问题。

引用计数的定义方式

1.将引用计数定义为int
缺点:每个对象的引用计数之间是独立的,如果增加指向这块空间的指针,也只会修改新增这个指针所在的对象的引用计数,就会使得每块空间对应引用计数不相同。
例如:用s1对象拷贝构造了s2对象,s1中的计数为1,而s2中计数为2

2.将引用计数定义为static int
缺点:因为静态成员为该类的所有对象所共享,就会使得String类创建的所有对象的指针哪怕指向不同的空间,但是这些对象的引用计数都相等。

3.将引用计数定义为int的指针
缺点:int
的指针占4个字节,每次创建一个String对象,都会为其向操作系统申请呢4个字节的内存,这样就经常申请许多的小块内存,会造成内存碎片,对效率造成影响。
所以对这种方式进行改进,将_str与引用计数放在一起,就在_str的头上4个字节处存放引用计数,当我们取引用计数时,只用将*((int*)(_str-4))

class String
{
public:
	String(char* str = "")
		:_str(new char[strlen(str) + 5])    //因为多开了4个字节给引用计数,所以这里加5,上面引用计数和_str独立加的是1,只有这里的_str包含引用计数,后面的_str都不包含引用计数
	{
		_str += 4;   //从_str+4才表示有效的字符,前面是引用计数
		strcpy(_str, str);
		GetRefCount() = 1;    //将引用计数置为1
	}

	// s2(s1)
	String(const String& s)
		:_str(s._str)
	{
		++(GetRefCount());
	}

	//s2 = s1
	String& operator=(const String& s)
	{
		if (_str != s._str)
		{
			if (--(GetRefCount()) == 0)
			{
				delete[](_str - 4);
			}
			_str = s._str;
			++GetRefCount();
		}
		return *this;
	}

	~String()
	{
		if (--GetRefCount() == 0)
		{
			delete[](_str - 4);
		}
	}

	int& GetRefCount()
	{
		return *((int*)(_str - 4));
	}

	const char* c_str()
	{
		return _str;
	}

	void CopyOnWrite()
	{
		if (GetRefCount() > 1)
		{
			char* newstr = new char[strlen(_str) + 5];
			newstr += 4;
			strcpy(newstr, _str);
			--GetRefCount();
			_str = newstr;
			GetRefCount() = 1;
		}
	}

	char& operator[](size_t pos)
	{
		CopyOnWrite();
		return _str[pos];
	}

private:
	char* _str;  // 引用计数放在_str的头上4个字节处
};

注意:引用计数的写实拷贝,读有时也会拷贝
在String类中,如果想要取出里面的某个字符或者修改某个对应位置上的字符需要重载operator[];
因为operator[]既可以读也可以修改,为了统一,无论读写,都要重新拷贝;

度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值