visual C#(八)理解值和引用

参考书:《 visual C# 从入门到精通》
第二部分 理解C#对象模型
第8章 理解值和引用

8.1 复制值类型的变量和类

C#大多数基元类型(不包括string)都是值类型。将变量声明为值类型,编译器会生成代码来分配足以容纳这种值的内存块。

而类类型不同,声明类类型时,编译器不生成代码来分配足以容纳一个类的内存块。它会分配一小块刚好可以容纳一个地址的内存,以后该类实际占用的内存是在使用new关键字创建对象时分配的。类是应用类型的一个例子。引用类型容纳了对内存块的引用。我们要注意值类型和引用类型的区别。

对于值类型:

int i=42;
int copyi=i;//copyi为i的拷贝
i++;//i递增不会影响copyi的值 

而类类型:

Circle c=new Circle(42);
Circle refc=c;//refc和c将容纳和c一样的地址

想要复制引用类型可以提供一个方法用来拷贝。

8.2 理解null值和可空类型

考虑下面的情况:

Circle c=new Circle(42);
Circle copy=new Circle(99);
copy=c;

c赋给copy以后,copy原来引用的实例就不存在对它的任何引用。此时在运行时会通过垃圾回收机制来回收内存。但要知道垃圾回收是比较耗时的操作,所以不要创建不需要用到的对象。

C#允许将null赋值给任意引用变量,表明该变量不引用内存中的任何对象。

空条件操作符?Console.WriteLine($"The area of circle c is {c?.Area()};"),运行时若变量为null则忽略当前语句。

8.2.1 使用可空类型

注意:null本身时引用,不能赋值给值类型。int i=null;//非法

但可以将变量声明为可空值。可空值行为上与普通值类似,但可以将null赋给它。用?指定可空值:int? i=null;//合法

可将值类型表达式直接赋给可空变量,但不可将可空变量赋给值类型。

如果一个方法接受一个普通值类型的参数,就不能将一个可空变量作为实参传给它。

8.2.2 理解可空类型的属性

可空类型公开两个属性:判断类型是否实际包含非空的值(HasValue),和该值是什么(Value)。

int? i=null;
...;
if(!i.HasValue){
    i=99;
}else{
    Console.WriteLine(i.Value);
}

8.3 使用refout参数

向方法传递实参时,通常传递的时实参的拷贝,这样方法中对参数的修改并不会改变传入实参的原始值。这个机制本身是没问题的,但有时我们希望通过方法来实际的修改传入的参数。为此,C#提供了refout

8.3.1 创建ref参数

通过附加关键字ref,将传递实参的引用而不是拷贝。

static void doIncreament(ref int param){
    param++;
}
static void Main(){
    int arg=42;
    doIncreament(ref arg);
    Console.WriteLine(arg);
}

注意变量需要先初始化。

8.3.2 创建out参数

不同于ref,当需要传递未初始化的实参时就需要用到out

out参数必须要在方法中赋值。

static void doInitialize(out int param){
    param=42;
}
static void Main(){
    int arg;
    doInitialize(out arg);
    Console.WriteLine(arg);
}

8.4 计算机内存的组方式

操作系统和“运行时”通常将用于容纳数据的内存划分为两个独立的区域,每个区域都以不同方式管理。这两个区域通常称为。栈和堆的设计目标完全不同。

调用方法时,参数和局部变量所需的内存总是从栈中获取。方法结束后,参数和局部变量分配的内存将自动归还给栈,并在另一个方法调用时重新使用。

使用new创建对象时,创建对象所需内存总是从堆中获取。

栈和堆两个词来源于运行时的内存管理方式:

  • 栈内存就像一系列堆得越来越高的箱子。
  • 堆内存则像散布在房间里的一大堆箱子。

8.5 System.Object类

所有类都是System.Object类的派生类。C#提供object关键字作为它的别名。

Circle c;
c=new Circle(42);
object o;
o=c;

8.6 装箱

object类型的变量能引用任何类型的任何对象,也能引用值类型的实例。

int i=42;
object o=i;

上述代码在运行时在堆中分配了一小块内存,然后i的值被复制到这块内存中,最后让o引用该拷贝,这种将数据项从栈自动复制到堆的行为称为装箱

8.7 拆箱

为了访问已装箱的值,必须进行强制类型转换,简称转型。这个操作会检查是否能将一种类型安全转换成另一种类型,然后执行准换。

int i=42;
object o=i;
i=(int)o;

上述代码,o引用的是一个已装箱的 int,转型成功执行,编译器生成的代码会从装箱的int中提取出值,这个过程称为拆箱

8.8 数据的安全转型

C#提供两个有用的操作符,帮助我们在转型时能更加顺利的进行:is操作符和as操作符。

8.8.1 is操作符

is操作符验证对象的类型是不是自己希望的。

WrappedInt wi=new WrappedInt();
...;
object o=wi;
if (o is WrappedInt){
    WrappedInt temp=(WrappedInt)o;
    ...;
}

is左边是对对象引用,右边是类型名称。

8.8.2 as操作符

WrappedInt wi=new WrappedInt();
...;
object o=wi;
WrappedInt temp=o as WrappedInt;
if(temp!=null){
    ...;//转型成功代码才能执行
}

运行时as表达式尝试将对象转换成指定类型,成功则返回转换成功的结果,失败则表达式结果为null


另外补充一个细节:

熟悉CC++的人应该很熟悉一个重要的元素:指针。指针在使用时是非常容易因为使用不当而引发错误,造成系统的不安全。C#通过添加引用变量的方式使得指针的使用不那么必须,不用指针就不用担心误用指针引发不必要的麻烦了。但C#中还是可以像C++那样继续使用指针,但必须将代码标记为unsafe

public static void Main(string[] args){
    int x=99,y=100;
    unsafe{
        swap(&x,&y);
    }
    Console.WriteLine($"x is now {x}, y is now {y}");
}
public static unsafe void swap(int *a,int *b){
    int temp;
    temp=*a;
    *a=*b;
    *b=temp;
}

编译包含unsafe代码的程序时,必须右击项目->属性->生成->允许不安全代码(打勾)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值