结构体 - 它的作用是什么?

8

我了解struct类型的一些内容。但是我不明白:它有什么作用?何时应该使用?类、简单值类型和枚举 - 这就是我需要的全部东西。

有什么建议吗?

更新:请!不要告诉我struct在堆栈中(我知道这个)。struct的作用是什么?


这不是你问题的答案,但如果你使用结构体,请避免使用可变结构体:https://dev59.com/0XRC5IYBdhLWcg3wAcM3 - Meta-Knight
答案是,它们是值类型存储在堆栈上,而不是引用类型存储在堆中。所以答案只是你说你不想要的答案。 - CaseyB
2
+1:这一定是一个好问题,因为至少有一半的提交答案都有多个踩的。 ;) - Juliet
请注意:http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx - RCIX
请翻译以下与编程有关的内容,从英文到中文。只返回翻译后的文本:dupe:https://dev59.com/questions/UHVD5IYBdhLWcg3wVKEb - RCIX
显示剩余3条评论
10个回答

22

如果你想要值类型语义,那么你选择结构体。如果你想要引用类型语义,那么你选择类。其他所有问题都次于这个问题。


5
这确实是正确的(正如你在其他地方所发表的优秀评论一样),但可能并不太有帮助:我认为大多数会问这个问题的人显然不理解引用类型和值类型语义之间的区别。 - Jeff Sternal
2
但问题是“struct - 它是用来干什么的?”,而不是“值类型和引用类型之间有什么区别。” 如果发帖人想知道这个问题,他应该在评论中问,甚至更好的方法是打开一个新的问题。 我相信很多人也会乐意回答那个问题。 - jason
3
这个答案当然是正确的,但如果您能解释一下其中的区别,那就更好了……有几个区别。 - Stan R.
@Stan R.:正如我在回答Jeff Sternal时提到的,我认为这更适合在一个独立的问题中讨论。结构体和类分别是值类型和引用类型语义类型的实现;它们并未定义该概念。 - jason
1
@Jason。我同意Jason的看法,但我们不能像那样挑剔每一个问题。如果我们这样做,我们永远不会得到答案。虽然有很多SO问题回答了这个确切的问题,但我不知道为什么这个问题还没有被关闭。 - Stan R.

10

MSDN提供了一份指南:在类和结构之间进行选择

如果类型的实例很小且通常具有短生命周期,或者通常嵌入到其他对象中,请考虑定义结构而不是类。

除非该类型具有以下所有特征,请勿定义结构:

  • 它逻辑上表示单个值,类似于原始类型(例如整数、双精度等)。
  • 它的实例大小小于16个字节。
  • 它是不可变的。
  • 它不会经常被装箱。

6
我讨厌那个指南,因为它谈论了性能问题、结构/堆栈类/堆实现细节等无关紧要的内容。这些问题对于主要的关注点——语义——来说是次要的。 - jason
@Jason:有三种实体类型:可变值类型、可变引用类型和不可变类型。除了可变的结构体继承自引用类型外,它们在语义上与引用类型没有太大区别。我个人认为,可变值类型有时很有用,并且认为对它们的大多数抱怨实际上是针对.NET如何处理它们的限制(例如无法指定自定义装箱运算符),但有些人却讨厌它们。 - supercat

6

应该是一个结构体的东西(因为它们是值):

  • struct Color
  • struct Point
  • struct Rectangle
  • struct GLVertex(包含位置、颜色、法线和纹理坐标)
  • struct DateTime

应该是一个类的东西(因为它们是你要引用的东西):

  • class RandomGenerator
  • class Socket
  • class Thread
  • class Window

为什么呢? 看看下面的代码。

class Button
{
    public Point Location { get; set; }
}

class Program
{
    public static void Main()
    {
        var button = Util.GetButtonFromSomewhere();
        var location = button.Location;
        Util.DrawText("one", location);
        location.Y += 50;
        Util.DrawText("two", location);
        location.Y += 50;
        Util.DrawText("three", location);
    }
}

