打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
关于C#中Struct的拷贝

为什么写这么一篇鸡肋文章?

其实关于浅拷贝、深拷贝,struct结构体,网络上已然有太多大作可以拜读。作者们都恨不得连这些东西的祖宗十八代都淘换出来。

而作为一个程序,总有不知不觉脑子钻进牛角尖的时候。作者今天在考虑结构体内部成员的拷贝相关问题的时候,写了两个例子,却无意间因为基础的赋值语句、以及多想到的String本身特殊的机制而产生了错觉。

所以,把错觉分享,也算是为了在头脑发热的时候准备一盆冷水吧,起码重复对记忆而言是有百利而无一害的。

浅拷贝与深拷贝

作为编程学习中常见的问题,关于浅拷贝与深拷贝的文章已然很多,这里只是作为引言聊一聊了。

 浅拷贝深拷贝
值类型复制本身复制本身
引用类型复制引用复制指向的对象本身

这里点出浅拷贝与深拷贝,是为了记录之前让网上一个例子弄混淆的东西,所以这里只是为了引出后续的话题。

Struct的拷贝

这个才是今天的主题,一切也都是因为Struct引起。

Struct是个值类型

这一点基础扎实的猿儿们都知道。所以我们可以确定,Struct本身在进行赋值的时候本身就进行的是“复制本身”,而无论内部是否包含引用类型。

我们可以通过写demo测试以上内容。

// 定义一个结构体,包含一个值类型与一个引用类型成员struct Str{public int num;public string name;public Str (int num, string name){this.num = num;this.name = name;}public override string ToString (){return string.Format ("[Str, num:{0}, name:{1}]", num, name);}}
// 随便定义个方法,只为了看看通过赋值之后,struct的拷贝是怎样的public void WhatWillOpen (){Str s1 = new Str (9, "Default");Str s2 = s1;Log ("s1:" + s1);Log ("s2:" + s2);Log ("\n");s1.num = 3;s1.name = "Custom";Log ("s1:" + s1);Log ("s2:" + s2);}

经过上述骚操作,最终输出如下:

s1:[Str, num:9, name:Default]
s2:[Str, num:9, name:Default]

s1:[Str, num:3, name:Custom]
s2:[Str, num:9, name:Default]

WHAT!?!?!?!?!?!?

name 不是引用类型吗,为什么 s1name 变化后 s2name 还是原来的值!?

是不是哪里错了,String不是class吗?

上面的结果可以看得出,Struct进行值类型的拷贝之后,两个值之间不再有关联,所以修改了 s1num 之后, s2num 依旧是赋值时的 9

那么问题来了,String作为引用类型,为什么在修改了 s1name 之后,为什么 s2name 依旧没变?难道Struct中的String也进行了深拷贝?

题外话:C#的String究竟怎么玩的?

也许有人发现刚才的问题出在哪里了,不过我还是想聊聊我遇到这个问题时候做的例子[捂脸]~~~

C#当中的String是引用类型没错,但是这块儿我想到了C#虚拟机对字符串类型进行的特殊处理了。这部分的详细内容可以网上找找看看,我只通过代码说明需要证明的问题。

// 通过定义两个字符串变量,并进行操作,观察结果// 就可以明白刚才的问题究竟是什么鬼string st1 = "Default";string st2 = st1;Log ("st1:" + st1);Log ("st2:" + st2);Log ("\n");st1 = "Custom";Log ("st1:" + st1);Log ("st2:" + st2);

结果:

st1: Default
st2: Default

st1: Custom
st2: Default

所以,出现的问题竟然是。。。

各位看官莫砸我,原因原来仅仅是因为我们String进行了重新赋值!!!

作为引用类型,最上方例子中的 Str 结构中变量 name 仅仅是一个String类型的引用,所以当在外部调用赋值语句时,自然就把这个 Str 结构中的指针指向了其他地方!!!

所以,傻X有时候仅仅是一瞬间的事,想岔了,自然就成了傻X了:-(。

其实例子已经说明Struct是做了深拷贝了

在做完深拷贝之后,同样都是 Str 结构,修改 s1 的引用类型成员的引用,并没有修改 s2 的引用类型成员的引用。所以这也是为什么会有最上例子中最终那样的输出结果。

当然这个例子我觉得还是有迷惑性的,否则我干嘛钻牛角尖(我说是就是!)。

换个?,更健康

因为String类型自身的特殊性,所以我们可以换一个自定义引用类型作为Struct的成员并观察。

// 全新的对名字的封装类class Name{public string name;public Name (string name){this.name = name;}}struct Str{public int num;public Name name;// 此处名字改为我们自定义的类public Str (int num, string name){this.num = num;this.name = new Name (name);}public override string ToString (){return string.Format ("[Str, num:{0}, name:{1}]", num, name.name);}}
// 修改原来那个随便定义个方法public void WhatWillOpen (){Str s1 = new Str (9, "Default");Str s2 = s1;Log ("s1:" + s1);Log ("s2:" + s2);Log ("\n");s1.num = 3;s1.name.name = "Custom";Log ("s1:" + s1);Log ("s2:" + s2);Log ("\n");s1.num = 5;s1.name = new Name ("AllNew");Log ("s1:" + s1);Log ("s2:" + s2);}

这么一来,最终结果会如何???

s1:[Str, num:9, name:Default]
s2:[Str, num:9, name:Default]

s1:[Str, num:3, name:Custom]
s2:[Str, num:9, name:Custom]

s1:[Str, num:5, name:AllNew]
s2:[Str, num:9, name:Custom]

这次这个例子足够说明问题了。

当我们修改 s1 中引用类型内部内容的时候,因为 s2 中的引用内容与 s1 中的引用内容指向的是同一块地方,所以当 s1namename 变了的时候, s2 中的 name 也产生了相同的变化。

而当我们修改 s1 引用类型指向的时候, s2 中的引用类型并没有更改指向内容。所以当我们直接更改了 s1name 的时候,s2name 依然指向之前的对象。

结语

在C#的世界里,Struct作为值类型,其拷贝遵循的一直是复制本身的原则,只不过复制本身之后,其内部的指针(作为C起家,我还是倾向用指针描述引用)变量只是复制了指针所指向的地址罢了,而那个地址内的内容却并不会产生复制。

如果想对Struct进行完全的深度拷贝,则需要我们另下一番功夫去实现。而且C#本身并不可以对 = 操作符进行重载,所以我们只能自己定义方法取进行深拷贝了。

好了,这盆冷水,终于让我清醒了。但愿别再脑子发热出现这种幼稚问题了。发个帖吐吐槽,让别人笑笑就好。



作者:卅云川
来源:简书

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C# 9.0 新特性
C#中简单的正则表达式(也经常会用到的)
String是值传递还是引用传递
python中数字类型和字符串类型的相互转换的方法
结构体的定义和使用
1.4 泛型编程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服