您的位置:首页 > 其它

Chapter5 基元类型、引用类型和值类型

2013-01-14 16:41 267 查看

1.1 编程语言的基元类型

下面给个C#基元类型与对应的FCL类型
表格:

C#基元类型

FCL类型

CLS相容

说明

sbyteSystem.SByte有符号8位值
byteSystem.Byte无符号8位值
shortSystem.Int16有符号16位值
ushortSystem.UInt16无符号16位值
intSystem.Int32有符号32位值
uintSystem.UInt32无符号32位值
longSystem.Int64有符号64位值
ulongSystem.UInt64无符号64位值
charSystem.Char16位Unicode字符
floatSystem.SingleIEEE32位浮点值
doubleSystem.DoubleIEEE64位浮点值
boolSystem.Boolean一个true/false值
decimalSystem.Decimal一个128位高精度浮点值(常用于金融计算)
stringSystem.String一个字符数组
object System.Object所有类型的基类型
dynamicSystem.Object对于CLR,和object完全一致
讨论点:使用FCL类型还是用基元类型
C#语言规范称:从风格上说,最好使用关键字(基元类型)。
作者赞同:我情愿使用FCL类型名称,完全避免使用基元类型名称。
作者的理由:
1.例如:有些程序员认为,int在32位的机器上代表32位整数,在64位的机器上代表64位整数。其实呢这是跟操作系统无关的,int始终映射到System.Int32,如果使用Int32一目了然,就不会产生那样的误解了。
2.例如:在C#中,long映射到System.Int64,而在C++中long视为一个Int32,甚至大多数语言都不将long看作一个关键字。
--其实我也是赞同作者的观点。
checked和unchecked基元类型操作
都是溢出给逼得,控制溢出的一个办法使用/checked+编译器开关,如果发生溢出,抛出OverflowException异常
除了全局性的打开和关闭溢出检查,C#提供了checked和unchecked来对特定代码溢出检查,类似于try catch块。
作者建议:
1.尽量使用有符号数值类型代替无符号数值类型(无符号数值类型是CLS不相容的)
2.如果可能会发生不希望的溢出,把这些可能发生的代码放在checked块中

1.2 引用类型和值类型

CLR支持的两种类型:引用类型和值类型
在使用引用类型时必须注意到一下问题:
1.内存必须从托管堆中分配
2.在堆上分配的每个对象都有些额外的成员,这些成员必须初始化
3.对象的其他字节(为字段而设)总是设为零
4.从托管堆上分配一个对象时,可能强制执行一次垃圾回收操作

一下演示值类型与引用类型的区别:



//引用类型
class SomeRef { public Int32 x; }
//值类型
struct SomeVal { public Int32 x;}
static void ValueTypeDemo()
{
SomeRef r1 = new SomeRef();//在堆上分配
SomeVal v1 = new SomeVal();//在栈上分配
r1.x = 5; //提领指针
v1.x = 5; //在栈上修改
Console.WriteLine(r1.x); //显示5
Console.WriteLine(v1.x); //同样显示5

SomeRef r2 = r1; //只复制引用指针
SomeVal v2 = v1; //在栈上分配并复制成员
r1.x = 8; //r1.x和r2.x都会更变
v1.x = 9; //v1.x会更改,v2.x不变
}


1.3 值类型的装箱与拆箱

装箱与拆箱的概念太一般了,总之值类型与引用类型之间互相转换就会引发装箱与拆箱行为,而这个过程对性能有负面影响
例如:

void Method()
{
Int32 i=0;
Object o=i;
Int32 j=(Int32)o;
}


理解一下上述代码在内存中是如何变化的
如图:



解释装箱操作时内部发生的事情:
1.在托管对中分配好内存,分配的内存量=值类型各个字段需要的内存量+托管堆中所有对象都有的两个额外成员(类型对象指针和同步索引块)需要的内存量
2.值类型的字段复制到新分配的堆内存。
3.返回对象的地址,现在这个地址是对一个对象的引用,值类型现在是个引用类型。
解释拆箱操作时内部发生的事情:
1.获取以装箱对象的各个字段的地址(如果“对已装箱值类型实例引用”的变量为null,就会抛出NullRefrenceException异常)
2.将这些字段包含的值从堆中复制到基于栈的值类型实例中

未装箱的值类型是比引用类型更“轻”的类型,归于以下两个原因:
1.它们不再托管堆上分配。
2.它们没有堆上的每个对象都有的额外成员,也就是类型对象指针与同步索引块。

通过以下例子来验证对值类型、装箱和拆箱的理解程度

//Point是个值类型
internal struct Point
{
private Int32 m_x,m_y;
public Point(Int32 x,Int32 y)
{
m_x=x;
m_y=y;
}
public void Change(Int32 x,Int32 y)
{
m_x=x;
m_y=y;
}
public override string ToString()
{
return String.Format("({0},{1})", m_x, m_y);
}
}

static void Main(string[] args)
{
Point p = new Point(1, 1);
//调用WriteLine之前会对p进行装箱,WriteLine会在已装箱的Point上调用ToString
Console.WriteLine(p);//显示(1,1)
//该方法将P在栈上的m_x和m_y字段的值都该为2
p.Change(2, 2);
//再次对p进行装箱,WriteLine会在已装箱的Point上调用ToString
Console.WriteLine(p);//显示(2,2)

//第三次装箱,将已装箱的p对象的引用赋予o
Object o = p;
Console.WriteLine(o);//显示(2,2)

/*我们希望通过Change方法来更新已装箱的Point对象中的字段
* 首先转型为一个Point,就是对o拆箱,
* 并将已装箱的Point中的字段复制到线程栈上的一个临时Point中
* 这个临时的Point的m_x和m_y字段会编程3和3
* 但已装箱的Point不受这个影响,所以会再次显示(2,2)
*/
((Point)o).Change(3, 3);
Console.WriteLine(o);//显示(2,2)
}
现在的问题所在是,我们不能更改已装箱值类型中的字段,不过我们可以使用接口来欺骗C#
//接口定义了一个Change方法
internal interface IChangeBoxedPoint
{
void Change(Int32 x, Int32 y);
}
//Point是个值类型
internal struct Point:IChangeBoxedPoint
{
private Int32 m_x,m_y;
public Point(Int32 x,Int32 y)
{
m_x=x;
m_y=y;
}
public void Change(Int32 x,Int32 y)
{
m_x=x;
m_y=y;
}
public override string ToString()
{
return String.Format("({0},{1})", m_x, m_y);
}
}
//接口方法如何修改一个已装箱的值类型中的字段
static void Main(string[] args)
{

//对p进行装箱,更改它已装箱的对象,然后丢弃它
((IChangeBoxedPoint)p).Change(4, 4);
Console.WriteLine(p); //显示(2,2)
//更改已装箱的对象,并显示
((IChangeBoxedPoint)o).Change(5, 5);
Console.WriteLine(o);//显示(5,5)
}


1.4对象哈希码

能将任何实例放到一个哈希表集合中
System.Object提供了虚方法GetHashCode,它能获取任意对象的Int32哈希码
如果一个类型重写了Equals方法,那么还应重写GetHashCode方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: