C#结构和类的六点区别
引言··· 1
区别一:存储类型··· 3
堆和栈:··· 3
结构和类的存储类型:··· 3
区别二:继承性··· 4
区别三:初始化··· 5
区别四:构造函数··· 5
区别五:析构函数··· 7
区别六:关键字··· 7
类和结构的使用选择:··· 7
参考:··· 8
1、一览表:··· 8
2、结构和类的区别:··· 8
3、结构和类的异同:··· 8
4、源代码:··· 8
Struct,cs· 8
Class.cs· 10
Program.cs· 13
引言
我们先来看一个例子:
例1:类和结构的基本定义
上面的两个图片一个定义的是类,另一个是结构的定义。从表面上来看,两中数据类型的定义基本没什么区别,类里面有的成员结构都能有,事实上也确实如此。在c#中,两者在本质上都属于数据结构,封装着一组整体作为一个逻辑单位的数据和行为。 数据和行为是该类或结构的“成员”,它们包含各自的方法、属性和事件等(本主题后面列出了这些内容)。结构和类有很大的相似性:
1、 都是container类型,这表示它们可以包含其他数据类型作为成员。他们可以包含的内容基本相同:字段、构造函数、方法、属性、常量、事件、索引器、运算符、嵌套类型等;
2、 成员都可以分为静态和非静态,成员的类型、访问方式可以互不相同;
3、 方法(或称函数)都可以进行重载、复写等操作;
4、 都派生于System.Object;
5、 结构定义函数和类中定义函数完全相同;
6、 都能进行封装;
7、 都能响应接口;
8、 都可以通过泛型定义;
9、 都可以声明和触发事件,而且两者都可以声明委托(Delegate);
10、 方法或成员的调用方式、对象的初始化都相同;
11、 默认情况下所有的字段、方法都是私有的
结构与类在语法上有着很大的相似,但是两者也存在着很明显的区别,具体表现在下面六个方面:
区别一:存储类型
堆和栈:
“栈”(stack)和“堆”(heap)这两个词来源于“运行时”(runtime)对内存进行组织的方式:
栈内存就像一系列堆叠越高的箱子。调用方法时,它的每个参数都被放入一个箱子,并将这个箱子放到栈的最顶部。每个局部变量也同样分配到一个箱子,并同样放到栈的最顶部。方法结束之后,方法的所有箱子都会从栈中移除。
堆内存则像散布在房间里的一大堆箱子,而不像栈那样,每个箱子都严格地叠置在另一个箱子上方。每个箱子都有一个标签,它标记了这个箱子是否正在使用。创建一个新对象时,“运行时”会查找一个空箱子,并把它分配给对象。对对象的引用则存储在栈上的一个局部变量中。“运行时”将跟踪每个箱子的引用数量(记住,两个变量可能引用同一个对象)。一旦最后一个引用消失,运行时就将箱子标记为“未使用”。将来某个时候,会清除箱子里的东西,使之能真正重用。
结构和类的存储类型:
结构是值类型数据,存储在栈(stack)中。结构进行数据复制的时候,是将原来数据进行备份。创建结构时,结构赋值到的变量保存该结构的实际数据。 将结构赋给新变量时,将复制该结构。 因此,新变量和原始变量包含同一数据的两个不同的副本。 对一个副本的更改不影响另一个副本。
如:对例1中定义的结构作如下操作:
// 使用结构
static void UseStruct()
{
Date labourDay = new Date(5, 1);
Date yaoMingBirth = labourDay;
labourDay.ReadDate();
yaoMingBirth.ReadDate();
yaoMingBirth.Change(1980, 11, 8);
labourDay.ReadDate();
yaoMingBirth.ReadDate();
}
显示结果:修改其中一个变量的值,不会影响和它同一个拷贝的另外一个变量。
类是引用类型数据,存储在堆(heap)中和栈(stack)中,堆中存储的是真实的数据,栈中存储的是数据在堆中的地址。就像是在仓库里面放苹果,我们把放苹果的箱子编号,再将编号记录在一个本子上面,这个本子就是栈,仓库就是堆。
如:对例1中定义的类进行如下操作:
// 使用类
static void UseClass()
{
Birthday myBirth = new Birthday(1987, 6, 12);
Birthday liliBirth = myBirth;
myBirth.ReadDate();
liliBirth.ReadDate();
myBirth.Change(1990, 3, 16);
myBirth.ReadDate();
liliBirth.ReadDate();
}
显示结果:修改一个引用的值,另一个相同的引用的值就会发生改变。
区别二:继承性
大家都知道,类是可以继承的,它可以继承其他的;类或者接口,也可以被继承,并且,类的许多特性是通过继承来展现的,要阻止类的继承,必须显示的声明sealed。
结构没有继承:它不能继承另一个结构或者类,也不能被继承。也正因为如此,结构不能有抽象成员。虽然结构没有明确的用sealed声明,可是结构是隐式的sealed
但是,结构能够继承接口,方法和类继承接口是一样的:
例如:结构实现接口
interface IImage
{
void Paint();
}
struct Picture : IImage
{
public void Paint()
{
// painting code goes here
}
private int x, y, z; // other struct members
}
区别三:初始化
类可以声明的时候就初始化。如:
class Birthday
{
int year;
byte month;
byte day;
int count = 5;
}
但是同样的语句用到struct结构中就会发生错误,因为结构中不能在声明一个变量的时候就初始化:
注意,结构在声明全局变量的时候是可以在声明时就初始化的
区别四:构造函数
类和结构都有自己的默认构造函数(默认构造函数不带参数),也都可以编写自己的带参数的构造函数,编写的方法都一样:构造函数没有返回值,并且名称与类(或者结构相同)。但是他们是有区别的:
在类中,一旦我们编写了带参数构造函数,默认构造函数就不存在了。当我们要调用不带参数的构造函数来初始化对象时,我们必须再自己编写一个不带参数的构造函数。
如:例1 中类Birthday的构造函数:
// 构造函数:0在日期中是没有意义的,用来表示用户没有输入
public Birthday(int yy, byte mm, byte dd)
{
this.year = yy;
this.month = mm;
this.day = dd;
}
public Birthday(int yy, byte mm)
: this(yy, mm, 0)
{
}
public Birthday(int yy)
{
this.year = yy;
}
但是在结构中,始终存在一个不带参数的默认构造函数,并且,这个构造函数是不可替代的,不能重写,也不能覆盖,所以,下面操作是错误的:
public override Date()
{
this.year = 1987;
this.month = 6;
this.day = 12;
}
在结构中,我们只能编写带参数的构造函数,不能编写不带参数的构造函数。
在类中,声明构造函数的时候我们可以不用初始化所有的字段,系统的(runtime机制)会将我们忽略的字段自动初始化为该类型的零值(0,null,true)
如,类birthday:我们用构造函数:
public Birthday(int yy)
{
this.year = yy;
}
声明一个对象,系统会自动将月日初始化为0,显示的时候就只有年。
但是在结构中,我们自己编写的构造函数必须显式的为结构的所有字段赋值,否则会发生编译错误。例如,我们将构造函数稍微改动一下:
public Date(byte mm, byte dd)
{
this.month = mm;
this.day = dd;
}
编译运行不会成功,显示错误: Field 'StructAndClass.Date.year' must be fully assigned before control is returned to the caller
区别五:析构函数
类有析构函数,我们都知道,但是结构是没有析构函数的。
假如我们为结构Date添加析构方法:
~Date()
{
Console.WriteLine("谁说结构没有析构方法?");
Console.ReadKey();
}
编译会弹出错误:
Only class types can contain destructors
区别六:关键字
1、在类中可以使用但是在结构中限制使用的关键字有:abstract、sealed、protected;
2、Static关键字可以用在类名前面用来声明静态类,但是不能用在struct前面,不存在静态结构。
类和结构的使用选择:
类通常用于对较为复杂的行为建模,或对要在创建类对象后进行修改的数据建模。结构最适合一些小型数据结构,这些数据结构包 含的数据以创建结构后不修改的数据为主。
何时该用struct、何时该用class:
1、 大多数情况下该类型只是一些数据时,该类型的行为类似于基于类型,结构式最佳的选择,否则用class
2、 在表示抽象或者多层次的数据的时候,类是最好的选择:
3、 该类型不要继承自任何类型,否则用class
4、 该类型不要继承自任何类型,否则用class
5、 该类型的实例不会频繁地用于方法的参数传递,否则用class
6、 该类型的实例不会被频繁地用于诸如ArrayList、Hashtable之类的集合中,否则用class struct表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存,在此情况下,结构的成本较低;当struct变得很大时,应该用class:堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
参考:
1、一览表:
2、结构和类的区别:
http://msdn.microsoft.com/zh-cn/library/ms173109.aspx
3、结构和类的异同:
http://hi.baidu.com/loveastyy/blog/item/f15601e8eb771938b80e2da1.html
4、源代码:
Struct,cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StructAndClass
{
struct Date
{
int year;
byte month;
byte day;
// 构造函数
public Date(int yy, byte mm, byte dd)
{
this.year = yy;
this.month = mm;
this.day = dd;
}
public Date(byte mm, byte dd)
{
// 0年是没有意义的,用来做节日的标记
this.year = 0;
this.month = mm;
this.day = dd;
}
public Date(int yy)
: this(yy, 1, 1)
{
}
// 读取日期时间
public void ReadDate()
{
if (this.year == 0)
{
Console.WriteLine("{0}-{1}", this.month, this.day);
}
else
{
Console.WriteLine("{0}-{1}-{2}", this.year, this.month, this.day);
}
}
// 修改日期时间
public void Change(int yy, byte mm, byte dd)
{
this.year = yy;
this.month = mm;
this.day = dd;
}
public void Change(byte mm, byte dd)
{
Change(0, mm, dd);
}
// 重写ToString()方法
public override string ToString()
{
string s = null;
if (this.year == 0)
{
Console.WriteLine("{0}-{1}", this.month, this.day);
s = this.month.ToString() + "-" + this.day.ToString();
}
else
{
Console.WriteLine("{0}-{1}-{2}", this.year, this.month, this.day);
s = this.year.ToString() + "-" + this.month.ToString() + "-" + this.day.ToString();
}
return s;
}
}
}
Class.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StructAndClass
{
class Birthday
{
int year;
byte month;
byte day;
// 构造函数:0在日期中是没有意义的,用来表示用户没有输入
public Birthday(int yy, byte mm, byte dd)
{
this.year = yy;
this.month = mm;
this.day = dd;
}
public Birthday(int yy, byte mm)
: this(yy, mm, 0)
{
}
public Birthday(int yy)
{
this.year = yy;
}
~Birthday()
{
Console.WriteLine("没有任何争议,类是有析构方法的!");
Console.ReadKey();
}
// 读取生日时间
public void ReadDate()
{
if (this.month == 0)
{
Console.WriteLine("{0}", this.year);
}
else if (this.day == 0)
{
Console.WriteLine("{0}-{1}", this.year, this.month);
}
else
{
Console.WriteLine("{0}-{1}-{2}", this.year, this.month, this.day);
}
}
// 修改生日时间
public void Change(int yy,byte mm,byte dd)
{
this.year = yy;
this.month = mm;
this.day = dd;
}
public void Change(int yy, byte mm)
{
Change(yy, mm, 0);
}
public void Change(int yy)
{
Change(yy, 0, 0);
}
// 重写ToString()方法
public override string ToString()
{
string s = null;
if (this.month == 0)
{
Console.WriteLine("{0}", this.year);
s = this.year.ToString();
}
else if (this.day == 0)
{
Console.WriteLine("{0}-{1}", this.year, this.month);
s = this.year.ToString() + "-" + this.month.ToString();
}
else
{
Console.WriteLine("{0}-{1}-{2}", this.year, this.month, this.day);
s = this.year.ToString() + "-" + this.month.ToString() + "-" + this.day.ToString();
}
return s;
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StructAndClass
{
class Program
{
// 使用结构
static void UseStruct()
{
Date labourDay = new Date(5, 1);
Date yaoMingBirth = labourDay;
labourDay.ReadDate();
yaoMingBirth.ReadDate();
yaoMingBirth.Change(1980, 11, 8);
labourDay.ReadDate();
yaoMingBirth.ReadDate();
}
// 使用类
static void UseClass()
{
Birthday myBirth = new Birthday(1987, 6, 12);
Birthday liliBirth = myBirth;
myBirth.ReadDate();
liliBirth.ReadDate();
myBirth.Change(1990, 3, 16);
myBirth.ReadDate();
liliBirth.ReadDate();
Birthday bird = new Birthday(1988);
bird.ReadDate();
}
// 程序入口
static void Main(string[] args)
{
UseClass();
UseStruct();
Console.ReadKey();
}
}
}