谈谈我理解的拷贝
这段时间在跟周围开发者讨论的过程中,发现大部分情况下我们很难区分清楚开发中涉及的各种拷贝。
比如深拷贝浅拷贝、指针拷贝和对象拷贝以及object-c中系统容器类特有的不可变拷贝和可变拷贝。
更有人曾经就简单粗暴的将深拷贝解析为可变拷贝,将浅拷贝解析为不可变拷贝(如题图所示)。
这里首先要严肃说明的是,认为系统容器类的不可变拷贝即为浅拷贝而可变拷贝为深拷贝这是完全错误的概念!
那么这几个概念到底该如何解析?下面我就从我自己理解的角度来帮助大家分析:
1. 深拷贝和浅拷贝
探讨深拷贝和浅拷贝最多的实际是在C++的项目中。例如我们有一个CMessage类,只定义了了一个指针成员pReceiver,同时定义并实现其默认的构造函数析构函数。
// 类定义
class CMessage{
int* pReceiver;
public:
CMessage();
~CMessage();
CMessage(const CMessage&);
};
// 类实现
#include "CMessage.hpp"
CMessage::CMessage()
{
pReceiver = new int(0);
}
CMessage::~CMessage()
{
delete pReceiver;
pReceiver = null;
}
实际上此时,编译器还会帮我们生成一个拷贝构造函数:
CMessage::CMessage(const CMessage& resMsg)
{
pReceiver = resMsg.pReceiver;
}
由于这个拷贝构造并未对指针成员进行内容拷贝,只是复制了其地址,所以在用到拷贝赋值的地方,实际会出现复制出来的对象和被复制对象的成员pReceiver实际指向了同一块内存。
这就是经常被大家提起的浅拷贝。
为解决浅拷贝带来的内存访问冲突问题,所以引入了深拷贝。如,对于上面的代码示例,将拷贝构造函数改成如下方式,即实现了深拷贝。
CMessage::CMessage(const CMessage& resMsg)
{
pReceiver = new int;
*pReceiver = *resMsg.pReceiver;
}
由此,不难看出,深拷贝和浅拷贝实际探讨的是在对象拷贝时对其指针成员的赋值方式是内容拷贝还是地址拷贝的问题。
2. 指针拷贝和对象拷贝
这里所说的指针拷贝实际就是上面提到的地址拷贝,而对象拷贝即上面的内容拷贝。
如何理解这个概念呢,还是拿上面的C++代码的例子。
假设我们有两个函数,AddressCopy和ContentCopy,分别实现的指针拷贝和对象拷贝。
则它们的实现当如下:
// pMsgB指针拷贝msgA
void AddressCopy()
{
CMessage msgA;
CMessage *pMsgB = &msgA;
}
// pMsgB对象拷贝msgA
void ContentCopy()
{
CMessage msgA;
CMessage *pMsgB = new CMessage(msgA);
}
由上面的代码可看出,指针拷贝和对象拷贝实际探讨的是指针对象的赋值方式是直接指向原始对象还是指向跟原始对象拥有相同值的新对象的问题。
3. OC中的copy和mutableCopy
里所说的指针拷贝实际就是上面提到的地址拷贝,而对象拷贝即上面的内容拷贝。
OC中的copy和mutableCopy实际就是前面提到的可变拷贝和不可变拷贝,分别遵循NSCopying和NSMutableCopying协议。
因为OC中这两个协议都需显式实现才支持,所以对于自定义类,因开发者的实现而异。
系统容器类均实现了上面的协议,所以我们下面来看看系统容器类的这两种拷贝。
NSString* testA = @"测试消息内容";
// 不可变拷贝
NSString* testB = [testA copy];
// 可变拷贝
NSMutableString* testC = [testA mutableCopy];
NSLog(@"%p,%p,%p",testA,testB,testC);
NSLog(@"%@,%@,%@",testA,testB,testC);
[testC replaceOccurrencesOfString:@"内容" withString:@"内容改变" options:NSLiteralSearch range:NSMakeRange(0, testC.length)];
NSLog(@"%p,%p,%p",testA,testB,testC);
NSLog(@"%@,%@,%@",testA,testB,testC);
这段代码输出的信息如下:
0x10966d120,0x10966d120,0x7fb3c8420e30
测试消息内容,测试消息内容,测试消息内容
0x10966d120,0x10966d120,0x7fb3c8420e30
测试消息内容,测试消息内容,测试消息内容改变
由此可以看出,testB实际和testA指向相同的地址,而testC实际是指向了另外的内存。所以也就是OC系统容器类的copy实际是指针拷贝,而mutableCopy则是内容拷贝。
所以对copy而言,新旧指针指向的实际是同一块内存,并不存在内存的拷贝,所以也就无从谈深浅拷贝的问题。
mutableCopy则是拷贝了容器自身,返回了一个新的可变数组,指向不同的内存地址而已。
全文完
ps:前面两个例子中的c++代码只要稍作转变就可替换成OC代码,此处鉴于篇幅问题,不一一列举。