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# 内存的具体表现- 通用类型系统 深拷贝 浅拷贝 函数传参的更多相关文章

  1. “通用类型系统”(CTS)

    一.什么是“通用类型系统”(CTS) 描述类型的定义和行为 二.CTS规范 一个类型可以包含零个或者多个成员1,成员①字段(Field)作为对象状态一部分的数据变量.字段根据名称和类型来区分②方法(M ...

  2. python集合增删改查,深拷贝浅拷贝

    集合 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 去重,把一个列表变成集合,就自动去重了. 关系 ...

  3. JavaScript之深拷贝&浅拷贝

    深拷贝&浅拷贝,说起来都明白,但是说不出所以然.今天就系统的整理下思绪,一点点的将其分析出所以然 废话不多说 浅拷贝 简单的说就是一个值引用,学生时代接触过编程的人都应该了解过指针,浅拷贝可以 ...

  4. 【04】Python 深拷贝浅拷贝 函数 递归 集合

    1 深拷贝浅拷贝 1.1 a==b与a is b的区别 a == b    比较两个对象的内容是否相等(可以是不同内存空间) a is b  比较a与b是否指向同一个内存地址,也就是a与b的id是否相 ...

  5. java中深拷贝浅拷贝简析

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  6. 【opencv】imread 赋值 深拷贝浅拷贝

    import cv2 import copy import os def filter_srcimg(dstimg): ss=3 srcimg=copy.deepcopy(dstimg) #aa=5 ...

  7. Java基础 深拷贝浅拷贝

    Java基础 深拷贝浅拷贝 非基本数据类型 需要new新空间 class Student implements Cloneable{ private int id; private String na ...

  8. JS Object Deep Copy & 深拷贝 & 浅拷贝

    JS Object Deep Copy & 深拷贝 & 浅拷贝 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refe ...

  9. iOS内存管理(二)之深拷贝和浅拷贝

    对象拷贝(复制对象) 1.复制对象顾名思义,复制一个对象作为副本,它会开辟一块新的一块内存(堆内存)来存储副本对象,就像复制文件一样.即源对象和副本对象是两块不同的内存区域.   2.NSObject ...

随机推荐

  1. HTML5本地图片裁剪并上传

    最近做了一个项目,这个项目中需要实现的一个功能是:用户自定义头像(用户在本地选择一张图片,在本地将图片裁剪成满足系统要求尺寸的大小).这个功能的需求是:头像最初剪切为一个正方形.如果选择的图片小于规定 ...

  2. 读书笔记 effective c++ Item 37 永远不要重新定义继承而来的函数默认参数值

    从一开始就让我们简化这次的讨论.你有两类你能够继承的函数:虚函数和非虚函数.然而,重新定义一个非虚函数总是错误的(Item 36),所以我们可以安全的把这个条款的讨论限定在继承带默认参数值的虚函数上. ...

  3. nicescroll 配置参数

    jQuery滚动条插件兼容ie6+.手机.ipad http://www.areaaperta.com/nicescroll/ 配置参数 当调用“niceScroll”你可以传递一些参数来定制视觉方面 ...

  4. Linux学习之sudo命令

    在学习Linux用户管理时,我们不得不需要了解一个命令,那就是sudo.sudo的作用是切换身份,以其他身份来执行命令. 那么为什么在Linux系统中我们需要来切换身份呢?原因有以下几个方面 1.养成 ...

  5. CI Weekly #16 | 从另一个角度看开发效率:flow.ci 数据统计功能上线

    很开心的告诉大家,flow.ci 数据统计功能已正式上线. 进入 flow.ci 控制台,点击「数据分析」按钮,你可以按照时间日期筛选,flow.ci 将多维度地展示「组织与项目」的构建数据指标与模型 ...

  6. iOS 中的单例设计模式

    单例设计模式:在它的核心结构中只包含一个被称为单例类的特殊类.例如文件管理中的NSUserDefault,应用程序中的UIApplication,整个应用程序就这一个单例类,负责应用程序的一些操作,单 ...

  7. Java版权信息之Jautodoc

    Java项目开发中,常常需要在编码文件上面加上一些版权声明或者类注释,如果文件很多,手工去添加或者修改,会很麻烦.可以利用工具满足我们的要求.一.版权声明可以使用Jautodoc.将jautodoc的 ...

  8. Linux开机启动(bootstrap)下

    init process (根据boot loader的选项,Linux此时可以进入单用户模式(single user mode).在此模式下,初始脚本还没有开始执行,我们可以检测并修复计算机可能存在 ...

  9. 跟着刚哥梳理java知识点——面向对象(八)

    面向对象的核心概念:类和对象. 类:对一类事物描述,是抽象的.概念上的定义. 对象:实际存在的该类事物的每个个体,因而也成为实例(Instance). Java类及类的成员:属性(成员变量Field) ...

  10. Robotframe work之环境搭建(一)

    准备安装如下:Python2.7.10.robot framework3.0.2.wxPython 2.8.12.1.robot framework-ride 1. 官网下载安装python,目前wx ...