C#中的值类型和引用类型

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

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类型的数据结构,用于存放变量,例如:

int n = 0;

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

int* p = (int*)malloc(sizeof(int) * 20);

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

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

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

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

using System;

namespace Edu.Study.OO.ValueType {

	/// <summary>
	/// 定义一个值类型,使用struct关键字定义
	/// 值类型可以实现接口,但无法继承其它类
	/// </summary>
	public struct StructAge : ICloneable {
		
		/// <summary>
		/// 值类型中存在一个字段,字段一般为值类型
		/// </summary>
		private int value;
	
		// 一般而言,我们需要给值类型类提供一个只读静态字段,作为类的初始值
		// 一般初始化为一个无效值
		public readonly static StructAge Empty = new StructAge();
	
		/// <summary>
		/// 值类型类可以具备构造器,但默认构造器总是默认存在并且无法重新定义
		/// </summary>
		public StructAge(int age) {
	
			// 值类型类,构造器中无法使用属性
			// this.Value = age;
			// 只能够对字段赋值
			if (age <= 0 || age > 120) {
				throw new OverflowException();
			}
			this.value = age;
		}
	
		/// <summary>
		/// 属性声明
		/// </summary>
		public int Value {
			get {
				return this.value;
			}
			set {
				if (value <= 0 || value > 120) {
					throw new OverflowException();
				}
				this.value = value;
			}
		}
	
		/// <summary>
		/// 对象复制方法,实现ICloneable接口。返回当前对象的拷贝
		/// </summary>
		public object Clone() {
			return new StructAge(this.value);
		}
	}
	
	
	class Program {
		static void Main(string[] args) {
			// 声明一个值类型变量,其值等于StructAge.Empty值
			StructAge sage = StructAge.Empty;
			sage.Value = 22;
		
			// 令变量other的值等于sage的值
			StructAge sother = sage;
			// 改变sother对象的Value属性值
			sother.Value = 23;
	
			// 可以看到,更改sother对象的值,并不影响sage的值
			Console.WriteLine(sage.Value);
			Console.WriteLine(sother.Value);
	
			// 重新初始化other变量的值
			// new操作符在这里的含义,只是让sother等于一个新的StructAge值,值类型不存在引用
			sother = new StructAge(55);
			Console.WriteLine(sother.Value);
	
			// 调用Clone方法,让sage的值等于sother对象的值
			sage = (StructAge)sother.Clone();
			Console.WriteLine(sage.Value);
	
			// 改变sage的值,可以发现,同样对sother的值无影响
			sage.Value = 100;
			Console.WriteLine(sage.Value);
		}
	}
}


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

using System;

namespace Edu.Study.OO.ValueType {

	/// <summary>
	/// 我们声明一个引用类型作为比较
	/// </summary>
	public class ClassAge : ICloneable {
		/// <summary>
		/// 引用类型的字段可以为任何类型
		/// </summary>
		private int value;
	
		// 引用类型变量可以初始化为null,所以无需
		// public readonly static StructAge Empty = new StructAge();
	
		/// <summary>
		/// 在引用类型中,如果我们声明了参数构造器,并且我们需要默认构造器,则必须手工声明默认构造器
		/// </summary>
		public ClassAge() {
			this.value = 0;
		}
	
		/// <summary>
		/// 在引用类型中的构造器可以访问属性
		/// </summary>
		public ClassAge(int age) {
			this.Value = age;
		}
	
		/// <summary>
		/// 属性声明
		/// </summary>
		public int Value {
			get {
				return this.value;
			}
			set {
				if (value <= 0 || value > 120) {
					throw new OverflowException();
				}
				this.value = value;
			}
		}
	
		/// <summary>
		/// 对象复制方法,实现ICloneable接口。返回当前对象的拷贝
		/// </summary>
		public object Clone() {
			return new ClassAge(this.value);
		}
	}
	
	
	class Program {
		static void Main(string[] args) {
			// 定义引用类型变量,可以为null
			ClassAge cage = null;
			// new运算符声明一个对象,使用变量cage保存其引用
			cage = new ClassAge(22);

			// = 运算符将cage中保存的引用传递给cother
			ClassAge cother = cage;
			// 通过变量cother更改对象的Value属性
			cother.Value = 23;

			// 可以发现,cage和cother引用到了相同的对象上
			Console.WriteLine(cage.Value);
			Console.WriteLine(cother.Value);

			// cother变量引用到一个新的对象上,new运算符生成了新的对象,返回新的引用
			// cother中原来引用的对象被丢弃
			cother = new ClassAge(55);
			Console.WriteLine(cother.Value);

			// 调用Clone方法,得到cother所引用对象的一个副本
			cage = (ClassAge)cother.Clone();
			Console.WriteLine(cage.Value);

			cage.Value = 110;
			Console.WriteLine(cother.Value);
			Console.WriteLine(cage.Value);
		}
	}
}

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

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值