string是值类型还是c string 引用类型型

4373人阅读
&&&&&&& 一直记得,JAVA语言除了8大基本类型(byte,short,char,int,long,float,double,boolean),其他类型皆为引用类型。但是,今天有人告诉我String为值类型,顿时迷惑了。回来百度一通,有人说String是值类型,也有人说String是引用类型,大家都有自己的说法。
&&&&&&& 好吧,求人不如求己,实践出真知。
&&&&&&& 首先申明下结论:Java中的String是正宗的引用类型,但是,一定条件下,String会表现出一定的值特性。贴上代码,开工:
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, &test1() = & + test1(), Toast.LENGTH_LONG).show();
Toast.makeText(this, &test2() = & + test2(), Toast.LENGTH_LONG).show();
Toast.makeText(this, &test3() = & + test3(), Toast.LENGTH_LONG).show();
private boolean test1() {
        String a = &123&;
 String b = &123&;
return a ==
private boolean test2() {
String a = new String(&123&);
String b = new String(&123&);
return a ==
private boolean test3() {
String a = &123&;
doTest3(a);
return TextUtils.equals(&123&, a);
private void doTest3(String str) {
str = str + &123&;
&&&&&&& 从test1开始分析:
&&&&&&& test1()返回值为true,看起来有点像值类型。但是,一切都是可以解释的:
&&&&&&& 首先分析:
String a = &123&;
String a = new String(&123&);&&&&&& 的不同之处:
前者的String对象分配在堆中,但在常量池中保存了指向String对象的指针,而a为String型指针,指针的内容和常量之中“123”对应的指针相同。具体来说,执行过程如下:
首先,常量池中查找“123”的指针如果在常量池中未能找到“123”的指针,则在堆中分配“123”的内存空间,把地址保存到常量池中,并把这个地址赋值给String型指针a如果在常量池中找到“123”的指针,说明堆中已经存在“123”的实体,因为常量表示一个不可变的对象,所以,没有必要再创建新的实例,直接把常量池中的指针内容赋值给String型指针a
而后者,实际上涉及到两个String实体,“123”在堆中存在一个实体,并且在常量池中存在一个指向“123”的指针,而new String()会在堆中创建一个新的String实体,并深度拷贝“123”的内容,并返回新的String实体的地址,赋值给指针a
&&&&&&&& 所以,test1中,a和b两个指针都指向常量“123”的实体,他们的值相等,故a==b为true。
&&&&&&&& 而test2中,a和b两个指针都指向各自的String实体,虽然两个String实体都深度拷贝自常量“123”,所以内容相同,但是,因为不是同一个String实体,故而内存地址不同,a==b为false。
&&&&&&&& 说到这里,顺便说明一个C#和Java不同之处:C#对于所有的String对象都做了常量化处理,所以,内容确定的String实体在堆上只会有一个实例。故,如果test2函数以C#语言实现,则仅会有一个实例,a==b为true,这也是为什么C#中,String虽然是引用类型,却可以使用“==”操作符判断两个字符串相等的原因。
&&&&&&&& 再来说下test3
&&&&&&&& 粗看时,string类型作为引用传递,似乎应该被doTest3函数所改变,故TextUtils.equal(&123&,&123123&)应该为false才对,而实际测试结果则是:test3()返回值为true。
&&&&&&&& 这里,让我通过调试来分析原因:
首先,创建a,a的实体地址为“”
接着,进入到doTest3函数中,str的实体地址也为“”,这时,内存中存在a和str两个指针,他们都指向同一个String实体
然后,我们发现str指针指向了一个新的实体“123123”,地址为“”,但是这并不影响a指针,a指针仍旧指向之前的实体“123”
最后,函数返回,a指针果然还指向“123”,所以TextUtils.equal(&123&,&123&)返回true
&&&&&&& 总结:
&&&&&&& 我想前面的解释已经很清楚了,String类型的确是引用类型,虽然某些情况下,看起来有些像值类型。
&&&&&&& 另外,相信有些细心的朋友已经发现了,String的实现实际上就是char[]加变量count(先忽略offset和hash code),的确从本质上来说,String可以归结于char[],既然char[]是引用类型的,那String怎么可能是值类型呢?
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:88517次
积分:1201
积分:1201
排名:千里之外
原创:27篇
评论:22条
(1)(1)(1)(2)(2)(2)(2)(3)(1)(2)(2)(7)(1)(1)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'中国领先的IT技术网站
51CTO旗下网站
c#引用类型与值类型的区别大盘点
值类型直接存储其值,变量本身就包含了其实例数据,而引用类型保存的只是实例数据的内存引用。因此,一个值类型变量就永远不会影响到其他的值类型变量,而两个引用类型变量则很有可能指向同一地址,从而发生相互影响。
作者:snowfly123来源:博客园| 17:21
解析:CLR支持两种类型:值类型和引用类型。用Jeffrey Richter(《CLR via
C#》作者)的话来说,&不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题&。这就要求我们正确理解和使用值类型和引用类型。
值类型包括C#的基本类型(用关键字int、char、float等来声明),结构(用struct关键字声明的类型),枚举(用enum关键字声明的类型);而引用类型包括类(用class关键字声明的类型)和委托(用delegate关键字声明的特殊类)。
C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。
(1)在C#中,变量是值还是引用仅取决于其基本数据类型。C#
的基本数据类型都与平台无关。C#的预定义类型并没有内置于语言中,而是内置于.NET
Framework中。.NET使用通用类型系统(CTS)定义可以在中间语言(IL)中使用的预定义数据类型。C#中所有的数据类型都是对象。它们可以
有方法、属性等。例如,在C#中声明一个int变量时,声明实际上是CTS(通用类型系统)中System.Int32的一个实例:
int&i;&i&=&1;&string&s;&s&=&i.ToString();&
(2)System.Object和System.ValueType。引
用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即
直接继承System.ValueType。作为所有类型的基类,System.Object提供了一组方法,这些方法在所有类型中都能找到。其中包含
toString方法及clone等方法。System.ValueType继承System.Object。它没有添加任何成员,但覆盖了所继承的一些
方法,使其更适合于值类型。
(3)值类型。C#的所有值类型均隐式派生自System.ValueType:
结构体:struct(直接派生于System.ValueType)。
值类型:整型,sbyte(System.SByte的别
名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16)、
,uint(System.UInt32),ulong(System.UInt64),char(System.Char)。
浮点型:float(System.Single),double(System.Double)。
用于财务计算的高精度decimal型:decimal(System.Decimal)。
bool型:bool(System.Boolean的别名)。
用户定义的结构体(派生于System.ValueType)。
枚举:enum(派生于System.Enum)。
可空类型。
每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:
int i = 0;
int i = new
使用new运算符时,将调用特定类型的默认构造函数并对变量赋予默认值。在上例中,默认构造函数将值0赋给了i。
所有的值类型都是密封(seal)的,所以无法派生出新的值类型。
得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类
型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。可以用
Type.IsValueType属性来判断一个类型是否为值类型:
TestType&testType&=&new&TestType&();&if&(testTypetype.GetType().IsValueType)&{&Console.WriteLine(&{0}&is&value&type.&,&testType.ToString());&}&
(4)引用类型C#有以下一些引用类型:
数组(派生于System.Array)
用户需定义以下类型。
类:class(派生于System.Object);
接口:interface(接口不是一个&东西&,所以不存在派生于何处的问题。接口只是表示一种contract约定[contract])。
委托:delegate(派生于System.Delegate)。
object(System.Object的别名);
字符串:string(System.String的别名)。
可以看出:
引用类型与值类型相同的是,结构体也可以实现接口;引用类型可以派生出新的类型,而值类型不能;引用类型可以包含null值,值类型不能;引用类型变量的赋值只复制对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
(5)内存分配。值
类型的实例经常会存储在栈上的。但是也有特殊情况。如果某个类的实例有个值类型的字段,那么实际上该字段会和类实例保存在同一个地方,即堆中。不过引用类
型的对象总是存储在堆中。如果一个结构的字段是引用类型,那么只有引用本身是和结构实例存储在一起的(在栈或堆上,视情况而定)。如下例所示:
public&struct&ValueTypeStruct&{&private&object&referenceTypeO&public&void&Method()&{&referenceTypeObject&=&new&object();&object&referenceTypeLocalVariable&=&new&object();&}&}&ValueTypeStruct&valueTypeStructInstance&=&new&ValueTypeStruct();&valueTypeStructInstance.Method();&&
单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块都在栈上。但是字段referenceTypeObject是引用类型,局部变量referenceTypeLocalVarible也是引用类型。
public&class&ReferenceTypeClass&{&private&int&_valueTypeF&public&ReferenceTypeClass()&{&_valueTypeField&=&0;&}&public&void&Method()&{&int&valueTypeLocalVariable&=&0;&}&}&ReferenceTypeClass&referenceTypeClassInstance&=&new&ReferenceTypeClass();&&referenceTypeClassInstance.Method();&&
referenceTypeClassInstance
也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段
_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?
对上面的情况正确的分析是:引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,简称引用类型部署在托管堆上。值类型总是分配在它声明的地方,作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
(6)辨明值类型和引用类型的使用场合。在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
SomeType[]
oneTypes = new SomeType[100];
果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后
数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主
要是存储数据,值类型比较合适。
一般来说,值类型(不支持多态)适合存储供
C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:该类型的主要职责用于数据存储。
该类型的共有接口完全由一些数据成员存取属性定义。
该类型永远不可能有子类。
该类型不具有多态行为。
答案:在C#中,变量是值还是引用仅取决于其数据类型。C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。数组的元素,不管是引用类型还是值类型,都存储在托管堆上。
用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实
例)存储;作为局部变量时,存储在栈上。值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义
应用程序的行为。
原文链接:【责任编辑: TEL:(010)】
大家都在看猜你喜欢
原创热点头条头条热点
24H热文一周话题本月最赞
讲师:30981人学习过
讲师:119833人学习过
讲师:91361人学习过
精选博文论坛热帖下载排行
本书全面深入地介绍网络安全的配置与实现技术,包括系统管理、用户账户、病毒防御、灾难恢复、文件备份、安全策略、注册表等服务器安全,用...
订阅51CTO邮刊Swift中的值类型和引用类型
招聘信息:
本文由CocoaChina翻译组成员 DevTalking ( )翻译自苹果官方博客 &
在Swift中,类型分为两类:第一种是值类型,该类型的每个实例持有数据的副本,并且该副本对于每个实例来说是独一无二的一份,比如结构体(struct)、枚举(enum)、元组(tuple)都是值类型。第二种是引用类型,该类型的实例共享数据唯一的一份副本(在native层面说的话,就是该类型的每个实例都指向内存中的同一个地址),比如类(class)就是引用类型。在这篇文章中,我们将深入探讨值类型和引用类型的使用价值,以及如何在某种场景下选择正确的类型。
它们有什么不同?
值类型最基本的特点就是复制,这影响到它的赋值、初始化、传参等操作。来看看下面的代码示例:
&struct&S&{&var&data:&Int&=&-1&}&var&a&=&S()&var&b&=&a&&&&&&&&&&&&&&&&&&&&&&&&a.data&=&42&&&&&&&&&&&&&&&&&&&&&&println("\(a.data),&\(b.data)")&&&&
引用类型的复制行为其实是隐式的创建了一个共享的实例,作为原始类型的参照。下面的例子中,两个变量都会参照唯一的那个共享实例的数据,所当改变这两个变量中任何一个的数据,都会同样作用于原始类型的数据:
&class&C&{&var&data:&Int&=&-1&}&var&x&=&C()&var&y&=&x&&&&&&&&x.data&=&42&&&&&&println("\(x.data),&\(y.data)")&&&&
Mutation在安全性中的角色
选择值类型的一个很重要的原因是可以让你比较容易的理解和掌控你的代码。如果你使用值类型,那么都是唯一的数据值、类型的副本在和你打交道,你对数据的修改只作用于数据所属的类型实例,所以你可以不用担心因为你在某个地方对数据的修改而影响到其他地方的数据。这在多线程环境中非常有用,因为在多线程下,不同的线程有可能会在你不知情的情况下改变数据。发生这种Bug后,调试起来就非常困难。
因为值类型和引用类型的表象区别就在于当你修改类型实例的数据时,它们对原始类型数据的处理方式不同。但是有一种情况,值类型和引用类型的处理方式却又相似,那就是当类型实例的数据为只读的时候。在不存在修改的情况下,值类型和引用类型就没什么区别了。
你可能会觉得这一点很有用,假如说一个class是完全不能被重定义的,那么就比较符合使用Cocoa的NSObject对象的一些习惯,并能很好的保持原本的语义。今天,在Swift你可以通过定义不可改变的存储属性来创建一个不可重定义的类,这样可以避免暴露出的API被修改。事实上,许多普通的Cocoa框架里的类,比如NSURL,都被定义成了不可重定义的类。尽管如此,Swift目前还不提供任何机制像结构体(struct)和枚举(enum)一样去强制使一个class成为不可重定义的类(比如像子类)。
如何选择正确的类型?
如果你想创建一个新类型,那么你应该选择值类型还是引用类型呢?当你使用Cocoa框架时,很多API都是NSObject的子类,那么你就必须要使用引用类型,也就是class。在其他情况下,这里有一些指导建议你可以参考:
使用值类型的情形:
&使用==运算符比较实例数据的时候。
&你想单独复制一份实例数据的时候。
&当在多线程环境下操作数据的时候。
使用引用类型(比如class)的情形:
&当使用===运算符判断两个对象是否引用同一个对象实例的时候。
&当上下文需要创建一个共享的、可变的对象时。
在Swift中,Array、String、Dictionary都是值类型。它们的使用方式类似C语言中得int,每一个实例都有一份数据。你不需要进行显示的复制操作去防止数据在你不知情的情况下被修改。更重要的是,你可以跨线程进行传参而不需要考虑同步的问题,因为传递值类型很安全。秉着高安全性的精神,这种类型划分模式能帮助你在Swift中写出更加有可预测性的代码。
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量5548点击量4060点击量3713点击量3659点击量3251点击量3244点击量3231点击量3195点击量3116
&2016 Chukong Technologies,Inc.
京公网安备89C# string 到底是引用类型还是值类型
我的图书馆
C# string 到底是引用类型还是值类型
.Net 框架程序设计(修订版)中有这样一段描述:String类型直接继承自Object,这使得它成为一个引用类型,也就是说线程上的堆栈上不会驻留有任何字 符串。(译注:注意这里的“直接继承”。直接继承自Object的类型一定是引用类型,因为所有的值类型都继承自System.ValueType。值得 指出的是System.ValueType却是一个引用类型。)。&代码一:string&&str1 =&"string"&;&string&&str2 =&"string"&;&Console&.WriteLine(string&.ReferenceEquals(str1, str2));既 然String类型是引用类型,那么代码一输出的应该是False,然而事实上代码一输出时的是True。这是因为当CLR初始化的时,它会创建一个内部 的散列表,Key为字符串,Value为指向托管堆中字符串对象的引用。当构造str1时,先会去散列表中查询是否存在”string”字符串,如果不存 在那么会在托管堆中构造一个新的String对象,然后将”string”字符串和指向该对象的引用添加到散列表中,当构造str2时,由于散列表中存在 Key为”string”的引用,于是将Value值赋值给str2,那么str1和str2引用的是同一个String对象,代码一自然就返回True 了代码二:static void&&Main(string&[] args) {&string&&str =&"string"&; Change(str);&Console&.WriteLine(str); }&static void&Change(string&&str) { str =&"Changed"&; }方法传递的参数是原内容的拷贝,其过程如果用图可表示为:语句str=”Changed”之前语句str=”Changed”之后这 样可以看到原来String对象并未改变str=”Changed”只是创建一个新的String对象(其它引用类型是改变内存地址1指向的值),因此这 个方法的参数需要加上ref或者out修饰符。因此这里也可以得出字符串具有恒等性,也就是说一个字符串一旦被创建,我们就不能再将其变长、变短、或者改 变其中的任何字符。代码三:string&&str1 =&"string"&;&string&&str2 =&"system."&&+&"string"&;&string&&str3 =&"system."&&+ str1;&Console.WriteLine(string&.Equals(str3, str2));Console&.WriteLine(string&.ReferenceEquals(str2, str3));&string&&str4 =&"system.string"&;&Console&.WriteLine(string&.Equals(str4, str2));Console&.WriteLine(string.ReferenceEquals(str2, str4));根 据代码一和二的分析,代码三的输出结果为:True True True True,然而事实却不是这样,正确的结果为:True False True True。这是因为动态创建的字符串不会去查询散列表,而是直接在托管堆中创建新的String对象,如语句string str3 = “syetem.”+str1,因此用string.ReferenceEquals来比较str2和str3会返回False,而用 string.ReferenceEquals来比较str2和str4会返回True。当然可以将str3字符串手动加入到散列表中,并返回引 用:str3 = string.Intern(str3),这样用string.ReferenceEquals来比较str2和str3会返回True,至于 string.Equals都返回True的原因是String重写了Equals方法,内部会先检查两个引用是否指向同一个对象,如果是返回True, 不是则再比较各个字符。
TA的最新馆藏[转]&[转]&[转]&[转]&[转]&[转]&
喜欢该文的人也喜欢什么是值类型,什么是引用类型
概念:值类型直接存储其值,而引用类型存储对其值的引用。部署:托管堆上部署了所有引用类型。
引用类型:基类为Objcet
值类型:均隐式派生自System.ValueType:
byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。
引用类型:
string 和 class统称为引用类型。
值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
引用类型的对象总是在进程堆中分配(动态分配)。
我们来看下面一段代码:
输出结果:
值类型在栈内分配空间大小因变量类型而异;
引用类型在栈内的空间大小相同;
1. 通用类型系统
C#中,变量是值还是引用仅取决于其数据类型。
C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为IL,即编译为基于CTS类型的代码。
例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:
确保IL上的强制类型安全;
实现了不同.NET语言的互操作性;
所有的数据类型都是对象。它们可以有方法,属性,等。例如:
s&=&i.ToString();
以下关系图()说明了这几种类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。
类型类别:
C#的所有值类型均隐式派生自System.ValueType:
结构体:struct(直接派生于System.ValueType);
数值类型:
整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char);
浮点型:float(System.Single),double(System.Double);
用于财务计算的高精度decimal型:decimal(System.Decimal)。
bool型:bool(System.Boolean的别名);
用户定义的结构体(派生于System.ValueType)。
枚举:enum(派生于System.Enum);
可空类型(派生于System.Nullable&T&泛型结构体,T?实际上是System.Nullable&T&的别名)。
每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:
int i = new int();
Int32 i = new Int32();
int i = 0;
Int32 i = 0;
引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
可以用Type.IsValueType属性来判断一个类型是否为值类型:
TestType testType = new TestType ();
if (testTypetype.GetType().IsValueType)
&&&& Console.WriteLine("{0} is value type.", testType.ToString());
3.引用类型
C#有以下一些引用类型:
数组(派生于System.Array)
用户用定义的以下类型:
类:class(派生于System.Object);
接口:interface(接口不是一个&东西&,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
委托:delegate(派生于System.Delegate)。
object(System.Object的别名);
字符串:string(System.String的别名)。
可以看出:
引用类型与值类型相同的是,结构体也可以实现接口;
引用类型可以派生出新的类型,而值类型不能;
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
对于最后一条,经常混淆的是string。我曾经在一本书的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是值类型,等等。例如:
string s1 = "Hello, ";
string s2 = "world!";
string s3 = s1 + s2;//s3 is "Hello, world!"
这确实看起来像一个值类型的赋值。再如:
string s1 = "a";
string s2 = s1;
s1 = "b";//s2 is still "a"
改变s1的值对s2没有影响。这更使string看起来像值类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。
4. 值类型和引用类型在内存中的部署
经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。
MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:
object reference = new object();
关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。
再来看值类型。《C#语言规范》上的措辞是&结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)&而不是&结构体在栈上分配内存&。这不免容易让人感到困惑:值类型究竟部署在什么地方?
考虑数组:
int[] reference&=&new&int[100];
根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。
而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?
如果用WinDbg去看,就会发现它们并不在栈上,而是在托管堆上。
实际上,对于数组:
TestType[] testTypes&=&new&TestType[100];
如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。
如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。
4.2类型嵌套
引用类型部署在托管堆上;
值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
从上下文看,mc是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;
值类型字段_value1属于引用类型实例mc的一部分,所以跟随引用类型实例mc部署在托管堆上(有点类似于数组的情形);
value2是值类型局部变量,所以部署在栈上。
而对于值类型实例,即MyStruct:
根据上下文,值类型实例ms本身是一个局部变量而不是字段,所以位于栈上;
其引用类型字段_object1不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是ms的一部分,位于栈);
其引用类型局部变量_object2显然部署在托管堆上,并被一个位于栈的引用所持有。
所以,简单地说&值类型存储在栈上,引用类型存储在托管堆上&是不对的。必须具体情况具体分析
在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
SomeType[] oneTypes = new SomeType[100];
如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
该类型的主要职责用于数据存储。
该类型的共有接口完全由一些数据成员存取属性定义。
该类型永远不可能有子类。
该类型不具有多态行为。
5. 辨明值类型和引用类型的使用场合
在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
SomeType[] oneTypes = new SomeType[100];
如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
该类型的主要职责用于数据存储。
该类型的共有接口完全由一些数据成员存取属性定义。
该类型永远不可能有子类。
该类型不具有多态行为。
值类型和引用类型的区别(小结)
引用类型可以实现接口,值类型当中的结构体也可以实现接口;
引用类型和值类型都继承自System.Object类。
1)范围方面
C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
2)内存分配方面:
数组的元素不管是引用类型还是值类型,都存储在托管堆上。
引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)
3)适用场合
值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如&& int? a =& );
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
值得注意的是,引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
内容参考来自文章:
阅读(...) 评论() &

我要回帖

更多关于 new string和string 的文章

 

随机推荐