关于C#中Struct的拷贝

摘自:https://www.jianshu.com/p/7a3d703cf57f

 

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

其实关于浅拷贝、深拷贝,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#本身并不可以对 = 操作符进行重载,所以我们只能自己定义方法取进行深拷贝了。

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



作者:卅云川
链接:https://www.jianshu.com/p/7a3d703cf57f
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 结构体(struct)和类(class)都是C#的数据类型。它们的区别在于,结构体是值类型,类是引用类型。结构体通常用于简单的数据类型,类则通常用于更复杂的数据类型和对象。另外,类具有继承的概念,而结构体则没有。在使用时需要根据具体情况选择适合的数据类型。 ### 回答2: C首先可以是音乐的一个音调或者一个音符,是西洋音乐符号系统的第三个音符,一般为央C,频率为261.63Hz。在音乐,C可以出现在调式的任何位置,代表不同的音高和情感变化。 此外,C也可以代指计算机编程语言的一种高级编程语言——C语言。C语言是一种高效、结构化程序设计语言,常用于操作系统、编译器、网络驱动程序等方面的软件开发。C语言具有简洁易懂、运行速度快、可移植等优点,在现代计算机软件开发被广泛使用。 除此之外,C也可以是化学元素周期表的一种化学元素,即碳(Carbon)。碳是一种非金属元素,以其广泛存在于生物体和地球内部、极为重要的化学活性和多种同素异形体等特性而被广泛研究和运用。碳可以制造高纯度的材料,如金刚石、石墨等;也可以构成复杂的有机化合物,如烷基、烯基、炔基等,用于生命科学、材料科学、化学工程等方面的研究和应用。 综上所述,C是一个多义词,具有音乐、计算机编程、化学等多种意义,在不同的领域都有广泛的应用和研究。 ### 回答3: 很抱歉,您的问题似乎缺失了一个完整的问题。请提供更详细的信息,以便我能够对您的问题提供更有针对性和准确的回答。谢谢!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值