C#学习笔记之结构体

时间:2023-03-08 18:46:08
C#学习笔记之结构体

1.概述

结构是一种与类相似的数据类型,不过它较类更为轻量,一般适用于表示类似Point、Rectangle、Color的对象。基本上结构能办到的类全都能办到,但在某些情况下使用结构更为合适,后面会有提到。

结构具有以下特点:

  • 结构可以实现接口。
  • 结构可以声明带参数的构造函数。
  • 结构不能声明默认构造函数(没有参数的构造函数)或析构函数。
  • 结构是值类型,而类是引用类型。
  • 实例化结构体时可以不使用new运算符。
  • 结构类型是不可抽象、隐式密封的,故不能使用abstract和sealed修饰符。
  • 在结构中声明字段时,字段无法被初始化,除非字段加上关键字const或static。
  • 一个结构不能从另一个结构或类继承,而且不能作为一个类的基。所有结构都直接继承自 System.ValueType,后者继承自 System.Object。
  • 结构在赋值时进行复制。将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。 不过结构仍然可以使用ref和out参数引用的方式传值给函数成员。另,在使用值类型的集合(如 Dictionary<string, myStruct>)时,请务必记住这一点。

2.结构体声明

    //结构体的声明
public struct Rectangle
{
public double x,y;
public static double z = 5;//结构体里的字段只有加上static或者const关键字才能被初始化 //结构体里的构造函数必须是带参数的
public Rectangle(double x1, double y1)
{
x= x1;
y= y1;
Console.WriteLine(x.ToString()+" "+y.ToString());
}
} class Prgram1
{
static void Main(string[] args)
{
//结构体的初始化
Rectangle rect1;
Rectangle rect2 = new Rectangle();
Rectangle rect3 = new Rectangle(5, 5); //输出各个的值
Console.Write("rect1:");
Console.WriteLine("x = {0}, y = {1}", rect1.x, rect1.y); Console.Write("rect2:");
Console.WriteLine("x = {0}, y = {1}", rect2.x, rect2.y); Console.Write("rect3:");
Console.WriteLine("x = {0}, y = {1}", rect3.x, rect3.y); }
} /* 输出:
rect1: x = 0, y = 0
rect2: x = 0, y = 0
rect3: x = 5, y = 5
*/

示例1

3.结构体赋值

由于结构体是值类型,故将一个结构体赋给新变量时,同时将复制它的所有数据,但是对新副本所做的修改不会影响原结构体。简单的说就是赋值时只复制值不复制地址。示例如下:

    class Prgram2
{
static void Main(string[] args)
{
Rectangle rect = new Rectangle(10, 10);
Rectangle rect1 = rect;
rect1.width = 5;
rect1.length = 5; Console.Write("rect:");
Console.WriteLine("x={0},y={1}",rect.length,rect.width); Console.Write("rect1:");
Console.WriteLine("x={0},y={1}", rect1.length, rect1.width);
}
}
/* 输出:
rect: x = 10, y = 10
rect1: x = 5, y = 5
*/

示例2

但在这里我有过小小的疑问,MSDN中在说到结构体的赋值时提到:“结构可用作可以为 null 的类型,因而可向其赋 null 值”,这句话有点饶,乍一看就想一个值类型怎么能赋空值呢。后来仔细看了一下这句话提到的“可以为null的类型”,才发现它指的是Nullable结构即可空类型,这下明白了。对于值类型如果要赋空值,可以使用Nullable结构。用法就是Nullable<Rectangle>或者更方便的Rectangle?,关于可空类型园子里讲的很多,这里就不多说了。

4.结构体的装箱拆箱

大家都知道,装箱就是值类型到引用类型转变的过程,拆箱就是引用类型到值类型转变的过程。对于结构体来说当然也是这样,当一个结构体转换成Object或者它所继承的接口类型时,就是装箱,反之则是拆箱。值得一提的是,当我们把结构体转换后,再次改变结构体的值,转换成的引用对象的值是不变的,因为转变过后该引用对象就是不再是原来的值对象了。示例如下:

    interface IPolygon
{
int EdgeNum { get; set; }
} public struct Rectangle : IPolygon
{
private int edgenum;
public int EdgeNum
{
get { return edgenum; }
set { edgenum = value; }
}
} class Prgram3
{
static void Main(string[] args)
{
Rectangle rect = new Rectangle();
rect.EdgeNum = 4;
Console.WriteLine("rect.EdgeNum={0}", rect.EdgeNum);//4 //隐式装箱为接口类型
IPolygon polygon = rect as IPolygon;
polygon.EdgeNum = 8;
Console.WriteLine("Changed interface.");
Console.WriteLine("polygon.EdgeNum={0},rect.EdgeNum={1}",polygon.EdgeNum,rect.EdgeNum);//8,4 //改变结构体的值再输出
rect.EdgeNum = 5;
Console.WriteLine("Changed Struct.");
Console.WriteLine("polygon.EdgeNum={0},rect.EdgeNum={1}", polygon.EdgeNum, rect.EdgeNum);//8,5
}
}

