c# 内存的具体表现- 通用类型系统 深拷贝 浅拷贝 函数传参
c# 通用类型系统 及变量在 深拷贝 浅拷贝 函数传参 中的深层次的表现
在编程中遇到了一些想不到的异常,跟踪发现,自己对于c#变量在内存上的表现理解有偏差,系统的学习并通过代码实验梳理了各种情况下,变量在内存级的表现情况,对以后的coding应该有些帮助。在此记录以免忘记了。。。
1. 通用类型系统
先来一张图:

通用数据类型分为了值类型和引用类型。
我们定义一个int型实际上是一个system.int32的实例,在语法上我们像使用其他的类对象一样,但是,存储的的仍然是基本类型。这种把基本类型和引用类型在语法上统一起来,所以可以称之为
通用类型系统。其实每一个值类型都有一个.net相对应的system.***。
引用类型,string是引用类型。在很多人看来string的表现与值类型一样,但是他是引用类型,只是string类的很多成员函数返回的重新生成的string对象。我们应该把引用类型当做对象指针。
在学习c++时,我们知道变量分配在堆或者栈上。在c#讨论是我们不在区分堆栈,现在统一称为堆栈。c#引入了一个新的内存空间,称之为托管堆。
引用一张别人的图,图右侧的堆其实应该是托管堆。

值类型和引用类型的对象指针分配在堆栈上,而引用类型的具体对象分配在托管堆上。
2. 变量赋值操作
值类型赋值:值类型的赋值很简单,即在堆栈申请空间,将值写入申请的空间。int a =10;即堆栈上给a分配4字节空间,4字节空间保存了10的二进制;int b =a,即堆栈上给b分配4字节空间,4字节空间保存了a的值即10的二进制.a与b是完全独立的,b只是使用了a的值对自己的空间进行了赋值。
引用类型赋值:引用类型声明时即在堆栈分配了4字节的空间(一个指针的大小),初始值为0x00000000。如object boxed,此时boxed分配了4字节的空间,空间内为0x00000000;boxed= new object(),此时在托管堆分配了空间存放了一个object的对象,boxed在堆栈的4个字节的空间存放了对象在托管堆栈的首地址。当时使用object a =b时,堆栈上分配了a的4字节的空间,其值是b的4字节的空间的值。即a,b保存了同样的地址值,指向了同样的托管堆中的一个对象。a,b只是同一个对象的不同“指针”。
3. 引用类型的浅拷贝和深拷贝
浅拷贝和深拷贝问题出现在对象包含一个引用类型的成员这种情况下。说白了,浅拷贝和深拷贝就是有没有对对象的引用类型成员重新分配空间。引用类型对象存储在托管堆中,若对象本身包含一个引用类型的成员时,此成员在对象中存储的是引用类型的成员对象的地址。理解为“我”包含一个指针,指向了一个“他”,你要浅拷贝我时,我把这个指针给你,你也指向了“他”;你要深拷贝我时,创建一个“他”的弟弟即使用new,你指向“他”的弟弟即new的返回值(地址)。
示例说明下:
class Program
{
static void Main(string[] args)
{ Test a = new Test("aaa",);
Test b = a;
Test c = a.Clone();
c.myInt = ;
c.myStr = "bbb";
c.myIntClass.myInt = ;
Console.WriteLine(a.myInt +" "+ a.myStr+" "+a.myIntClass.myInt);
Console.WriteLine(b.myInt + " " + b.myStr + " " + b.myIntClass.myInt);
Console.WriteLine(c.myInt + " " + c.myStr + " " + c.myIntClass.myInt); Console.ReadLine(); }
}
public class Test:Object
{
public string myStr { get; set; }
public int myInt { get; set; } public IntClass myIntClass { get; set; }
public Test Clone()
{
return (Test)this.MemberwiseClone();
}
public Test(string _str,int _int)
{
myStr = _str;
myInt = _int;
myIntClass = new IntClass(_int);
}
}
public class IntClass
{
public int myInt { get; set; }
public IntClass(int _int)
{
myInt = _int;
}
}

