C#类和对象(四)——结构

    前面介绍了类如何封装程序中的对象,也介绍了如何将它们存储在堆中,通过这种方式可以在数据的生存期上获得很大的灵活性,但性能会有一定的损失。因为托管堆的优化,这种性能损失比较小。但是,有时仅需要一个小的数据结构。此时,类提供的功能多于我们需要的功能,由于性能原因,最好使用结构。看看下面的例子:

public class Dimensions

 {

public double Length { get; set; }

public double Width { get; set; }

}

    上面的代码定义了类Dimensions,它只存储了某一项的长度和宽度。假定编写一个布置家具的程序,让人们试着在计算机上重新布置家具,并存储每件家具的尺寸。表面看来使字段变为公共字段会违背编程规则,但这里的关键是我们实际上并不需要类的全部功能。现在只有两个数字,把它们当成一对来处理,要比单个处理方便一些。既不需要很多方法,也不需要从类中继承,也不希望.NET运行库在堆中遇到麻烦和性能问题,只需要存储两个double类型的数据即可。

    为此, 只需 修改 代码, 关键字 struct 代替 class, 定义 一个 结构 而 不是 类, 如 本章 前面 所述:

public struct Dimensions

{

public double Length { get; set; }

public double Width { get; set; }

}

    为结构定义函数与为类定义函数完全相同。下面的代码说明了结构的构造函数和属性:

public struct Dimensions

{

public double Length { get; set; }

public double Width { get; set; }

public Dimensions( double length, double width)

{

Length = length;

Width = width;

}

public double Diagonal => Math. Sqrt( Length * Length + Width * Width);

}

结构是值类型,不是引用类型。它们存储在栈中或存储为内联(如果它们是存储在堆中的另一个对象的一部分),其生存期的限制与简单的数据类型一样。

●结构不支持继承。

●对于结构,构造函数的工作方式有一些区别。如果没有提供默认的构造函数,编译器会自动提供一个,把成员初始化为其默认值。

●使用结构,可以指定字段如何在内存中布局。

因为结构实际上是把数据项组合在一起,所以有时大多数或者全部字段都声明为public。严格来说,这与编写.NET代码的规则相反——根据Microsoft,字段(除了const字段之外)应总是私有的,并由公有属性封装。但是,对于简单的结构,许多开发人员都认为公有字段是可接受的编程方式。

下面将详细说明类和结构之间的区别。

1、结构是值类型

虽然结构是值类型,但在语法上常常可以把它们当作类来处理。例如,在上面的Dimensions类的定义中,可以编写下面的代码:

var point = new Dimensions();

point. Length = 3;

point. Width = 6;

注意,因为结构是值类型,所以new运算符与类和其他引用类型的工作方式不同。new运算符并不分配堆中的内存, 而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段。对于结构,可以编写下述完全合法的代码:

Dimensions point;

point. Length = 3;

point. Width = 6;

如果Dimensions是一个类,就会产生一个编译错误,因为point包含一个未初始化的引用——不指向任何地方的一个地址,所以不能给其字段设置值。但对于结构,变量声明实际上是为整个结构在栈中分配空间,所以就可以为它赋值了。但要注意下面的代码会产生一个编译错误,编译器会抱怨用户使用了未初始化的变量:

Dimensions point;

double D = point. Length;

2、结构和继承

结构遵循其他数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。在结构上调用new运算符,或者给所有的字段分别赋值,结构就完全初始化了。当然,如果结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0。结构会影响性能的值类型,但根据使用结构的方式,这种影响可能是正面的,也可能是负面的。正面的影响是为结构分配内存时,速度非常快,因为它们将内联或者保存在栈中。在结构超出了作用域被删除时,速度也很快,不需要等待垃圾回收。负面影响是,只要把结构作为参数来传递或者把一个结构赋予另一个结构(如A=B,其中A和B是结构),结构的所有内容就被复制,而对于类,则只复制引用。这样就会有性能损失,根据结构的大小,性能损失也不同。注意,结构主要用于小的数据结构。但当把结构作为参数传递给方法时,应把它作为ref参数传递,以避免性能损失——此时只传递了结构在内存中的地址,这样传递速度就与在类中的传递速度一样快了。但如果这样做,就必须注意被调用的方法可以改变结构的值。

结构不是为继承设计的。这意味着:它不能从一个结构中继承。唯一的例外是对应的结构(和C#中的其他类型一样)最终派生于类System.Object。因此,结构也可以访问System.Object的方法。在结构中,甚至可以重写System.Object中的方法——如重写ToString()方法。结构的继承链是:每个结构派生自System.ValueType类,System.ValueType类又派生自System.Object。ValueType并没有给Object添加任何新成员,但提供了一些更适合结构的实现方式。注意,不能为结构提供其他基类:每个结构都派生自ValueType。

3、结构的构造函数

为结构定义构造函数的方式与为类定义构造函数的方式相同。前面说过,默认构造函数把数值字段都初始化为0,且总是隐式地给出,即使提供了其他带参数的构造函数,也是如此。

C#6中,也可以实现默认的构造函数,为字段提供初始值(这一点在早期的C#版本中未实现)。为此,只需要初始化每个数据成员:

public Dimensions()

{

Length = 0;

Width = 1;

}

public Dimensions( double length, double width)

{

Length = length;

Width = width;

}

另外,可以像类那样为结构提供Close()或Dispose()方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

掌控自身命运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值