《CLR via C#》读书笔记--基元类型、引用类型和值类型

时间:2022-03-25 23:21:13

编程语言的基元类型

编译器直接支持的数据类型称为基元类型基元类型直接映射到Framework类库中存在的类型。例如:C#中的int直接映射到System.Int32类型。下表给出了C#基元类型与对应的FCL类型

《CLR via C#》读书笔记--基元类型、引用类型和值类型

C#规范中写道从风格上说,最好是使用关键字,而不是使用完整的系统类型名称,但作者认为最好使用FCL类型名称

许多开发人员纠结于到时使用string还是String。由于C#的string关键字直接映射到System.String,所以两者没有区别。有些人说应用程序在32位操作系统运行,int代表32位整数;在64位系统运行,int代表64位系统。这是错误的。C#的int始终映射到System.Int32所以始终代表32位系统。

C#对操作结果总是进行截断,而不是向上取整。

除了转型,基本类型还能写成字面值,就是类型本身的实例。

Console.WriteLine(123.ToString()+456.ToString()); //123456

编译器在编译时就能完成表达式求值,从而增强应用程序性能。

checked和unchecked基元类型操作

C#通过提供checkedunchecked操作符来提供这种灵活性。以下为使用了unchecked操作符的例子:

Uint32 invalid = unchecked(UInt32)(-1);// OK

以下使用了checked操作符;

Byte b = 100;
b =checked((Byte)(b+200)); // 抛出OverflowException异常

也可以这样使用:把需要检查的放到一个语句块中

checked{
Byte b = 100;
b = (Byte)(b+200);
}

建议:

  1. 尽量使用有符号数值类型,而不是无符号数值类型。这允许编译器检查更多的上溢、下溢错误。
  2. 写代码时,如果代码可能发生你不希望的溢出,就把这些代码放到checked语句中。同时捕捉OverflowException异常,得体的从错误中恢复。
  3. 写代码时,将允许发生溢出的代码放到unchecked块中,比如在计算校验和时。

引用类型和值类型

CLR支持两种类型:引用类型值类型引用类型托管堆分配,C#的new操作符返回对象的内存地址--即指向对象数据的地址。使用引用类型必须清楚以下四个事实

  1. 内存必须从托管堆分配
  2. 堆上分配的每个对象都有一些额外成员,这些成员必须初始化
  3. 对象中的其它字节总是设为零
  4. 从托管堆分配对象时,可能强行执行一次垃圾回收。

使用值类型能缓解托管堆的压力

任何称为的类型都是引用类型,如System.ExceptionSystem.IO.FileStream类等。所有值类型都被称为结构枚举,如System.Int32结构Boolean结构Decimal结构System.TimeSpan结构等。所有结构都是System.ValueType的直接派生类。System.ValueType又直接从System.Object派生。所有枚举从System.Enum派生,后者从System.ValueType派生。

演示引用类型和值类型的区别

《CLR via C#》读书笔记--基元类型、引用类型和值类型

引用类型和值类型让我想起了报纸和网址的区别。使用引用类型就像给了别人一个网址,你在这边修改数据,别人看到的也会修改。而值类型的话更像是给了别人一份报纸的拷贝,你在这边修改,只是像在报纸上做笔记,别人不能看到修改。有些不是很恰当,看上面的例子就会理解了。

如果使用new操作符,C#会认为实例已被初始化:

SomeValue v1 = new SomeValue();
Int32 a = v1.x;//可以通过编译,v1已被初始化为0

而如果

SomeValue v1;
Int32 a = v1.x;//使用了可能未赋值的字段x

对于许多值类型,我们都建议将全部字段标记为readonly

值类型和引用类型的区别:

  • 值类型有两种形式:未装箱已装箱,引用类型总是处于已装箱模式
  • 值类型重写了EqualsGetHashCode方法。由于这个默认实现存在性能问题,在定义自己的值类型时,应该重写EqualsGetHashCode方法,并提供他们的显示实现
  • 值类型能不应引入任何虚方法,不能是抽象的方法
  • 引用类型变量中包含堆中对象的地址,变量创建时会被初始化为null,表明当前不是有效对象。试图使用null引用类型会跑出NullReferenceException异常,相信初学者会经常遇到。
  • 值类型复制会逐字段的复制,引用类型复制只复制内存地址
  • 两个或多个引用变量可能引用堆中同一对象,而值类型不会,它自成一体。

值类型的装箱和拆箱

public static void Go() {
ArrayList a = new ArrayList();
Point p; // Allocate a Point (not in the heap).
for (Int32 i = 0; i < 10; i++) {
p.x = p.y = i; // Initialize the members in the value type.
a.Add(p); // Box the value type and add the
// reference to the Arraylist.
}
}
// Declare a value type.
private struct Point { public Int32 x, y; }

point是值类型,添加到ArrayList中要发生装箱,造成性能损失。

装箱时要发生的事情:

  • 在托管堆中分配内存。
  • 值类型的字段赋值到新分配的内存
  • 返回对象地址,现在改地址是对象引用,值类型成了引用类型。

注意FCL现在包含一组新的泛型集合集。应该使用List而不是ArrayList。好处有:泛型集合类允许开发人员使用值类型时不需要对集合中的项进行拆箱和装箱(最重要);增强了编译时的类型安全性等等

拆箱的步骤:

  1. 获取已装箱Point对象中的各个Point字段的地址。
  2. 将字段包含的值复制到基于栈的值类型空间中。

第五章内容还剩下一半,明天发。顺便发下我的博客网址L:www.kuiblog.com,谢谢。一起交流