a和b是指向同一个Test对象的引用,c是a的一个浅拷贝;即c指向的对象内有一个int,一个指向string的指针(与a指向string的指针的值相同),一个指向IntClass的指针(与a指向IntClass的指针的值相同)。当改变c的int时,a不受影响;当改变c的intClass时,其实是改变了a,c指向的同一个对象,故a,b,c的IntClass 同时改变了。这里有个有趣的现象,即string也是引用类型,而c改变了其myStr,a竟然没收受到影响。这要特殊说明string是一个特殊的引用类型,
c.myStr = "bbb";其实是重新创建了一个string对象,c的mystr指向了新的对象,故a不受影响。string对象是不会被改变值的,对string的任何操作都是在一个新的对象上进行,然后返回新的对象引用。所以很多时候string表现的更像一个值类型,虽然它的确是一个引用类型。
4. 函数传参
无论是值类型还是引用类型,函数默认传递方式都是值传递,形参都“copy”了实参的值。 对于值类型很好理解,即堆栈上给形参分配了新的空间,把实参的值copy进新分配的空间。对于引用类型,堆栈上给形参分配了新的空间,把实参的值--实参在堆栈保持的4字节的对象地址 copy进了新分配的空间,注意,并没有在托管堆创建对象。所有,函数传递的引用类型,实参与形参在堆栈上是不同的空间,但其所指向的对象是同一个。
示例:
Test a = new Test("123", 10);
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
int b = 100;
Change(a, b);
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
Console.WriteLine(b);
public static void Change(Test _test,int _int)
{
_test.myInt = _int;
_int *= 10;
}

经过函数change之后对象a的myInt变为了100,而int类型的b的值并没有变化还是100。
可以简单的认为函数的值传递仅限于堆栈上的分配空间和对于新空间的赋值。
out 和 ref :
c#函数有out和ref2个关键字修饰。先讲讲ref,ref相当于c++中的&传参。ref在堆栈上也没有分配空间,形参和实参在堆栈上是一个空间。所有对形参的任何改变都会体现在实参上,甚至,对于形参指向对象的改变也会体现在实参上。示例如下:
Test a = new Test("", );
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
int b = ;
Change(ref a, ref b);
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
Console.WriteLine(b);
public static void Change(ref Test _test,ref int _int)
{
_test = new Test("new", _int);
_int *= ;
}
change传递的是ref关键字修饰的参数,在change内部对于形参的任何操作相当于对实参的操作。我们与不带ref关键字的对比下:
Test a = new Test("", );
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
int b = ;
Change( a, b);
Console.WriteLine(a.myInt + " " + a.myStr + " " + a.myIntClass.myInt);
Console.WriteLine(b);
public static void Change( Test _test, int _int)
{
_test = new Test("new", _int);
_int *= ;
}
不带ref关键字时,对于引用类型的赋值操作不会体现在实参上,这是由于我们把形参的堆栈上的地址值赋值为了在托管堆新分配的对象。而实参堆栈上的地址值依然指向旧的对象。不带ref实参,形参在堆栈是不同的2块空间。 out关键字与ref一样,形参与实参是同一块堆栈地址,不同的时,函数退出前必须对out参数赋值(即在托管堆分配新的对象)。 总结函数传参: 默认是指传递,在堆栈上分配空间并把实参在堆栈上相应值复制到新分配的空间。对于形参堆栈的操作(值类型的赋值,引用类型的赋值)不会体现到实参上。对于托管堆的操作(改变引用类型的成员)会体现到实参上。
ref传递,在堆栈上没有分配新的空间,形参和实参是同一块堆栈地址。对于形参堆栈的操作(值类型的赋值,引用类型的赋值)和 对于托管堆的操作(改变引用类型的成员)都会体现到实参上。
out传递,在堆栈上没有分配新的空间,形参和实参是同一块堆栈地址。函数返回前必须对形参赋值。(若转载请注明博客园源地址)
c# 内存的具体表现- 通用类型系统 深拷贝 浅拷贝 函数传参的更多相关文章
- “通用类型系统”(CTS)
一.什么是“通用类型系统”(CTS) 描述类型的定义和行为 二.CTS规范 一个类型可以包含零个或者多个成员1,成员①字段(Field)作为对象状态一部分的数据变量.字段根据名称和类型来区分②方法(M ...
- python集合增删改查,深拷贝浅拷贝
集合 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 去重,把一个列表变成集合,就自动去重了. 关系 ...
- JavaScript之深拷贝&浅拷贝
深拷贝&浅拷贝,说起来都明白,但是说不出所以然.今天就系统的整理下思绪,一点点的将其分析出所以然 废话不多说 浅拷贝 简单的说就是一个值引用,学生时代接触过编程的人都应该了解过指针,浅拷贝可以 ...
- 【04】Python 深拷贝浅拷贝 函数 递归 集合
1 深拷贝浅拷贝 1.1 a==b与a is b的区别 a == b 比较两个对象的内容是否相等(可以是不同内存空间) a is b 比较a与b是否指向同一个内存地址,也就是a与b的id是否相 ...
- java中深拷贝浅拷贝简析
Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
- 【opencv】imread 赋值 深拷贝浅拷贝
import cv2 import copy import os def filter_srcimg(dstimg): ss=3 srcimg=copy.deepcopy(dstimg) #aa=5 ...
- Java基础 深拷贝浅拷贝
Java基础 深拷贝浅拷贝 非基本数据类型 需要new新空间 class Student implements Cloneable{ private int id; private String na ...
- JS Object Deep Copy & 深拷贝 & 浅拷贝
JS Object Deep Copy & 深拷贝 & 浅拷贝 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refe ...
- iOS内存管理(二)之深拷贝和浅拷贝
对象拷贝(复制对象) 1.复制对象顾名思义,复制一个对象作为副本,它会开辟一块新的一块内存(堆内存)来存储副本对象,就像复制文件一样.即源对象和副本对象是两块不同的内存区域. 2.NSObject ...
随机推荐
- TypeScript入门-接口
▓▓▓▓▓▓ 大致介绍 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约. ▓▓▓▓▓▓ 接口 例子: function printLabel(labelledO ...
- 解决修改mysql的data_dir所引发的错误
今天,搭建新购的阿里云ECS环境(ubuntu 16.04 LTS),需要将mysql的数据保存在新挂载的磁盘上(已挂载到/mnt下),先停掉mysql服务,然后查看mysql并数据保存的位置: vi ...
- 关于开发环境中的消息在download时没有下载下来时的问题
业务场景:在开发环境改了一些代码,现在需要将这些代码(包括class和数据库对象)移植到开发环境,整理出了Objectlist(就是该模块定义了哪些数据库对象),然后上传到FTP服务器上时,再执行do ...
- 微信小程序-实战巩固(二)
刚刚写了小程序入门没几天,小程序就开放个人开发者资格,感觉为我而来啊 \(≧▽≦)/.迫不及待的去注册,准备将之前的处女作传上去体验一把,结果卡在了服务器配置上:免费的果然不靠谱/(ㄒoㄒ)/~~,后 ...
- Ubuntu下搜狗输入法突然无法输入中文
百度了很久的,后面看到这个帖子,找到解决办法.引用:http://blog.csdn.net/kiss_the_sky/article/details/62238529 删除配置文件,重启搜狗 ubu ...
- 简单的叙述下SQL中行列转换的小知识!
行列转换对于工作还是学习中总是不可避免的会遇到(虽然本人还尚未工作,萌萌哒的学生一枚),解决的方法也有很多,我这里就总结一下我所想解决的问题以及怎么去解决的方法, 可能网上已经有很多类似的方法了,有的 ...
- Java--JDBC连接数据库
我们知道Java中的jdbc是用来连接应用程序和数据系统的,本篇文章主要就来看看关于JDBC的实现和使用细节.主要包含以下几点内容: JDBC的基本知识(数据驱动程序) JDBC的连接配置 ...
- 性能测试培训:批量执行Jmeter脚本之ant调用
性能测试培训:批量执行Jmeter脚本之ant调用 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的load ...
- 老李分享: Oracle Performance Tuning Overview 翻译下
1.2性能调优特性和工具 Effective data collection and analysis isessential for identifying and correcting perfo ...
- 4.熟悉Java基本类库系列——Java 正则表达式类库
正则表达式语法结构图: Java正则表达式类库结构图: Java典型例子 1.String类 matches()方法 判断字符串是否符合特定正则表达式 @Test public void testRe ...