这将绘制3个垂直对齐的文本标签。但是,如果Point是一个类,这也会移动按钮,这真的很意外:var location = button.Location感觉应该复制一个值,而不是引用!换句话说,我们认为Point是一个值类型而不是引用类型。这里的“value”是以数学意义的“value”使用的。考虑数字5,它是一个抽象对象,“在某个地方”,你只是“引用”它。类似地,一个Point只是存在。它没有存在的地方,我们无法更改它。因此,我们选择将其作为结构体,以便它具有用户期望的语义。
另一方面,我们可以有class Button { public Window Parent { get; set; } }。在这里,Parent是一个实体,所以我们用引用类型表示它-Window。使用像myButton.Parent.Redraw();这样的代码可能是有意义的。因此,Window应该是一个类。
到目前为止一切都很好。但是这些可能对您来说听起来太模糊了。如何真正确定某个东西是"感觉"像引用还是值呢?我的经验法则很简单: Foo a = b; a.Mutate(); 应该做什么?
如果似乎应该不改变 b,则使 Foo 成为结构体。
否则将其作为类。
在这里使用最小惊讶原则

回答“struct vs class”的最佳方法通常是通过提出“什么应该…”的问题,尽管我会用“a.x=5”代替“a.Mutate()”;我还要注意到vb.net和C#有一些令人讨厌的限制,可能需要使用类来实现某些值语义(例如,几乎不可能使包含数组的结构体表现得像值类型)。 - supercat

4

简单的值类型最好通过结构体实现。

Struct Usage Guidelines

It is recommended that you use a struct for types that meet any of the following criteria:

* Act like primitive types.
* Have an instance size under 16 bytes.
* Are immutable.
* Value semantics are desirable.

您还需要了解类实例是在堆上分配的。结构体是值类型,分配在栈上。


好的。但是,如果使用结构体的原因仅仅是为了内存优化,那么...你是否在使用反射?重型SQL查询?LINQ?我认为这些任何一项都会与结构体的优化重叠。 - lak-b
1
结构体并不总是分配在栈上。选择结构体的主要原因与其分配在栈上的实现细节没有任何关系。 - jason
你所给出的解释有一点值得思考:http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx - Pedro
我并不是说结构体只有在内存优化方面才有用。 结构体是创建值类型的理想工具,这就是我的观点。Jason: 在.NET中,你如何创建一个分配在堆上的结构体? - Frederik Gheysels
@Frederik Gheysels:被装箱的结构体、作为类成员的结构体以及因为是匿名方法或迭代器块中的外部变量而提升的结构体都分配在堆上。 - jason
@Pedro:很抱歉,但你应该仔细阅读我的帖子,并重新阅读Eric Lippert的文章。在那篇文章中,Eric说“值类型并不总是分配在堆栈上”。这是正确的,因为您也可以使用类(例如System.String)来实现值类型。但是,我说过结构体是分配在堆栈上的,而结构体是值类型。我没有说“值”类型分配在堆栈上。这就像我说:“企鹅是一种鸟,企鹅不能飞”,然后你得出结论说“鸟不能飞”。 - Frederik Gheysels

4

首先,您必须了解值类型和引用类型之间的区别。我假设您已经知道这是什么,因为您要求跳过此部分。

Struct是一个值类型,您可以获得使用值类型时所拥有的所有特权。

  • 结构体是按值传递的。例如:

    DateTime time = new DateTime(); DateTime newTime = time; //您不是在引用time //相反,您创建了一个新实例

  • 结构体并不是轻量级的类,它们可能有很多方法,只看DateTime结构就知道了。

  • 结构体在性能上可能更轻量级,但并非总是如此。考虑将大型结构体传递给一个方法。因为结构体是值类型,每次将其传递到一个方法中时,都会创建一个结构体的新实例,因此每次都会复制结构体。如果您有一个相当大的结构体,这将是一个更大的性能损失。

  • 由于结构体是值类型,因此您可能偶尔需要对其进行装箱和取消装箱。

简而言之,请使用结构体来表示内存中的原子值。


