C# 2010 从入门到精通 学习笔记7 第8章 理解值和引用
本章内容
- 理解值类型和引用类型的区别
- 使用 ref 和 out 关键字,修改方法的参数传递方式
- 对一个 object 类型的变量进行初始化或赋值,从而对一个值进行装箱
- 对引用了已装箱(boxed)值的对象引用进行强制类型转换,从而对值进行拆箱(unbox)
8.1 复制值类型的变量和类
所有基本数据类型(如 int,float,double 和 char)统称为值类型(value type)。将一个变量声明为一个值类型时,编译器将生成代码来分配足以容纳这种值的一个内存块。
类类型(比如第 7 章讲述的 Circle 类)则有不同的处理方式。类是引用类型(reference type)的一个例子。在引用类型中,容纳的只是对内存块的引用。
注意:除了 string 类型是引用类型,C#语言的大多数基本数据类型都是值类型。本章中,对类这样的引用类型的描述同样适用于 string 类型。事实上,C#语言中,string关键字只是 System.String 类的一个别名。
parameter 和 argument 一般不加以区别。但在必要的时候,会将 argument 译成“实参”,表示它是实际传入的参数值;parameter 则会译成“参数”或“形参”,表明它只是实际参数的一个“占位符”
8.2 理解 null 值和可空值
应该尽量在声明一个变量的同时对它进行初始化。
C#允许将 null 值赋给任意引用变量。值为 null 的变量表明该变量不引用内存中的任何对象。
8.2.1 使用可空类型
利用 C#定义的一个修饰符,可以将一个变量声明为一个可空(nullable)的值类型。可空的值类型在行为上与普通的值类型相似,但你可以将一个 null 值赋给它。我们用一个问号(?)来指明一个值类型是可空的。
8.2.2 理解可为空类型的属性
可空类型对外揭示了两个属性。其中,HasValue 属性指出一个可空类型是包含一个真正的值,还是包含 null。如果包含一个真正的值,可以利用 Value 属性来获取这个值。
注意:可空类型的 Value 属性是只读的。可以利用这个属性来读取一个变量的值,但不能修改它。为了更改一个可空变量的值,请使用普通的赋值语句。
8.3 使用 ref 和 out 参数
通常,在向方法传递一个实参时,对应的参数(形参)会用实参的一个副本来初始化。随便在方法内部进行什么修改,都不会影响作为参数来传递的一个变量的原始的值。
8.3.1 创建 ref 参数
如果为一个参数(形参)附加了 ref 关键字作为前缀,该参数就会成为实参的一个别名(或者对实参的一个引用),而不再是实参的一个副本。使用 ref 参数时,向参数应用的任何操作,都会同样应用于实参,因为参数(形参)和实参引用的是同一个对象。向一个 ref 参数传递一个实参时,实参也必须附加 ref 关键字作为前缀。这个语法明确告知用户:实参可能发生改变。
8.3.2 创建 out 参数
你可能希望由方法本身来初始化参数,所以希望向其传递一个未初始化的实参。
out 关键字与 ref 关键字非常相似。可以为参数(形参)附加 out 前缀,使参数成为实参的一个别名。和使用 ref 一样,向参数应用的任何操作都会同时应用于实参。为 out 参数
传递一个实参时,实参也必须附加 out 关键字作为前缀。
传递一个实参时,实参也必须附加 out 关键字作为前缀。
关键字 out 是 output(输出)的简称。在向方法传递一个 out 参数之后,必须在方法内部对其进行赋值。
由于 out 参数必须由方法来赋值,所以在调用方法时不需要对实参进行初始化。
注意:ref 和 out 修饰符除了能应用于值类型的参数,还能应用于引用类型的参数。效果是完全一样的。参数将成为实参的一个别名。让参数引用一个新构造的对象,实际就是让实参引用那个对象。
8.4 计算机内存的组织方式
操作系统和运行时(runtime)通常将用于容纳数据的内存划分为两个独立的区域,每个区域都采取不同的方式进行管理。这两个区域通常称为栈(stack)和堆(heap)。栈和堆的设计目标是完全不同的。
调用一个方法时,它的参数以及它的局部变量需要的内存总是从栈中获取。方法结束后(要么正常返回,要么抛出一个异常),为参数和局部变量分配的内存将自动归还给栈,并可在另一个方法调用时重新使用。
使用 new 关键字来创建一个对象(类的一个实例)时,构造对象所需的内存总是从堆中获取。使用引用变量,同一个对象可以从几个地方引用。对象的最后一个引用消失之后,对象占用的内存就可供重用(虽然并不一定被立即回收)。
注意:所有值类型都是在栈上创建的,所有引用类型(对象)都是在堆上创建的(虽然引用本身还是在栈上)。可空类型实际是引用类型,所以是在堆上创建的。
8.4.1 使用栈和堆
堆内存是一种有限的资源。如果堆内存被耗尽,new 操作将抛出一个OutOfMemoryException,对象创建将失败。
8.5 System.Object 类
所有类都是 System.Object 的一个具体化的类型;可以使用 System.Object 来创建一个变量,这个变量能引用任何引用类型。
8.6 装箱
将一个数据项从栈(stack)自动复制到堆(heap)的行为称为装箱(Boxing)。
重要提示:如果修改一个变量的原始值,那么不会修改堆(heap)上现有的值,因为它只是一个副本。类似地,修改堆上的值,变量的原始值不会发生改变。
8.7 拆箱
为了访问已装箱的值,必须进行一次强制类型转换(cast);这个操作会检查是否能够安全地将一种类型转换成另一种类型,然后执行转换。
装箱和拆箱都会产生较大的开销,因为它们涉及不少检查工作,而且需要分配额外的堆(heap)内存。
装箱有一定的用处,但滥用会严重影响程序的性能。
8.8 数据类型的安全转换
C#语言提供了两个相当有用的操作符,可以帮助你以更优雅的方式执行强制类型转换,这就是 is 和 as 操作符。
8.8.1 is 操作符
is 操作符取两个操作数:左边是对一个对象的引用,右边是一个类型名称。如果左边的对象是右边的类型,则 is 表达式的求值结果为 true,反之为 false。
8.8.2 as 操作符
和 is 操作符一样,as 操作符取一个对象和一个类型作为其左边和右边的操作数。“运行时”会尝试将对象转换成指定的类型。如果转换成功,就返回转换成功的结果。