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

值类型与引用类型

值类型:派生自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所有的字段复制到新的结构变量中。改变新的结构变量的字段的值不会影响原结构变量的字段值。

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

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();
}

包含引用类型的值类型

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

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();
}

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

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

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

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);
}

}

可以看出:

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

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

小结

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

问题

值类型

引用类型

这个类型分配在哪里?

分配在栈(stack)上

分配在托管堆(heap)上

变量是怎么表示的?

是副本

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

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

不能,是封闭(sealed)的

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

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

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

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

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

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

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


posted @ 2011-03-01 14:31 黄宝强 阅读(...) 评论(...) 编辑 收藏