C#中结构类型和类类型在语法上非常相似,他们都是一种数据结构,都可以包括数据成员和方法成员。结构是一种值类型,并且不需要堆分配。 结构的实例化可以不使用 new 运算符。
总体评价:
在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。 结构类型永远不是抽象的,并且始终是隐式密封的,因此在结构声明中不允许使用abstract和sealed修饰符。结构不能声明默认构造函数(没有参数的构造函数)或析构函数,但可以声明带参数的构造函数。
所有结构都直接继承自System.ValueType,后者继承自 System.Object。 结构在赋值时进行复制。将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。
在使用值类型的集合(如 Dictionary<string, myStruct>)时,请务必记住这一点。 结构类型的变量直接包含了该结构的数据,而类类型的变量所包含的只是对相应数据的一个引用(被引用的数据称为“对象”)。但是结构仍可以通过ref和 out参数引用方式传递给函数成员。 结构可用作可以为 null 的类型,因而可向其赋 null 值。
1、结构和类的区别
结构和类的区别:
1、结构是值类型,它在栈中分配空间;而类是引用类型,它在堆中分配空间,栈中保存的只是引用。
2、结构类型直接存储成员数据,让其他类的数据位于对中,位于栈中的变量保存的是指向堆中数据对象的引用。
3、结构类型可以有实例构造函数和静态构造函数,但不能有析构函数。
C#中的简单类型,如int、double、bool等都是结构类型。如果需要的话,甚至可以使用结构类型结合运算符运算重载,再为C#语言创建出一种新的值类型来。
和类的对比:
结构 类
数据类型 值类型 引用类型
是否必须使用new运算符实例化 否 是
是否可声明无参数的构造函数 否 是
数据成员可否在声明的同时初始化 声明为const或static可以,数据成员不可以 可以
直接派生自什么类型 System.ValueType 有
是否有析构函数 无 有
可否从类派生 否 可以
可否实现接口 可以 可以
实例化时在栈还是在堆分配内存 栈 堆,栈中保存引用
该类型的变量可否被赋值为null 否 可以
可否定义私有的无参构造函数 否 可以
是否总有一个默认的无参构造函数 是 否
无论结构使用预定义的、无参数的构造函数,还是使用用户定义的、有参数的构造函数进行初始化,都会初始化结构的数据成员。不过预定义的,无参的 会将数值型初始化为默认值,引用类型初始化为null;而用户自定义的初始化策略对个成员进行初始化。因此结构类型的数据成员不允许在声明是显式初始化。
由于结构是值类型,并且直接存储数据,因此在一个对象的主要成员为数据且数据量不大的情况下,使用结构会带来更好的性能。当为结构分配内存,或者当结构超出了作用域被删除时,性能会非常好,因为他们将内联或者保存在堆栈中。当把一个结构类型 的变量赋值给另一个结构时,对性能的影响取决于结构的大小,如果结构的数据成员非常多而且复杂,就会造成损失。
结构和类的适用场合分析:
1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
2、对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。
struct A { public int x; //不能直接对其进行赋值 public int y; public static string str = null; //静态变量可以初始化 public A(int x,int y) //带参数的构造函数 { this.x = x; this.y = y; Console.WriteLine("x={0},y={1},str={2}", x, y,str); } } class Program { static void Main(string[] args) { A a = new A(1,2); A a1 = a; a.x = 10; Console.WriteLine("a1.x={0}",a1.x); Console.Read(); } }
二、结构体的定义与赋值
2.1 结构体的声明
public struct AddressBook
{
//字段、属性、方法、事件
}
对于类而言,两个变量指向同一个对象的情况是存在的,因此对这样两个变量中的任意一个进行操作,起结果必然会影响另外一个,因为它们指向的是同一个对象。
结构体是值类型,直接包含它自己的数据,每个结构都保存自己的一份数据,修改每一个结构的数据都不会对其他结构的数据造成影响。
如果从结构中创建一个对象,并将该对象赋给某个变量,则该变量包含结构的全部值。复制类型为结构的变量时,将同时复制该结构所持有的所有数据。由于结构不是引用类型,因此结构类型的变量不能被赋予null值。
public class Program { static void Main(string[] args) { PersonStruct p1, p2; //与类一样,但可以不new p1.Name = "张飞"; p1.MobilePhone = "13553663108"; p1.Birthday = DateTime.Now.AddYears(-10); p2 = p1; //将p1的值赋给p2 //由于是值类型,因此赋值等于将全部值全部复制到p2的栈空间 p2.Name = "关羽"; //然后修改p2的值看是否会影响p1 Console.WriteLine(p1.Name); //输出 张飞 没影响 PersonClass p3 = new PersonClass(); p3.Name = "张飞"; p3.MobilePhone = "13553663108"; p3.Birthday = DateTime.Now.AddYears(-10); PersonClass p4 = new PersonClass(); p4 = p3; //将p3的值赋给p4 赋值后,由于是引用类型,因此两个对象指向的是同一个地址(堆空间) p4.Name = "关羽"; //然后修改p4的值看是否会影响p3 Console.WriteLine(p3.Name); //输出 关羽 有影响 Console.ReadKey(); } } public class PersonClass { public string Name; public string MobilePhone; public DateTime Birthday; } public struct PersonStruct { public string Name; public string MobilePhone; public DateTime Birthday; }
将一个结构变量赋值给另一个结构变量,就是把数据从一个结构复制到另一个结构。而类则不同,在类的变量之间,复制的是引用,而不是类数据。。因此当数据比较大的时候,这种数据复制机制会带来较大的开销。
2.2 实例构造函数
结构类型都有一个预定义的,没有参数的构造函数,这点与类是一样的。此构造函数不允许删除和重定义,并且这个无参数的构造函数会一直存在,并不会因为定义了其他带参数的构造函数就消失,这一点和类不同。
注意如果没有使用new运算符,是不可以使用数据成员的值(除非已显示地设置了该数据成员的值)和调用函数成员的(除非所有数据成员均已经被赋值)。
struct A { static A() { Console.WriteLine("I am A."); } public void Fun() { } } class Program { static void Main(string[] args) { A a=new A(); a.Fun(); //结构的实例成员被引用 Console.Read(); } } 结果为:I am A.
结构不能使用默认的构造函数,只能使用带参数的构造函数,当定义带参数的构造函数时,一 定要完成结构所有字段的初始化,如果没有完成所有字段的初始化,编译时会发生错误。 结构可以使用静态构造函数吗? 可以,结构的静态构造函数与类的静态构造函数所遵循的规则大体相同。 结构的静态构造函数何时将触发呢? 结构的实例成员被引用,结构的静态成员被引用,结构显示声明的构造函数被调用。 但是创建结构类型的默认值不会触发静态构造函数。为什么结构不能自定义无参数的构造函数? 结构类型的构造函数与类的构造函数类似,用来初始化结构的成员变量,但是struct不能包含显式默认构造函数, 因为编译器将自动提供一个构造函数,此构造函数将结构中的每个字段初始化为默认值表中显示的默认值。 然而,只有当结构用new实例化时,才会调用此默认构造函数。对值类型调用默认构造函数不是必需的。
2.3 静态构造函数
和类一样,结构类型也可以有静态构造函数,静态构造函数用于初始化静态数据成员。静态构造函数有如下特点:
1、静态构造函数不能有访问修饰符和参数;
2、静态构造函数不能访问实例成员;
3、静态构造函数无法直接进行调用;
结构和类的静态构造函数的触发规则不同,类的静态构造函数是在创建第一个实例或引用任何静态成员之前自动调用的,而结构的静态构造函数在以下情况调用:
1、使用显式声明的构造函数进行初始化
2、调用结构的方法或访问结构的静态数据成员(无论是读取还是赋值,访问实例数据成员不会触发CLR自动调用静态的构造函数)。
3、结构的多态和可继承性
结构直接派生自System.ValueType,间接派生自System.Object,但结构是隐式密封的,不能作为基类在派生出其他的结构,也不能从类派生,但可以从接口派生。
结构的特性:
1、结构类型总是隐式密封的,因此在定义结构时不能使用sealed和abstract关键字;
2、因为结构不能作为基类,结构的成员不能使用如下访问修饰符:protected和protected,internal;
3、结构的函数成员不能声明为abstract和virtual,但是可以使用override关键字,用以覆写它的基类System.ValueType中的方法。
interface ITest { void Fun(int x,int y); } struct A:ITest { public void Fun(int x,int y) //隐式实现接口里的方法 { Console.WriteLine("x={0},y={1}", x, y); } } class Program { static void Main(string[] args) { A a; //结构的实例化可以不使用new a.Fun(1, 2); Console.Read(); } } // 结果为:x=1,y=2
一个结构声明可以指定实现的接口列表,但是不能指定基类。 由于结构不支持类与结构的继承,所以结构成员的声明可访问性不能是 protected 或 protected internal。 结构中的函数成员不能是 abstract 或 virtual,因而 override 修饰符只适用于重写从 System.ValueType 继 承的方法。 为在设计编程语言时将结构设计成无继承性? 其实类的继承是有相当的成本的 ——由于继承性,每个类需要用额外的数据空间来存储“继承图”来表示类的传承历史, 通俗地说来就是我们人类的家族家谱,里面存储着我们的祖宗十八代,只有这样我们才知道我们从哪里来的,而家谱肯定是需要额外的空间来存放的。 大家不要觉得这个存放“继承图”的空间很小,如果我们的程序需要用10000个点(Point)来存放游戏中的人物形体数据的话, 在一个场景中又有N个人,这个内存开销可不是小数目了。所以我们可以通过将点(Point)申明成 Struct而不是class来节约内存空间。
4、结构的装箱与拆箱
结构是值类型,因此当它被转换为object类型时,或者它所实现的接口类型的时候,就会执行装箱操作;同样,当执行相反操作的时候,就会执行拆箱操作。
一个类类型的值可以转换为 object 类型或由该类实现的接口类型,这只需在编译时把对应的引用当作另一个类型处理即可。 与此类似,一个object 类型的值或者接口类型的值也可以被转换回类类型而不必更改相应的引用。当然,在这种情况下,需要进行运行时类型检查。 由于结构不是引用类型,上述操作对结构类型是以不同的方式实现的。 当结构类型的值被转换为object 类型或由该结构实现的接口类型时,就会执行一次装箱操作。 反之,当 object 类型的值或接口类型的值被转换回结构类型时,会执行一次拆箱操作。
与对类类型进行的相同操作相比,主要区别在于: 装箱操作会把相关的结构值复制为已被装箱的实例,而拆箱则会从已被装箱的实例中复制出一个结构值。 因此,在装箱或拆箱操作后,对“箱”外的结构进行的更改不会影响已被装箱的结构。
struct Program { static void Main(string[] args) { int i = 1; object o = i; //隐式装箱 i = 123; Console.WriteLine("i={0},o={1}",i,o); Console.Read(); } } //结果为:i=123,o=1
参考文章
1. 逆心. C# 结构体 struct, 2013-5.
2. kuangkro. C#之结构struct(转), 2013-3.