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 ...
随机推荐
- redux核心思路和代码解析
最近在公司内部培训的时候,发现很多小伙伴只是会用redux.react-redux.redux-thunk的api,对于其中的实现原理和数据真正的流向不是特别的清楚,知其然,也要知其所以然,其实red ...
- 【Java 并发】详解 ThreadLocal
前言 ThreadLocal 主要用来提供线程局部变量,也就是变量只对当前线程可见,本文主要记录一下对于 ThreadLocal 的理解.更多关于 Java 多线程的文章可以转到 这里. 线程局部变量 ...
- js求三位数的和
例如输入508就输出5+0+8的和13: <!DOCTYPE html> <html lang="en"> <head> <meta ch ...
- 微信小程序支付简单小结与梳理
前言 公司最近在做微信小程序,被分配到做支付这一块,现在对这一块做一个简单的总结和梳理. 支付,对于购物来说,可以说是占据了十分重要的一块,毕竟能收到钱才是重点. 当然在开发之前,我们需要有下面这些东 ...
- 老李分享:持续集成学好jenkins之Git和Maven配置 1
老李分享:持续集成学好jenkins之Git和Maven配置 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...
- 玩转 SSH(七):使用 dubbo + zookeeper 实现服务模块化
一.创建 SSMVCAnnoDemo 项目 点击菜单,选择“File -> New Project” 创建新项目.选择使用 archetype 中的 maven-quickstart 模版创建. ...
- This Handler class should be static or leaks might occur Android
首先解释下这句话This Handler class should be static or leaks might occur,大致意思就是说:Handler类应该定义成静态类,否则可能导致内存泄露 ...
- Python__slots__详解
摘要 当一个类需要创建大量实例时,可以通过__slots__声明实例所需要的属性, 例如,class Foo(object): __slots__ = ['foo'].这样做带来以下优点: 更快的属性 ...
- 自定义一个EL函数
自定义一个EL函数 一般就是一下几个步骤,顺便提供一个工作常用的 案例: 1.编写一个java类,并编写一个静态方法(必需是静态方法),如下所示: public class DateTag { pri ...
- maven私服搭建nexus介绍(二)
1.各个仓库介绍 Hosted:宿主仓库 主要放本公司开发的SNAPSHOTS测试版本,RELEASES正式发行版.合作公司第三方的jar包. Proxy:代理仓库 代理中央仓库:代理Apache下测 ...