示例3

5.结构体的构造函数

关于结构体的构造函数,有以下几点需要知道:

当结构包含引用类型作为成员时,必须显式调用该成员的默认构造函数,否则该成员将保持未赋值状态且该结构不可用。

当我们声明一个结构体时,编译器会自动为其生成一个隐含的构造函数,该构造函数会将结构体中的每个字段初始化为默认值表中显示的默认值,而这个构造函数是不允许被替换的,故结构体不能自定义无参的构造函数。

当结构体中含有私有成员或以其他方式设置的不可访问成员时,这些成员只能在有参构造函数中进行初始化。并且当定义有参构造函数时,一定要完成所有字段的初始化,如果没有完成所有字段的初始化,编译时会发生错误。

结构体可以使用静态的构造函数,其使用方法与类的静态构造函数相类似。当结构体的第一个实例成员或静态成员或显示声明的构造函数被调用前,静态构造函数会被调用,且在整个过程中只被调用一次。

    public struct Rectangle
{
public double x,y;
public static int edgeNum; static Rectangle()
{
Console.WriteLine("The static constructor invoked");
} public void Test()
{
Console.WriteLine("The Test function invoked");
} public Rectangle(double x1,double y1)
{
x = x1;
y = y1;
}
} class Prgram4
{
static void Main(string[] args)
{
Rectangle.edgeNum = 4;
Console.WriteLine("Rectangle.edgeNum = {0}", Rectangle.edgeNum); Rectangle rect = new Rectangle(5, 5);
Console.WriteLine("rect.x={0},rect.y={0}", rect.x, rect.y); Rectangle rect1=new Rectangle ();
rect1.Test(); Console.Read();
}
} //上面三种都可以触发静态构造函数,但只会出现一次"The static constructor invoked"

示例4

6.结构体的多态和继承

结构体并不像类那样存在继承,它是隐式密封的,它可以实现接口但不能指定基类,并且由于这个特性它的成员的声明不能加protected和internal关键字。

结构体的成员不能是abstract或者virtual,因而voerride修饰符只适用于重写从System.ValueType继承的方法。

关于结构体为什么是不可继承的,下面这段话解释的很好:

为什么设计编程语言时将结构设计成无继承性?­
其实类的继承是有相当的成本的 ——由于继承性,每个类需要用额外的数据空间来存储“继承图”来表示类的传承历史,
通俗地说来就是我们人类的家族家谱,里面存储着我们的祖宗十八代,只有这样我们才知道我们从哪里来的,而家谱肯定是需要额外的空间来存放的。
大家不要觉得这个存放“继承图”的空间很小,如果我们的程序需要用10000个点(Point)来存放游戏中的人物形体数据的话,
在一个场景中又有N个人,这个内存开销可不是小数目了。所以我们可以通过将点(Point)申明成 Struct而不是class来节约内存空间。

7.结构体的适用场合

结构体适合一些小的数据结构,如Point、Rectangle、Color等,如果它们使用类类型的话,为了引用每个对象,则需分配更多内存;这种情况下,使用结构可以节约资源。或者如果确定数据结构不会用到一些面向对象的特性,那么结构体与类相比是个更好的选择。

--------------------------------------------分----------------割--------------线---------------------------------------------------

本文算是梳理了C#中有关结构体的基础知识,虽然写了不少,可基本上没有涉及到更深,像一些更底层的东西都没有说到。而且由于技术有限难免有所疏漏错误,如果您看出来请不吝指出,谢谢。

本文参考学习自以下地址,谢谢原作者们的贡献:

MSDN-使用结构

C#之结构

C# 结构体 struct

C# - 结构体转换为接口引用时隐式装箱的影响