1. 值类型和引用类型
在C#中有大量的值类型, int,char,float…等C#的预定义的基本类型都是值类型,这里面string是一个例外。值类型直接包含值,变量引用的位置就是值在内存中实际存储的位置,存储在称为栈的内存区域内。引用类型和引用它们的变量指向数据存储位置。引用类型并不直接存储值,它们存储的是对一个内存位置的引用(内存地址),要去那个位置才能找到真正的数据。引用类型指向的内存区域称为堆。
我们需要使用自定义的类型时,自定义的引用类型很常用,就是我们常用的“类”。在这里要提一下自定义的值类型,一种是“结构”,另外一种是“枚举”。• C++中的结构是多余的东西,仅仅为了兼容C而存在。C#中的结构与类有许多实质的区别,结构是轻量级的自定义数据类型,其数据存储于栈中。
2. 结构和类的区别
比较项目 | struct | class |
1. 存储位置 | 栈 | 托管堆 |
2. 变量赋值为null | 不能 | 可以 |
3. 继承与派生 | 不支持 | 支持 |
4. 定义无参构造函数 | 不能 | 能 |
5. 构造函数初始化所有字段 | 必须 | 不必 |
6. 用new来实例化一个对象 | 不一定 | 必须 |
7. 定义析构函数 | 不能 | 能 |
8. 对象复制 | 正本与副本无关 | 浅拷贝和深拷贝 |
9. 如何回收 | 无需回收 | 自动回收机制 |
10. 参数传送 | 按值传送 | 按地址传送 |
11. 使用初始化表 | 不能 | 可以 |
12. 作为List元素能否修改 | 不能 | 可以 |
C++中,结构和类几乎是同义词,区别只在于:结构的成员默认为公开的,类的成员默认为私有的。但是在C#中,结构和类的成员默认都是私有的。
下面用一个代码实例说明一下上述区别的表现,代码选自《C#本质论4.0》,代码清单8-1:
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Text;
using System.Collections.Generic;
namespace MyProcessSample
{
struct Angle
{
//4. 结构不能定义无参构造函数,所以以下的定义是错误的
//public Angle() { }//Error
//5. 构造函数初始化所有字段,如下面的初始化必须包括全部三个字段
public Angle(int hours, int minutes, int seconds)
{
_Hours = hours;
_Minutes = minutes;
_Seconds = seconds;
}
public int Hours
{
get { return _Hours; }
}
public int _Hours;
public int Minutes
{
get { return _Minutes; }
}
private int _Minutes;
public int Seconds
{
get { return _Seconds; }
}
private int _Seconds;
public Angle Move(int hours, int minutes, int seconds)
{
return new Angle(
Hours + hours,
Minutes + minutes,
Seconds + seconds);
}
}
// Declaring a class - a reference type
// (declaring it as a struct would create a value type
// larger than 16 bytes.)
class Coordinate
{
//4. 定义无参构造函数在类中是允许的,如下
public Coordinate() { }
public Angle Longitude
{
get { return _Longitude; }
set { _Longitude = value; }
}
private Angle _Longitude;
public Angle Latitude
{
get { return _Latitude; }
set { _Latitude = value; }
}
private Angle _Latitude;
}
class MyProcess
{
static void Main(string[] args)
{
//4. 定义无参构造函数,在这里可以调用无参的构造函数,而且下面的输出为0,为什么??
Angle ag = new Angle();
Console.WriteLine(ag.Minutes);
//2. 变量赋值为null 如下的代码说明struct不能直接赋值为null
//ag = null;//Error
//而可以使用下面的方式来声明
Angle? agg = null;
if (agg.HasValue)
Console.WriteLine("Has value");
else
Console.WriteLine("=NULL");
//6. 用new来实例化一个对象
//struct可以不使用new来初始化,如ag3,并且可以对公有成员初始化,当然这样会引起数据的不安全
Angle ag2 = new Angle(10, 5, 2);
Angle ag3;
ag3._Hours = 9;
//class不可以,如cd2这样的形式初始化就会报错
Coordinate cd1 = new Coordinate();
cd1.Longitude = ag2;
Coordinate cd2;
//cd2.Longitude = ag2;//Error
Coordinate cd3 = new Coordinate { Latitude = ag2, Longitude = ag };
Angle ag4 = new Angle { _Hours = 5 };//这里说明struct也是可以使用初始化列表的,前提是这个字段是公有的
Angle ag5 = new Angle(7, 3, 1);
Angle ag6 = new Angle(9, 9, 9);
List<Angle> allAgs = new List<Angle>() { ag, ag2, ag4 };
Console.WriteLine(allAgs[0].Hours + "," + allAgs[1].Hours + "," + allAgs[2].Hours);
allAgs[0] = allAgs[1];
Console.WriteLine(allAgs[0].Hours + "," + allAgs[1].Hours + "," + allAgs[2].Hours);
allAgs[0] = ag5;
Console.WriteLine(allAgs[0].Hours + "," + allAgs[1].Hours + "," + allAgs[2].Hours);
Coordinate cd5 = new Coordinate { Latitude = ag5,Longitude=ag6 };
List<Coordinate> allCds = new List<Coordinate>() { cd1, cd3 };
Console.WriteLine(allCds[0].Latitude.Minutes + "," + allCds[1].Latitude.Minutes);
allCds[0] = allCds[1];
Console.WriteLine(allCds[0].Latitude.Minutes + "," + allCds[1].Latitude.Minutes);
allCds[0] = cd5;
Console.WriteLine(allCds[0].Latitude.Minutes + "," + allCds[1].Latitude.Minutes);
//以下说明引用类型的赋值存在深拷贝,浅拷贝的问题,直接赋值相当于浅拷贝
Coordinate cd6 = cd5;
Console.WriteLine(cd6.Longitude.Hours + "," + cd5.Longitude.Hours);
cd6.Longitude = new Angle(999, 999, 999);
Console.WriteLine(cd6.Longitude.Hours + "," + cd5.Longitude.Hours);
}
}
}
对第8条,对象的复制方式,struct的形式完全和常用值类型一样,但是class的复制就涉及到深拷贝和浅拷贝,可具体参考本站其它博文(
http://blog.youkuaiyun.com/shaopengfei/article/details/39376441)。
第11条,第12条来自陈老师的讲课资料,不过现在验证来好象有点出入,比如第11条,如果struct里有一个公有的字段,那么是可以使用初始化列表初始化的。第12条也是,当时陈老师是这么讲解的:
List<T>中,元素只能用索引器来获取,例如 a[i]。索引器返回的本是可读可写的元素,但struct元素是按值传送的,修改返回值对于List中的元素毫无影响。例如,a[i] = a[j]; C#此时发出编译错误,实在是提醒程序员别误入陷阱。但元素如果是class的,返回值则是按地址传送的,是一个引用,修改其值就是修改List中的元素, a[i] = a[j]; 是允许的。
经过验证,见上述代码的第109-122行,这些都是可以实现的。或者我对这一段理解有问题,还有待于深入的探讨。
3. 结构适合的场合
struct 类型适于表示 Point、Rectangle 和 Color 等轻量对象。 尽管使用自动实现的属性将一个点表示为类同样方便,但在某些情况下使用结构更加有效。 例如,如果声明一个 1000 个 Point 对象组成的数组,为了引用每个对象,则需分配更多内存;这种情况下,使用结构可以节约资源。 因为 .NET Framework 包含一个名为 Point 的对象,所以本示例中的结构命名为“CoOrds”。
public struct CoOrds
{
public int x, y;
public CoOrds(int p1, int p2)
{
x = p1;
y = p2;
}
}
将较小的类声明为结构,可以提高系统的处理效率。