第 7 章:类和对象
匿名类
匿名类的直接意义:根本不知道是什么类型。可以使用 var 关键字将匿名类其声明为隐式类型的变量。
var anonymousObject = new { Name = "John", Age = 47 };
第8章:值和引用
变量(包括类)在声明时初始化。
private关键字创建了不能从类外访问的字段或方法。
创建自同一个类的两个对象,它们分别能访问对方的私有数据。
clone.radius = this.radius;
Circle 类的当前实例能够访问 clone对象的私有 radius 字段。
综上,私有所指的范围是类而不是对象。
可空类型
可用于描述数据库中部分不重要数据为空时的状态。
可空值类型在行为上与普通值类型相似,但可以将 null 值赋给它。
初始化可空值类型时要用问号(?):
int? i = null;
可以将恰当值类型的表达式直接赋给可空变量。
为了判断可空变量是否包含 null,可采取和引用类型一样的测试办法:
if (i == null)
...
实参调用:ref
需要使用实参调用时,为参数的声明和引用附加ref前缀。
static void pass(ref int param){
param++;
}
static void Main(){
int num = 42;
pass(ref num);
···
}
传递未初始化的实参:out
由方法本身初始化参数,所以调用方法时不需要对实参进行初始化。
为参数的声明和引用附加out前缀。
static void pass(ref int param)
{
param = 42;
}
static void Main()
{
int num ;
pass(ref num);
···
内存管理
“栈”和“堆”这两个词来源于“运行时”的内存管理方式。
栈内存就像一系列堆得越来越高的箱子。调用方法时,它的每个参数都被放入一个箱子,并将这个箱子放到栈的最顶部。每个局部变量也同样分配到一个箱子,并同样放到栈的最顶部。方法结束后,它的所有箱子都从栈中移除。
堆内存则像散布在房间里的一大堆箱子,不像栈那样每个箱子都严格地堆在另一个箱子上方。每个箱子都有一个标签,标记了这个箱子是否正在使用。创建新对象时,“运行时”查找空箱子,把它分配给对象。对对象的引用则存储在栈上的一个局部变量中。“运行时”跟踪每个箱子的引用数量(记住,两个变量可能引用同一个对象)。一旦最后一个引用消失,运行时就将箱子标记为“未使用”。将来某个时候,会清除箱子里的东西,使之能被重用。
p.s.这一部分引用自原文。
装箱与拆箱
int i = 42;
object o = i;
i 是值类型,所以它在栈中。如果 o 直接引用 i,那么引用的将是栈。然而,根据内存管理部分笔记,所有对象的引用必须在堆上。
为了解决这类矛盾,实际运行时在堆中分配一小块内存,然后 i 的值被复制到这块内存中,最后让 o 引用该拷贝。
这种将数据项从栈自动复制到堆的行为称为装箱。
装箱完成后栈和堆上的两个暂时相同的值再无关联。
为了访问已装箱的值,必须进行强制类型转换。
int i = 42;
object o = i; // 装箱
i = (int)o; //拆箱
假如强制类型转换成功执行,编译器生成的代码会从装箱的 int 中提取出值,这个过程称为拆箱。
两种操作对性能有一定影响。
安全转换数据类型涉及的操作符
is 操作符验证 期望对象的类型。
WrappedInt wi = new WrappedInt();
...
object o = wi;
if (o is WrappedInt) {
WrappedInt temp = (WrappedInt)o; // 安全转型
...
}
is 操作符取两个操作数:左边是对对象引用,右边是类型名称。如果左边的对象是右边的类型,则 is 表达式的求值结果为 true,反之为 false。
as操作符与is类似。
WrappedInt wi = new WrappedInt();
...
object o = wi;
WrappedInt temp = o as WrappedInt;
if (temp != null){
... // 只有转型成功,这里的代码才会执行
}
as 操作符取对象和类型作为左右操作数。“运行时”尝试将对象转换成指定类型。若转换成功,就返回转换成功的结果。转换失败时, as 表达式的求值结果为 null。