如果那些给负评的人能解释一下为什么要这样做,那就太好了。 - Stan R.
结构体不是不可变的(但我没有投反对票)。请参见https://dev59.com/OHRB5IYBdhLWcg3weXSX - Brian Rasmussen
1
@Brian。谢谢,我宁愿有人指出这一点,而不是被投票否决。我的意思是持有结构体的变量是不可变的。显然,一旦创建结构体,它就不是只读的,按照定义它是可变的..除非你创建一个不可变的结构体(我认为这是一个好习惯)。 - Stan R.

3

当你想要一个具有值语义(而不是引用语义)的“类”时,可以使用结构体。


假设您想要一个复数类型来存储复数。复数类型应该像int一样行为 - 因为它们都是某种数字。您不能将Point声明为类,因为类是引用类型。表达式“Complex a = b”应该将“a”的值分配为b的值,而不是引用它。 - el.pescado - нет войне

3
结构体是用于表示其身份由其属性存储的值而不是ID或键定义的东西的对象。这些被称为“值类型”,与被称为“实体类型”的对象相对应,其身份随时间而持续存在,并且不依赖于对象属性的值。
例如,Person类(实体)具有一个身份,即使从一个会话到另一个会话,甚至从一年到另一年,该身份也会持续存在,尽管Person的地址、电话号码、雇主等在不同实例之间可能会发生变化。如果您无意中同时在内存中拥有两个Person类的实例,它们代表同一个个体/实体,则重要的是它们具有相同的属性值。
另一方面,CalendarMonth对象(值类型)仅由指定其是哪个日历月份的值定义身份...无论您使用多少个“2009年3月”,它们都是可互换和等效的。另一个示例可能是表示税务程序中财政年度指定的对象。一个很好的例子是地址对象。(这里不谈论地址与人或企业等任何实体的关联,仅涉及地址本身)。更改地址的几乎任何属性都会使其成为不同的地址。一旦在内存中创建了地址对象,它就等效并可互换地与具有相同属性的每个其他地址对象。这就是为什么这些值类型通常应该是不可变的原因。当需要时,请使用所需的属性值创建一个新对象,并在使用完后将其丢弃。

1

如果你不知道为什么需要它,那么你可能不需要它。

结构体是值类型而不是引用类型。如果你不知道这意味着什么,那么你可能不需要它。


但是也许我错过了什么?我可以不使用枚举编码,但我想这样做会不太方便。 - lak-b
结构体和枚举不是同一回事。你为什么要提到它们呢? - Erik Funkenbusch
@Mystere 他使用枚举作为反例来反驳你的逻辑。仅仅因为他不知道结构体的用途并不意味着这种知识对他没有用处。同样地,他可以不理解枚举,但是这种知识会让他的生活更加轻松。 - Ross
我并没有说这些知识没有用处,我的观点是他可能还没有达到一个理解结构体和类之间区别对他的决策重要性的水平。即使我知道它们的区别,在我的编程中也很少需要使用结构体。 - Erik Funkenbusch
MystereMan,我尊重但强烈不同意您的观点。如果您不了解结构体和枚举之间的关系,那么也许您就“不需要”使用它们...显然,您并不欣赏结构体何时以及如何有用,否则您不会对它们如此消极。 - Charles Bretana
@CharlesBretana - 我想你误解了。我并没有对结构体或枚举持负面态度。也许你应该重新阅读一下。 - Erik Funkenbusch

1
例如:假设您想要一个数据类型来表示三维坐标中的 X、Y、Z 坐标。您不需要任何功能,只需要三个变量。这种情况下使用结构体可能更加合适,使用类可能过于复杂。

谢谢这个 :) 但是,我认为现在这样的优化是无用的:当使用反射和大型SQL查询(ORM中的3个连接)时,谁真正关心这些小事情呢(哈哈 :)我只看到一个有用的东西 - 结构体不能为NULL。 - lak-b
1
选择正确的工具也是一件很重要的事情。当我看到一个结构体时,我立刻就知道它的预期。对于像我的例子这样的东西,一个简单的结构体感觉更加正确。 - JimDaniel

0
实际上,我认为struct是C语言的一个遗产。我不认为我们必须在任何情况下都要使用它。也许有时你会觉得将一些东西留在堆栈上而不是堆中更有效率;但由于Java/C#从来不以效率为首要立场,所以只需忽略它即可:) 这是我的观点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接