值类型和引用类型

点击打开链接


  和C++不同,在C#中,bool,char,int,long,double等也是类,一个数字例如24,就是int类型的一个对象,例如:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. string s = 24.ToString();  

  我们调用了24的ToString方法,从这种我们熟悉的语法现象上可以看出,24是一个对象,它有自己的方法。

一、元类型

  .net平台有一种很有意思的机制,叫做数据类型映射机制,即可以将.net Framework定义的一系列类,以不同的名字映射到不同的语言中,所以C#的int类型实际上是一个叫做Int32的类的别名,24是Int32类型的一个对象。

  在C#中,只有很少的一部分类的对象可以使用常量来表示(例如24、true、3.14、"hello”),其它类的对象都必须使用new运算符来生成。这类可以用常量来表示的对象成为元类型对象,元类型定义在.net Framework中,System命名空间下。我们来看一下元类型在C#语言中的类型映射。


.net Framework元类型

C#类型

常量值(取值范围)字长

System.Boolean

bool

true/false 
System.Bytebyte0 - 255

无符号8位整数

System.Sbytesbyte-128 ~ 127有符号8位整数
System.Charchar0 ~ 65,535无符号16位整数
System.Int16short

-32,768 ~ 32,767

有符号16位整数
System.UInt16ushort0 ~ 65,535无符号16位整数
System.Int32int-2,147,483,648 ~ 2,147,483,647有符号32位整数
System.UInt32uint0 ~ 4,294,967,295无符号32位整数
System.Int64long

-9,223,372,036,854,775,808 ~ 
9,223,372,036,854,775,807

有符号64位整数
System.UInt64ulong

0 ~ 18,446,744,073,709,551,615

无符号64位整数
System.Singlefloat

±1.5 × 10-45 ~ ±3.4 × 1038

(7位有效数字)

32位单精度浮点数
System.Doubledouble

±5.0 × 10-324 到 ±1.7 × 10308

(15至16位有效数字)

64位双精度浮点
System.Decimaldecimal

±1.0 × 10-28 到 ±7.9 × 1028

(27至28位有效数字)

128位浮点数数
System.Stringstring任意字符串/


  以上类型的对象在C#中都可以使用“常量”来表示。例如常量”Hello”表示一个string类型的对象。

  上述元类型还有一个特点,即除了string类型外,其它均为值类型


二、值类型:

  学习过C语言的同学应该清楚,在C语言中,存在有两处重要的数据存储内存空间:栈和堆。栈是一个FILO类型的数据结构,用于存放变量,例如:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int n = 0;  

  堆是一个链式数据结构,用于分配任意大小内存空间,例如:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int* p = (int*)malloc(sizeof(int) * 20);  

  前者称为值类型变量,后者称为指针类型变量

  C语言书籍中对这两类变量有如下一段说明:栈内存结构可以快速的分配内存和回收内存,但栈内存空间有限,过分使用会发生“溢出”,所以栈用于分配常用的,占用空间较小的数据类型;堆内存结构分配内存较慢,但可利用空间较大,可以存放大型数据。

  在C#中,几乎所有的数据都存储在“堆”结构中,并且这个堆受到.net垃圾回收机制监控,称为“托管堆”,虽然.net的堆结构经过改良,内存分配效率要高于C语言的堆,但相对于栈,其效率仍显低下。并且为了能够正确的垃圾回收,每一次分配的堆空间要较实际需要空间略大,所以在小型数据上使用堆是得不偿失的。

  C#提供了一种特殊的类,值类型类,用来声明分配在栈上的对象。看如下范例:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. using System;  
  2.   
  3. namespace Edu.Study.OO.ValueType {  
  4.   
  5.     /// <summary>  
  6.     /// 定义一个值类型,使用struct关键字定义  
  7.     /// 值类型可以实现接口,但无法继承其它类  
  8.     /// </summary>  
  9.     public struct StructAge : ICloneable {  
  10.           
  11.         /// <summary>  
  12.         /// 值类型中存在一个字段,字段一般为值类型  
  13.         /// </summary>  
  14.         private int value;  
  15.       
  16.         // 一般而言,我们需要给值类型类提供一个只读静态字段,作为类的初始值  
  17.         // 一般初始化为一个无效值  
  18.         public readonly static StructAge Empty = new StructAge();  
  19.       
  20.         /// <summary>  
  21.         /// 值类型类可以具备构造器,但默认构造器总是默认存在并且无法重新定义  
  22.         /// </summary>  
  23.         public StructAge(int age) {  
  24.       
  25.             // 值类型类,构造器中无法使用属性  
  26.             // this.Value = age;  
  27.             // 只能够对字段赋值  
  28.             if (age <= 0 || age > 120) {  
  29.                 throw new OverflowException();  
  30.             }  
  31.             this.value = age;  
  32.         }  
  33.       
  34.         /// <summary>  
  35.         /// 属性声明  
  36.         /// </summary>  
  37.         public int Value {  
  38.             get {  
  39.                 return this.value;  
  40.             }  
  41.             set {  
  42.                 if (value <= 0 || value > 120) {  
  43.                     throw new OverflowException();  
  44.                 }  
  45.                 this.value = value;  
  46.             }  
  47.         }  
  48.       
  49.         /// <summary>  
  50.         /// 对象复制方法,实现ICloneable接口。返回当前对象的拷贝  
  51.         /// </summary>  
  52.         public object Clone() {  
  53.             return new StructAge(this.value);  
  54.         }  
  55.     }  
  56.       
  57.       
  58.     class Program {  
  59.         static void Main(string[] args) {  
  60.             // 声明一个值类型变量,其值等于StructAge.Empty值  
  61.             StructAge sage = StructAge.Empty;  
  62.             sage.Value = 22;  
  63.           
  64.             // 令变量other的值等于sage的值  
  65.             StructAge sother = sage;  
  66.             // 改变sother对象的Value属性值  
  67.             sother.Value = 23;  
  68.       
  69.             // 可以看到,更改sother对象的值,并不影响sage的值  
  70.             Console.WriteLine(sage.Value);  
  71.             Console.WriteLine(sother.Value);  
  72.       
  73.             // 重新初始化other变量的值  
  74.             // new操作符在这里的含义,只是让sother等于一个新的StructAge值,值类型不存在引用  
  75.             sother = new StructAge(55);  
  76.             Console.WriteLine(sother.Value);  
  77.       
  78.             // 调用Clone方法,让sage的值等于sother对象的值  
  79.             sage = (StructAge)sother.Clone();  
  80.             Console.WriteLine(sage.Value);  
  81.       
  82.             // 改变sage的值,可以发现,同样对sother的值无影响  
  83.             sage.Value = 100;  
  84.             Console.WriteLine(sage.Value);  
  85.         }  
  86.     }  
  87. }  


