URLhttps://learnscript.net/zh/programming/data/structures/
    复制链接转到说明  示例

    结构介绍,结构与类的区别

    我被代码海扁署名-非商业-禁演绎
    阅读 6:35·字数 1979·发布 

    前提

    阅读本节的前提是对值类型,引用类型,栈,堆等概念有所掌握,你可以查看栈,堆,指针介绍一节来了解他们。

    需要说明,本节的“类₁”一词,表示与结构无关的类,如果语言将所有数据类型均视为类的话。

    结构

    结构(Structure)也被称为结构体,他会通过一系列成员来存储数据,这看上去和类₁有点相似,但类₁通常更注重功能,而结构则以数据为重点,即便在某些语言中,结构可以拥有自己的方法。

    另外,结构是值类型,而类₁通常是引用类型,虽然结构本身可能也是类的一种,但特定的约束将使其具有确定的存储空间,以符合值类型的要求。

    为何使用结构?

    想必结构的设计初衷与类是相同的,没有人愿意面对重复而枯燥的代码,在不影响可读性的情况下,少定义一些变量,何乐而不为哪?从凌乱走向简洁,没有人会拒绝。

    结构的成员

    如前所述,结构以数据为重点,因此,字段是他的标准成员,如果你将值类型和引用类型的不同,视为结构和类₁的本质区别,那么结构拥有类₁的其他特点将是可以理解的。比如在 C# 中,从代码上看,结构和类₁的界线已经越来越模糊了,结构的成员已经拓展到了属性,方法,构造器。

    下面的 C# 结构MyDate拥有三个字段YearMonthDay,以及一个构造器。

    structures.cs
    // 结构 MyDate,表示日期
    struct MyDate
    {
    	// 字段 Year,表示年份
    	public int Year;
    	// 字段 Month,表示月份
    	public int Month;
    	// 字段 Day,表示一月中的第几天
    	public int Day;
    
    // 构造器 public MyDate(int year, int month, int day) { // 初始化结构的字段 Year = year; Month = month; Day = day; } }

    结构的成员访问级别

    对于结构的某些成员,比如字段,其访问级别应该默认为公共,原因很简单,既然侧重于数据,那么定义的字段多半是提供给外部访问的。当然,是否默认为公共,语言会有自己的决定。

    结构和运行效率

    值类型和默认的值传递,可能会使采用结构的逻辑运行效率降低,当结构包含大量数据并且代码中存在频繁的赋值行为时,因为这将导致需要进行反复且代价不菲的结构创建。

    如何解决结构可能带来的效率问题?

    这里有两种可能的方案,将值传递改为引用传递,或者,将结构改为类₁。引用传递可以减少结构的创建次数,本应被创建的副本会被本体取代,这种方案需要语言给予支持。改为类₁只能是尝试性的,因为类₁的创建同样是耗时的,如果不能从逻辑上大量减少其创建次数,那么你将不会从使用类₁中受益。

    无论采用哪种方案,请确保原本需要实现的业务目标不会受到影响。当然,检查设计也是必要的,或许结构本身就不应该包含如此众多的数据。

    创建结构MyDate的实例后,尝试通过先赋值再设置的方式修改月份,发现结果显示的月份依然为1,这是因为赋值产生了新的MyDate实例。

    *.cs
    // 创建结构 MyDate 的实例
    MyDate date = new(2024, 1, 16);
    
    // 尝试增加月份 for (int i = 0; i < 5; i++) { // 由于结构是值类型,默认的值传递将产生一个新的结构实例 MyDate newDate = date; newDate.Month++; }
    Console.WriteLine($"日期为:{date.Year}-{date.Month}-{date.Day}");
    日期为:2024-1-16

    将定义变量newDate的语句改为引用传递的方式后,再次运行程序,月份显示为6

    *.cs
    // 采用引用传递不会产生新的实例,修改月份得到了满意的效果
    ref MyDate newDate = ref date;
    日期为:2024-6-16

    结构的引用类型成员

    虽然结构是值类型,但其成员却可以是引用类型,当该成员存储某个类₁的实例时。事实上,该措施是必须的,因为你不可能将空间大小不确定的目标放入栈中,取而代之的只能是对该目标的引用。当然,这意味着结构实例和他的副本可能会用到某个类₁的同一个实例。

    下面,为结构MyDate增加一个引用类型的字段Todo

    structures.cs
    // 结构 MyDate,表示日期
    struct MyDate
    {
    	// …
    	// 字段 Todo,表示当天需要完成的事情
    	public TODO Todo = new();
    	// …
    }
    
    // 类 TODO,表示需要完成的事情 class TODO { // 属性 Content,表示事情的内容 public string Content { get; set; } = string.Empty; }

    创建MyDate的实例,并通过赋值的方式产生一个该实例的副本,通过dateTodoTodo字段修改TODO实例的Content属性,发现dateRest也受到了影响,因为他们使用了同一个TODO实例。

    *.cs
    // 创建 MyDate 的实例 dateRest
    MyDate dateRest = new(2000, 1, 16);
    // 修改 Todo 表示的 TODO 实例的 Content 属性
    dateRest.Todo.Content = "没什么事情";
    
    // 创建 MyDate 的实例 dateTodo MyDate dateTodo = dateRest; // 修改年份 dateTodo.Year = 2024; // 修改 Todo 表示的 TODO 实例的 Content 属性 dateTodo.Todo.Content = "有事了";
    Console.WriteLine($"{dateRest.Year}-{dateRest.Month}-{dateRest.Day}{dateRest.Todo.Content}"); Console.WriteLine($"{dateTodo.Year}-{dateTodo.Month}-{dateTodo.Day}{dateTodo.Todo.Content}");
    2000-1-16:有事了
    2024-1-16:有事了

    源码

    structures.cs·codebeatme/programming·GitHub