求重启咲良田13百度网盘真人版百度网盘?

C#基础(31)
Java基础(1)
C#和Java中对象分为值类型和引用类型,不同类型的对象存储方式不同,如果想要深入了解程序首先要分清楚:哪些是值类型?哪些是引用类?
C#中值类型和引用类型
1、值类型分配在栈上,它的变量直接存储变量的对象。
2、引用类型分配在栈上,它的变量通常存储一个指向对象的指针,变量通过该指针来引用对象,对象存储在托管堆中。
3、值类型继承自ValueType(注意:System.Value又继承自System.Object);引用类型继承自System.Object。
4、值类型变量存储其实例数据,因此在默认情况下,值类型的参数传递不会影响参数本身;而引用类型变量存储其数据在托管堆中的地址,因此以引用方式进行参数传递会影响到参数本身,因为两个变量引用了内存中的同一块地址。
5、值类型有装箱和拆箱两种表示,运行库提供了所有值类型的已装箱形式;而引用类型只有装箱形式。
6、值类型的内存不由GC控制,作用域结束时值类型会自动释放,减少了托管堆的压力,因此具有性能上的优势。例如:通常struct比class更高效;引用类型的内存回收由GC来完成。
7、值类型是密封的(sealed),不能作为其他任何类型的基类,但是可以单继承或多继承接口;而引用类型一般都有继承性。
8、值类型不具有多态性;而引用类型具有多态性。
9、值类型变量不可为null值,值类型变量都会自动初始化为0值;而引用类型变量默认情况下创建为null值,表示没有存储任何指向托管堆的引用地址。对值为null的引用类型对象做任何操作,都会抛出NullReferenceException异常。
值类型默认值截图:
引用类型默认值截图:
Java中值类型和引用类型
Java中值类型只有八种,没看错只有八种:byte、short、int、long、float、double、char、boolean,其余的全部都是引用类型。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:16118次
积分:1026
积分:1026
排名:千里之外
原创:79篇
(5)(6)(14)(12)(32)(9)(6)C# 深入装逼(25)
C#中值类型和引用类型
本文将介绍C#类型系统中的值类型和引用类型,以及两者之间的一些区别。同时,还会介绍一下装箱和拆箱操作。
值类型和引用类型
首先,我们看看在C#中哪些类型是值类型,哪些类型是引用类型。
基础数据类型(string类型除外):包括整型、浮点型、十进制型、布尔型。  
整型(sbyte、byte、char、short、ushort、int、uint、long、ulong )
浮点型(float 和 double )
十进制型(decimal )
布尔型(bool )
结构类型(struct)
枚举类型(enum)
引用类型:
class、interface、delegate、object、string、Array
变量的初始化中,都会有一个默认值,在C#中,我们可以通过default关键字去查看某个类型的默认值。
通过default(int)可以看到,int的默认值是0,default(bool)显示布尔类型的默认值是false。
对于所有的引用类型,默认值都会是null。
注意,这里有个特殊的情况就是结构struct,如果对一个结构进行default操作,我们将得到每个结构成员的初始值状态。也就是说,值类型成员赋予值类型的默认值,引用类型成员赋予引用类型的默认值。
简单对比值类型和引用类型
下面,我们通过一个简单的例子看看。假设有一个Point类型,有x和y两个坐标成员。
同样是下面一段代码
Point p1 = new Point(5,9);
Point p2 = p1;
如果Point类型是通过结构struct实现,那么p2将会是p1的一个副本,也就是说任何一个的修改都不会影响另外一个;如果Point类型是通过类class实现,那么p2和p1的引用值将会指向同一个对象。
为了进一步了解值类型和引用类型,我们需要介绍一下栈和堆这两个基本概念。
当我们在32位系统上运行一个程序的时候,这个程序就会有一个4GB的进程运行空间。我们所要讨论的栈和堆就存放在这个4GB的空间中。
栈和堆的简介
在C#中,栈(Stack)是指调用栈(call stack);堆(Heap)是指托管堆,由.NET垃圾收集器自动管理。
这里就不对栈和堆进行详细的分析了,只是举一个简单的例子来大致描述栈和堆的工作原理。
从图中可以看到,局部变量在栈上的变化(入栈),当函数执行结束后,栈上的空间将会被清理;但是我们在堆上分配的空间始终从在,只能等待GC去帮我们清理不会被引用到的空间。
值类型和引用类型的存放
介绍过栈和堆之后,下面我们看看值类型和引用类型是怎么存放的。
对于值类型的变量,这个变量本身就代表这个值类型的值;但是,对于引用类型的变量,这个引用类型的实例是在托管堆上分配的空间,而这个变量本身只是代表一个指向托管堆实例的引用(指针)。
所以这里,我们可以对值类型和引用类型变量的存储有两个概括:
引用类型永远存储在堆里
值类型和引用(指针)永远存储在它们声明时所在的堆或栈里
如果一个值类型不是在方法中定义的,而是在一个引用类型里,那么此值类型将会被放在这个引用类型里并存储在堆上
注意:根据上面第二点概括,可以得到”值类型一定存储在栈中”这个说法是错误的。例如,我们有一个Student类,在这个类中的Age属性是一个值类型,但是这个值类型是存储在Student类实例的空间中,也就是在堆上。
class Student
public string Name { get; set; }
public int Age{ get; set; }
装箱和拆箱
由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以通过显式(或隐式)操作相互转换。
这里,可以将装箱和拆箱描述为:
装箱是将值类型转换为引用类型
拆箱是将引用类型转换为值类型
装箱/拆箱的内部操作
其实,在装箱和拆箱的过程中都对应一系列的转换,这里就通过下图表示了。
在值类型进行装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。所以在C# 2.0中就引入了泛型来减少装箱操作和拆箱操作消耗。
为一个类型的值调用ToString,Equals,GetHashCode,如果该类型没有覆盖这些方法也会发生装箱,当你调GetType方法时总是发生装箱。另外,将值作为接口表达式使用时–把它作为赋给一个接口类型的变量时,或者把它作为接口类型的参数来传递也会发生装箱,例如:IComparable x=5,会对数字5装箱
本文介绍了C#中的值类型和引用类型,以及栈和堆的基本概念。然后分析了值类型和引用类型在栈和堆中的存放。
同时,我们也了解到了:
当使用引用类型时,我们是在和指向引用类型的引用(指针)打交道,而不是引用类型本身
当使用值类型时,我们是在和值类型本身打交道
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:9284次
排名:千里之外
转载:21篇3716人阅读
我们在前面的章节已经了解了值类型和引用类型,下面我们重点讲下值类型和引用类型在内存中的分配情况。
&&&&&&& 值类型实例通常分配在栈(Stack)上,并且不包含任何指向实例数据的地址,因为变量包含了其实例数据。值类型主要包括简单类型、结构体类型和枚举类型。
&&&&&& 例如如下代码:int& iCount=50;& //声明和初始化变量iCount
&&&&&&&&&&&&&&&&&&&& &&& int iSum=iC //将iCount中的数据赋值给iSum
&&&&&&&&&&&&&&&&&&&& &&& iCount++;&&&&&&&&&&& //增加iCount对iSum无关
在内存中给值类型分配的图形标识:
&&&&&&&& 引用类型实例通常分配在堆(Heap)上,变量保存了实例数据的地址引用,主要包括以下类型:class、interface、delegate、object、string以及其他的自定义引用类型。
如下代码:
&&&&&& Class Car
&&&&&&&&&&&&& Public string M
&&&&&&&&&&&&& Public int& M
Static void Main()
& Car &&&&& Ford=new Car();
& Car &&&&& BYD=new Car();
注意:Ford、BYD中分别存放各自new Car()的内存地址,即通常说的引用。
为什么要使用结构,针对小型相关变量组,例如,矩形的坐标或系统颜色的定义及操作,因为值类型直接分配内存效率比引用类型要高,所以结构还是有自己的用武之地。通常结构用来充当不同系统之间的数据传递的载体或者用在要求效率极高的软件中,比如游戏设计中就会采用大量的结构。
结构是从C语言遗留下来的一种类型,在C#中结构使用struct关键字定义。可以包含构造方法、常量、字段、方法、属性、索引器等成员。
结构类型语法:
访问修饰符&&struct&& 结构名
&&&& 定义结构成员
&&& 定义结构方法
针对每家的消费开销做的一个小案例如下:
class Program
&&&&& public struct Spending
&&&&&&&&&&&& public string Spending_No;//开销号
&&&&&&&&&&&& public string Spending_N//开销名
&&&&&&&&&&&& public string Spending_D//开销日期
&&&&&&&&&&&& public double Spending_C//开销金额
&&&&&&&&&&&& public string Spending_M//开销备注
&&&&& static void Main(string[]args)
&&&&& //注意:区分结构型的变量可以直接赋值使用
&&&&& //类变量必须将类的地址赋给对象
&&&&& Spending mySp;
&&&&& Console.WriteLine(&请输入你的开销号:&);
&&&&& mySp.Spending_No=Console.ReadLine();
&&&&& Console.WriteLine(&请输入你的开销名:&);
&&&&& mySp.Spending_Name=Console.ReadLine();
&&&&& Console.WriteLine(&请输入你的开销日期:&);
&&&&& mySp.Spending_Date=Console.ReadLine();
&&&&& Console.WriteLine(&请输入你的开销金额:&);
&&&&& mySp.Spending_Cost=Convert.ToDouble(Console.ReadLine());
&&&&& Console.WriteLine(&请输入你的备注:&);
&&&&& mySp.Spending_Memo=Console.ReadLine();
&&&&& Console.WriteLine(&***********************************&);
&&&&& Console.WriteLine(&您的开销编号:{0}&,mySp.Spending_No);
&&&&& Console.WriteLine(&您的开销名:{0}&,mySp.Spending_Name);
&&&&& Console.WriteLine(&您的开销日期:{0}&,mySp.Spending_Date);
&&&&& Console.WriteLine(&您的开销费用:{0}&,mySp.Spending_Cost);
&&&&& Console.WriteLine(&您的开销备注:{0}&,mySp.Spending_Memo);
【struct同class的区别:
&&&&&& 1.不支持继承
&&&& 2.为值类型;class引用类型;
&&&& 3.没有缺省的构造器;
&&&& 4.使用:通常用来封装小型相关变量组,例如,矩形的坐标或库存商品的特征;类封装比较多的相关变量;
&&&& 5.思考:同样的变量数的结构和类,谁的存取效率更高;为啥是结构!!!
&&&& 6.结构不能声明默认构造函数(没有参数的构造函数)或析构函数
&&&& 7.在结构中给实例字段赋值是错误的;&&&
&&&& 8.结构可以声明构造函数,但它们必须带参数。到第6章再讲,注意提醒我!!!
&&&& 9.一个结构不能从另一个结构或类继承,而且不能作为一个类的基。所有结构都直接继承自 System.ValueType,后者继承自 System.Object。到继承再讲;
&&&& 10.结构可以实现接口。注意到接口再讲;
枚举用在什么地方?打开每个文件的属性,我们会看到“只读、隐藏、存档”;操作一个文件时,你可以采用只读、可写、追加等模式;设置系统级别时,你可能会选择紧急、普通和不紧急来定义。这些各式各样的信息中,一个共同的特点是信息的状态分类相对稳定,在.Net中可以选择以类的静态字段来表达这种简单的分类结构,但是更明智的选择显然是:枚举。
枚举定义:
访问修饰符 enum 枚举名 {值1,值2… }
n&&&&&&&&枚举是一组描述性的名称
n&&&&&&&&枚举定义一组有限的值,不能包含方法
n&&&&&&&&对可能的值进行约束
n&&&&&&&&枚举允许描述性名称表示整数值
枚举的典型应用(WinForm):
choice = MessageBox.Show(&确定要退出吗?&, &退出系统&,
&&&&&MessageBoxButtons.OKCancel, rmation);
if(choice == DialogResult.OK) //DialogResult是个枚举,指示对话框的返回值
&&&&&Application.Exit();
做一个枚举扑克牌的花色,enum Suit{Clubs,Diamonds,Hearts,Spades};
在此处Suit枚举的基础类型默认为int型,枚举的指定类型只能是除char外的8种整数类型:byte、sbyte、ushort、int、uint、long和ulong,声明为其他类型将导致编译错误。
第一个元素默认为0,然后各成员依次加1;如果有成员显式设置相应值(比如8),则后续成员仍然在此值基础上加1;
数组和集合
一中小企业,老板给员工分配了一项任务,计算公司(25人)的平均销售额。
定义25个销售变量合适不?然后销售总额除以25可以吗?
为什么需要数组,我们去旅馆(宾馆)住宿,有不同的房间类型(单人间、双人间、三人间、豪华套房等),不同的房间类型又分不同的房号(比如从301—318;401—409等),这样客户根据不同的经济条件就可以选择相应的房间类型,然后旅馆为其分配相应的房间号。
数组:数组是一个变量,存储相同数据类型的一组数据
如何使用数组四步骤:
1、声明数组
2、分配空间
a = new int[5];&
3、赋值
a [0] = 8;
4、处理数据
a [0] = a[0] * 10;&&&&&&&&&&&&
1、声明数组: 告诉计算机数据类型是什么
数据类型&&& 数组名[ ] ;
&&&&&& 数据类型[ ]&&& 数组名;
&&& int[ ] S&&&&&&&&&&&& //销售业绩
int Sales[ ];&&&&&&&&&&&& //销售业绩
String[ ] empN&&&&&&& //员工姓名
2、分配空间: 告诉计算机分配几个格子
&&&&& 声明数组并分配空间
&&&&&& 数据类型[ ]& 数组名&& =&&new&& 数据类型[大小]& ;&&&&&&&
Sales = newint[30];&& //数组为引用类型,所以,需要new
Sales = newint[6];&&&&
empName = newString[30];
3、赋值:向分配的格子里放数据
&& score[0] = 89;
score[1] = 79;
score[2] = 76;
&&&& 思考:考虑如何一次多个赋值,希望大家给出解决方法
4、对数据进行处理:计算25位员工的平均销售额
for(int i = 0; i& 25; i ++){
&&&& Sales[i] = Console.ReadLine();
&int sum = 0;
for(int index = 0;index & Sales. index++){
&&&& sum = sum + Sales [index];
avg = sum /Sales.
回顾问题:一中小企业,老板给员工分配了一项任务,计算公司(25人)的平均销售额,使用动态录入并赋值的方式计算平均销售额
Foreach循环:
Foreach(基本数据类型& 变量名/对象名& in &array&)
&&&&&&&&&&&&& 循环遍历数组中的各元素
定义二维数组:
数据类型[,]数组名=new 数据类型[行数,列数]
针对多维数组一般都采用使用循环来初始化数据
案例:求1个3行3列距阵对角线的和,数字距阵如下:
1&&&& 2&&&& 3
4&&&& 5&&&& 6
7&&&& 8&&&& 9
ClassTwoArray
&&&&&& static void Main(string[]args)
&&&&&&&&&&&&& int[,]a={{1,2,3},{4,5,6},{7,8,9}}
&&&&&&&&&&&&& int sum=0;
&&&&&&&&&&&&& for(int i=0;i&3;i++)
&&&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&&& if(i==j||i+j==2)
&&&&&&&&&&&&&&&&&&&&&&&&&&& sum+=a[i,j];
Console.WriteLine(“和为:{0}”,sum);
思考:注意体会循环中判断
接下来,我们来看一下,Main()中的参数string[]args,我们已经学过方法参数和数组了,那么Main()中的东东是什么呢?就是一个string类型的数组而已。
那么这个数组有啥用呢?
我们称之为Main()的命令行参数,使用方法是在运行时,输入数据,传递给args数组,代码如下:
class CommandLine
&&& staticvoid Main(string[] args)
&&&&&&& System.Console.WriteLine(&参数总数{0}&, args.Length);
&&&&&&& for (int i = 0; i& args.L i++)
&&&&&&&&&&& System.Console.WriteLine(&args[{0}]= [{1}]&, i, args[i]);
编译:csc CommadLine.cs
运行如下几种命令行参数,试验效果如何?
命令行输入
传递给 Main 的字符串数组
CommadLine.exe 老板 Boss CEO
CommadLine.exe老板 Boss
CommadLine.exe &老板 Boss & CEO
&老板 Boss &
实践问题:
1.如果数组声明为 int[] arr = new int[16],则数组中的元素个数为______.
&&&& A.15&&B.16&&& C.17&&& D.18
2.看一些代码,输出的结果为( )
class nameee
&& &public static void Main()
&&&& int Number1=0;
&&&& int Number2=0;
&&&& int[] Array1=new int[]{12,33,24,65,26,47,38,29,90,41,99,81};
&&&& foreach (int Ctr inArray1)
&&&&&&&&& if (Ctr%2 == 1)
&&&&&& &&&&&Number1++;&&&&&&&&&
&&&&&&&&& else
&&&&&&&&& {
&&&&&&&&&&&&&& Number2++;
&&&&&&&&&&&&&& Console.WriteLine (Car);&&& &&&&&&&&&&&
&&&&&&&&&}
A. 12,24,26,38,90&& B.33,65,47,29,41,99,81
C.Ctr&&&&&&&&&&&& D. 12, 24, 26,29,33,38, 41, 47, 65,81, 90, 99
3.如果数组声明为string [] arr = new string[5],则数组的基类型是______.
& A.string&&&B.int&&& C.long&&& D.byte
4.在数组中,它的下标即数组的第一个位置是从______开始。
& A.0&&&&& B.-1&&&& C.1&&&& D.2
在本章中,我们主要学习了:
u&&&&&&值类型和引用类型的内存分配
u&&&&&&值类型的两种新类型:结构与枚举的定义与使用
u&&&&&&数组的使用
英语词汇:
英文&&&&&&&&&&&&全文&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&中文
Struct&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 结构&&&&&&&&
Datatype&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 数据类型&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
Namespace&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 命名空间
Detail&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 详细、详情
Product&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&& &&&&&&&&&&&& 产品
Cost&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 花费
Advance&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 高级
Enum&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&& 枚举
Array&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 数组
Source&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 源
Target&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 目标
Code&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 代码
Check&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 检查
Loop&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 循环
Foreach&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&& 循环
Param&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&& 参数
Total&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 总共的
Content&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 内容
Row&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 行
Length&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 长度
Rank&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&& 排; 横列; 行列& &
Sort&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&& 排序
Clear&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& & 清空
Index&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&& &&&&&&&&&&&&& 索引
Framework&&&&&&&&&&&&&&&&& & &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 框架
Collection&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 集合
Queue&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 队列
Stack&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 堆栈
练习项目:
1.&&& 利用结构来设计一个矩形,来实现求其面积,注意(设计矩形结构的Height、Widtd,可以适当参考MSDN)
课程项目扩充:
2.猜数游戏:从键盘中任意输入某员工销售额,判断销售数组中是否包含此销售额
3.求销售数组中的最大值。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:350881次
积分:5626
积分:5626
排名:第3524名
原创:212篇
转载:62篇
评论:36条
文章:172篇
阅读:276517
(5)(15)(9)(12)(17)(25)(63)(59)(14)(8)(1)(9)(1)(8)(1)(3)(1)(3)(8)(2)(11)(9)2341人阅读
首先,什么是值类型,什么是引用类型?
在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。
值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
& & & & 引用类型(reference type):string 和 class统称为引用类型。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。
& & & & 下面的例子说明值类型和引用类型的区别,代码如下:
using System.Collections.G
using System.L
using System.T
namespace Parameters
class Program
static void Main(string[] args)
static void Dowork()
int i = 0;
// int 是值类型
Console.WriteLine(i);
Pass.value(i);
// 值类型使用的是 i 的副本,i不变
Console.WriteLine(i);
WrappendInt wi = new WrappendInt(); // 创建类 WrappendInt 的另外一个实例
Console.WriteLine(wi.Number);
// 0 // 被默认构造器初始化为 0
Pass.Reference(wi);
// 调用方法,wi 和 param 将引用同一个对象
Console.WriteLine(wi.Number);
class Pass
public static void value(int param)
param = 42; // 赋值操作使用的是值类型参数的一个副本,原始参数不受影响
public static void Reference(WrappendInt param) // 创建类 WrappendInt 的一个实例
param.Number = 42;
// 此参数是引用类型的参数
class WrappendInt
// 类是引用类型
public int N
输出结果为:
从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《》作者来说,“不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance
issues into their code.)”。这就要求我们正确理解和使用值类型和引用类型。
1. 通用类型系统
C#中,变量是值还是引用仅取决于其数据类型。
C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为IL,即编译为基于CTS类型的代码。
例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:
确保IL上的强制类型安全;实现了不同.NET语言的互操作性;所有的数据类型都是对象。它们可以有方法,属性,等。例如:
s&=&i.ToString();
说明了CTS中各个类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。
2. 值类型
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;
使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。MSDN上有。
所有的值类型都是密封(seal)的,所以无法派生出新的值类型。
值得注意的是,引 用类型和值类型都继承自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在《》中说,接口只是表示一种约定[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. 值类型和引用类型在内存中的部署
经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。
:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:
object&reference&=&new&object();
关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。
再来看值类型。《》上的措辞是“结构体不要求在堆上分配内存(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 类型嵌套
更容易让人困惑的是引用类型包含值类型,以及值类型包含引用类型的情况:
public&class&ReferenceTypeClass
&&&&private&int&_valueTypeF
&&&&public&ReferenceTypeClass()
&&&&&&&&&_valueTypeField&=&0;
&&&&public&void&Method()
&&&&&&&&int&valueTypeLocalVariable&=&0;
ReferenceTypeClass referenceTypeClassInstance&=&new&ReferenceTypeClass();//Where
is _valueTypeField?
referenceTypeClassInstance.Method();//Where
is valueTypeLocalVariable?
public&struct&ValueTypeStruct
&&&&private&object&_referenceTypeF
&&&&public&void&Method()
&&&&&&&&&_referenceTypeField&=&new&object();
&&&&&&&&object&referenceTypeLocalVariable&=&new&object();
ValueTypeStruct valueTypeStructInstance&=&new&ValueTypeStruct();
valueTypeStructInstance.Method();//Where is _referenceTypeField?And where is referenceTypeLocalVariable?
单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。
referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?
引用类型部署在托管堆上;值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:
从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;值类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类似于数组的情形);valueTypeLocalVariable是值类型局部变量,所以部署在栈上。
而对于值类型实例,即valueTypeStruct:
根据上下文,值类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上;其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈);其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。
所以,简单地说“值类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。
5. 辨明值类型和引用类型的使用场合
在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
SomeType[] oneTypes = new SomeType[100];
如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
该类型的主要职责用于数据存储。&
该类型的共有接口完全由一些数据成员存取属性定义。&
该类型永远不可能有子类。&
该类型不具有多态行为。
5. 值类型和引用类型的区别(小结)
引用类型可以实现接口,值类型当中的结构体也可以实现接口;
引用类型和值类型都继承自System.Object类。
1)范围方面
C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
2)内存分配方面:
数组的元素不管是引用类型还是值类型,都存储在托管堆上。
引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)
3)适用场合
值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如&&
int?&a&=&null;
&);引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:20678次
排名:千里之外
原创:47篇
(1)(20)(1)(18)(16)

我要回帖

更多关于 重启咲良田网盘 的文章

 

随机推荐