我们声明类似的一个引用类型来作为比较:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. using System;  
  2.   
  3. namespace Edu.Study.OO.ValueType {  
  4.   
  5.     /// <summary>  
  6.     /// 我们声明一个引用类型作为比较  
  7.     /// </summary>  
  8.     public class ClassAge : ICloneable {  
  9.         /// <summary>  
  10.         /// 引用类型的字段可以为任何类型  
  11.         /// </summary>  
  12.         private int value;  
  13.       
  14.         // 引用类型变量可以初始化为null,所以无需  
  15.         // public readonly static StructAge Empty = new StructAge();  
  16.       
  17.         /// <summary>  
  18.         /// 在引用类型中,如果我们声明了参数构造器,并且我们需要默认构造器,则必须手工声明默认构造器  
  19.         /// </summary>  
  20.         public ClassAge() {  
  21.             this.value = 0;  
  22.         }  
  23.       
  24.         /// <summary>  
  25.         /// 在引用类型中的构造器可以访问属性  
  26.         /// </summary>  
  27.         public ClassAge(int age) {  
  28.             this.Value = age;  
  29.         }  
  30.       
  31.         /// <summary>  
  32.         /// 属性声明  
  33.         /// </summary>  
  34.         public int Value {  
  35.             get {  
  36.                 return this.value;  
  37.             }  
  38.             set {  
  39.                 if (value <= 0 || value > 120) {  
  40.                     throw new OverflowException();  
  41.                 }  
  42.                 this.value = value;  
  43.             }  
  44.         }  
  45.       
  46.         /// <summary>  
  47.         /// 对象复制方法,实现ICloneable接口。返回当前对象的拷贝  
  48.         /// </summary>  
  49.         public object Clone() {  
  50.             return new ClassAge(this.value);  
  51.         }  
  52.     }  
  53.       
  54.       
  55.     class Program {  
  56.         static void Main(string[] args) {  
  57.             // 定义引用类型变量,可以为null  
  58.             ClassAge cage = null;  
  59.             // new运算符声明一个对象,使用变量cage保存其引用  
  60.             cage = new ClassAge(22);  
  61.   
  62.             // = 运算符将cage中保存的引用传递给cother  
  63.             ClassAge cother = cage;  
  64.             // 通过变量cother更改对象的Value属性  
  65.             cother.Value = 23;  
  66.   
  67.             // 可以发现,cage和cother引用到了相同的对象上  
  68.             Console.WriteLine(cage.Value);  
  69.             Console.WriteLine(cother.Value);  
  70.   
  71.             // cother变量引用到一个新的对象上,new运算符生成了新的对象,返回新的引用  
  72.             // cother中原来引用的对象被丢弃  
  73.             cother = new ClassAge(55);  
  74.             Console.WriteLine(cother.Value);  
  75.   
  76.             // 调用Clone方法,得到cother所引用对象的一个副本  
  77.             cage = (ClassAge)cother.Clone();  
  78.             Console.WriteLine(cage.Value);  
  79.   
  80.             cage.Value = 110;  
  81.             Console.WriteLine(cother.Value);  
  82.             Console.WriteLine(cage.Value);  
  83.         }  
  84.     }  
  85. }  

  通过上述例子,我们可以得出值类型的几个特点:

  • 值类型类使用struct关键字声明;
  • 值类型类不能继承自其它类(除了Object类以外),但可以实现一个或多个接口;
  • 值类型类使用值类型作为字段类型,值类型类的字段无法具有默认值;
  • 值类型类有且必须具备编译器提供的默认构造器,且无法手动定义;值类型可以具有参数构造器,但不会因为具有参数构造器而失去编译器提供的默认构造器,在值类型构造器中,可以直接访问类中的字段,但无法访问类中的属性和方法;
  • =运算符对于值类型,是值的赋值而非引用的传递;值类型变量不能为null,因为值类型变量必须有值而非引用。值类型中不存在引用的概念;

  最为重要的一点:值类型的=是值得复制而非引用的传递,所以值类型赋值是值的传递,当值从一个变量传递到另一个变量后,这两个变量之间就没有任何联系了。这一点和引用类型非常不同。注意上述两段代码,第一段代码的66-72行和第二段代码的64-70行,查看运行结构,思考原因。

  因为值类型变量无法被初始化为null值,所以一般来说值类型类都提供一个公共只读静态字段Empty(只读字段使用readonly关键字声明,后面详细介绍)。这个静态只读量初始化为一个对于这个值类型来说的无效值。用于初始化一个表示“空”的值类型变量。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值