C#编程语言(七):值类型与引用类型

值类型与引用类型

2011030110083987.jpg

值类型:派生自System.ValueType类的类型是值类型,派生自ValueType的类型都会自动在栈(stack)上进行分配,因此有一个可预测的生命周期,而且非常高效。

引用类型:在继承链上没有System.ValueType的类型(如System.Type、System.String、System.Array、System.Exception以及System.Delegate)不会在栈上分配,而是在垃圾回收堆(heap)上进行分配。

另:C#中的所有基类型都是结构类型(例如:int对应System.Int32结构),结构类型是值类型;类类型是引用类型;栈的执行效率要比堆的执行效率高,可是栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑;因为结构是值类型所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用;(所以在以结构为参数传递时,最好使用ref,这样只传递地址引用,能够提高效率,同时也应注意这样结构的值也会随着方法调用而改变)

从功能上说,ValueType的唯一目的是"重写"由Object定义的虚方法来使用基于值而不是基于引用的语法:

 
  
public abstract class ValueType: object
{
protected ValueType();
public override bool Equals( object obj);
public override int GetHashCode();
public override string ToString();
}

由于值类型是基于值语法的,结构(包括所有数值数据类型如Int32等、自定义结构、枚举)的生命周期都是可预测的。但结构离开定义的作用域时,就会立即从内存中清除。

 
  
static void LocalValueType()
{
int i = 0 ; // int是System.Int32结构
Point p = new Point(); // Point是自定义结构
}
  // 出了方法作用域,i与p被清除了(已经被弹出栈)。

值类型与类类型的赋值

当把值类型赋值给另外一个时,是对字段成员逐一进行复制。对于System.Int32这样简单的数据类型,唯一需要复制的成员就是数值。而对像Point复杂点的自定义结构,就需要把Point所有的字段复制到新的结构变量中。改变新的结构变量的字段的值不会影响原结构变量的字段值。

当对引用类型进行复制时,是把原引用变量的地址指向赋给新的引用变量,两个引用变量指向同一个对象。

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
// 结构Point的定义
struct Point
{
public int X;
public int Y;
public Point( int XPos, int YPos)
{
X
= XPos;
Y
= YPos;
}
public void Increment()
{
X
++ ; Y ++ ;
}
public void Decrement()
{
X
-- ; Y -- ;
}
public void Display()
{
Console.WriteLine(
" X = {0}, Y = {1} " , X, Y);
}
}
// PointRef类型的定义
class PointRef
{
public int X;
public int Y;
public PointRef( int XPos, int YPos)
{
X
= XPos;
Y
= YPos;
}
public void Increment()
{
X
++ ; Y ++ ;
}
public void Decrement()
{
X
-- ; Y -- ;
}
public void Display()
{
Console.WriteLine(
" X = {0}, Y = {1} " , X, Y);
}
}
static void Main( string [] args)
{
Console.WriteLine(
" ***** 值类型 / 引用类型 *****\n " );
ValueTypeAssignment();
ReferenceTypeAssignment();
Console.ReadLine();
}
static void ValueTypeAssignment()
{
Console.WriteLine(
" 指定值类型\n " );
Point p1
= new Point( 10 , 10 );
Point p2
= p1;
p1.Display();
p2.Display();
p1.X
= 100 ;
Console.WriteLine(
" \n=> 改变 p1.X\n " );
p1.Display();
p2.Display();
}
static void ReferenceTypeAssignment()
{
Console.WriteLine(
" 指定引用类型\n " );
PointRef p1
= new PointRef( 10 , 10 );
PointRef p2
= p1;
p1.Display();
p2.Display();
p1.X
= 100 ;
Console.WriteLine(
" \n=> 改变 p1.X\n " );
p1.Display();
p2.Display();
}
2011030111053129.jpg

包含引用类型的值类型

默认情况下,但值类型包含其他引用类型时,赋值将产生一个引用副本。这样就有两个独立的结构,每个都包含指向内存中同一个对象的引用(也就是浅复制)。当先执行深度赋值(将引用的对象的状态完全复制到新的对象中)时,引用类型就需要实现ICloneable结构。

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
class ShapeInfo
{
public string infoString;
public ShapeInfo( string info)
{ infoString
= info; }
}
struct Rectangle
{
public ShapeInfo rectInfo;
public int rectTop, rectLeft, rectBottom, rectRight;
public Rectangle( string info, int top, int left, int bottom, int right)
{
rectInfo
= new ShapeInfo(info);
rectTop
= top; rectBottom = bottom;
rectLeft
= left; rectRight = right;
}
public void Display()
{
Console.WriteLine(
" String = {0}, Top = {1}, Bottom = {2}, Left = {3}, Right = {4} " ,
rectInfo.infoString, rectTop, rectBottom, rectLeft, rectRight);
}
}
static void Main( string [] args)
{
Console.WriteLine(
" ***** 值類型 / 引用類型 *****\n " );
ValueTypeContainingRefType();
Console.ReadLine();
}
static void ValueTypeContainingRefType()
{
Console.WriteLine(
" -> 創建 r1 " );
Rectangle r1
= new Rectangle( " 初始的信息 " , 10 , 10 , 50 , 50 );

Console.WriteLine(
" -> 把r1賦給r2 " );
Rectangle r2
= r1;
Console.WriteLine(
" -> 改變r2的值 " );
r2.rectInfo.infoString
= " 新的字符信息! " ;
r2.rectBottom
= 4444 ;
r1.Display();
r2.Display();
}
2011030111282618.jpg

可以看出,当使用r2的引用改变信息字符串的值时,r1的引用显示了同样的值(r1的引用的值也改变了)。

按值转递引用类型与按引用转递引用类型

引用类型可以作为参数传递给类型成员。但是,按按值转递一个对象与按引用转递一个对象大有不同。

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
static void Main( string [] args)
{
// 按值傳遞
Console.WriteLine( " *********按值傳遞引用類型對象***** " );
Car c
= new Car( " 寶馬 " , 100 );
Console.WriteLine(
" 按值傳遞前對象的狀態。 " );
c.Print();
SendACarByValue(c);
Console.WriteLine(
" 按值傳遞後對象的狀態。 " );
c.Print();
Console.ReadLine();
// 按引用傳遞
Console.WriteLine( " *********按值傳遞引用類型對象***** " );
Car bmw
= new Car( " 寶馬 " , 100 );
Console.WriteLine(
" 按引用傳遞前對象的狀態。 " );
bmw.Print();
SendACarByReference(
ref bmw);
Console.WriteLine(
" 按引用傳遞後對象的狀態。 " );
bmw.Print();
Console.ReadLine();

}
static void SendACarByValue(Car c)
{
// 改變c的Speed屬性的值
c.Speed = 150 ;
// 給c重新賦值(賦予新的Car對象的指向)
c = new Car( " 保時捷 " , 250 );
}
static void SendACarByReference( ref Car c)
{
// 改變c的Speed屬性的值
c.Speed = 150 ;
// 給c重新賦值(賦予新的Car對象的指向)
c = new Car( " 保時捷 " , 250 );
}
class Car
{
public int Speed { get ; set ; }
public string Name { get ; set ; }
public Car( string n, int s)
{
this .Name = n;
this .Speed = s;
}
public Car() { }
public void Print()
{
Console.WriteLine(
" 汽車{0}的速度是{1} " , this .Name, this .Speed);
}

}

2011030114015258.jpg

可以看出:

一、如果按值传递引用类型,被调用者可能改变对象的状态数据的值,但不能改变所引用的对象(不能指向另一个新的对象,即指向没有改变)。

二、如果按引用传递引用类型,被调用者可能改变对象的状态数据的值和所引用的对象(指向都改变了)。

小结

值类型与引用类型的比较:

问题

值类型

引用类型

这个类型分配在哪里?

分配在栈(stack)上

分配在托管堆(heap)上

变量是怎么表示的?

是副本

变量指向被分配的对象所占的内存

能否作为其他类型的基类?

不能,是封闭(sealed)的

能,若没有声明是封闭时可以。

能为这个类型定义构造函数吗?

能,但默认的构造函数被保留

(即自定义构造函数必须全部带有参数)

这个类型的变量什么时候消亡?

当超出定义它们的作用域时

当托管堆被垃圾回收器回收时


转载于:https://www.cnblogs.com/bruce-wong/archive/2011/03/01/1967107.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值