首页 > 编程笔记 > C#笔记 阅读:5

C# struct结构体的用法(非常详细)

C# 语言除了提供给用户基本数据类型之外,还使用户可以通过一些功能,如结构体(struct),创建属于自己的数据类型。

结构体常应用在小数据值上,如坐标点、组织小数据或数据结构。C# 语言编译程序会将这个自建的结构数据类型视为一般数据类型,也可以为此数据类型创建变量、数组或将其作为参数传递给函数等。

C#结构体类型的创建

C#语言提供了 struct 关键词,其可以将相关的数据组织起来,成为一组新的复合数据类型,此数据类型称为记录(record),这些相关的数据可以是不同类型,未来我们可以使用一个变量存取所定义的相关字段数据。

因为所使用的关键词是 struct,因此我们依据其中文译名将其称为结构体类型。声明 struct 的语法如下所示:
struct 结构名称
{
    修饰词 数据类型 数据名称1;
    ...
    修饰词 数据类型 数据名称n;
}
上述语法中的存取修饰词是指字段(field)数据的访问权限,C# 常见的结构访问权限有 public、private 和 internal。C# 是面向对象的程序语言,其数据有分级的存取限制,可以有下列修饰词:
例如,我们可以将学生的名字、性别和成绩组成一个结构的数据类型。下面是结构 Student 的声明,此结构内有 3 笔数据,分别是姓名 name、性别 gender、分数 score 等 3 个数据成员,它的声明方式与内存的说明图如下所示:



在上面的结构体声明中使用的 struct 是系统关键词,告诉 C# 语言编译程序,程序定义了一个结构的数据,结构数据名称是 Student(建议开头字母用大写),结构的内容有字符串 name(姓名)、字符 gender(性别)和整数 score(分数)。

C#结构体变量的声明

在顶级语句的 C#程序语言环境中,对结构体变量的声明需在顶级语句的下方。

创建好结构体后,下一步是声明结构体变量,声明方式如下:
结构体名称 结构体变量 1, 结构体变量 2, ……, 结构体变量 n;
或是
结构体名称 结构体变量 1;
……
结构体名称 结构体变量 n;

以前面所创建的结构体 struct Student 为例,想要声明 stu1 和 stu2 变量,则声明方式如下所示:
//第一种
Student stu1, stu2;
// ...
struct Student
{
    public string name;
    public char gender;
    public int score;
};
//第二种
Student stu1;
Student stu2;
// ...
struct Student
{
    public string name;
    public char gender;
    public int score;
};
从前面实例可以看到结构体 struct 变量,如果想要存取结构成员的内容,其语法如下:
结构变量.成员名称;
结构变量和成员名称之间是“.”。

C#创建结构体数据

自建结构体数据的数据来源可以分成用程序读取键盘输入或初始化数据。

1) 读取数据

从键盘输入创建结构数据,然后输出:
Student stu;
Console.Write("请输入姓名 : ");
stu.name = Console.ReadLine();
Console.Write("请输入手机号 : ");
stu.phone = Console.ReadLine();
Console.Write("请输入数学成绩 : ");
stu.math = Convert.ToInt32(Console.ReadLine());
Console.WriteLine($"Hi {stu.name} 欢迎你");
Console.WriteLine($"手机号码 : {stu.phone}");
Console.WriteLine($"数学成绩 : {stu.math}");

struct Student
{
    public string name;
    public string phone;
    public int math;
};
执行结果为:

请输入姓名 : zhangsan
请输入手机号 : 1234567
请输入数学成绩 : 98
Hi zhangsan 欢迎你
手机号码 : 1234567
数学成绩 : 98

我们知道,.NET 6.0 为了简单化 C# 的程序设计,默认使用顶级语句,对于上面程序而言,第 1~10 行就是所谓的顶级语句,顶级语句必须在结构、类、自定义的命名空间之前,上述第 11~16 行就是结构体。

2) 初始化结构数据

初始化结构数据可以使用大括号“{”和“}”包夹,大括号中间依据成员函数声明的顺序填入数据即可。初始化时字符串数据需用双引号,字符数据可以用单引号,数值数据可以直接输入数值。

此外,也可以使用 var 关键词来声明结构对象,当使用 new 关键词实体化此对象时,可以省略结构名称,细节可以参考下列方案。

例如,使用结构体名称和 var 关键词初始化结构数据,然后输出。
Student stu1 = new Student
{
    name = "zhangsan",
    phone = "1234567",
    math = 98
};

var stu2 = new
{
    name = "lisi",
    phone = "1235678",
    math = 99
};

Console.WriteLine($"Hi {stu1.name} 欢迎你");
Console.WriteLine($"手机号码 : {stu1.phone}");
Console.WriteLine($"数学成绩 : {stu1.math}");
Console.WriteLine($"Hi {stu2.name} 欢迎你");
Console.WriteLine($"手机号码 : {stu2.phone}");
Console.WriteLine($"数学成绩 : {stu2.math}");

struct Student
{
    public string name;
    public string phone;
    public int math;
};
执行结果为:

Hi zhangsan 欢迎你
手机号码 : 1234567
数学成绩 : 98
Hi lisi 欢迎你
手机号码 : 1235678
数学成绩 : 99

读者应该比较第 1 行和第 7 行声明 Student 对象 stu1 和 stu2 以及这两个对象实体化的方式。

将结构体对象的内容设置给另一个结构体对象

如果有两个相同结构的对象,分别是 family 和 seven,可以使用赋值号 =,将一个对象的内容设定给另一个对象。

例如创建一个 fruit 结构体,这个结构体有 family 和 seven 两个对象,其中先设定 family 的对象内容,然后将 family 对象内容设定给 seven 对象。
Fruit family; // 声明 family 对象
family.name = "香蕉";
family.price = 35;
family.origin = "高雄";
Console.WriteLine("全家 family 超商品项表");
Console.WriteLine($"品名 : {family.name}");
Console.WriteLine($"价格 : {family.price}");
Console.WriteLine($"产地 : {family.origin}");

Fruit seven; // 声明 seven 对象
seven = family; // 设定结构内容相等
Console.WriteLine("小七 seven 超商品项表");
Console.WriteLine($"品名 : {seven.name}");
Console.WriteLine($"价格 : {seven.price}");
Console.WriteLine($"产地 : {seven.origin}");

struct Fruit
{
    public string name;
    public int price;
    public string origin;
};
执行结果为:

全家 family 超商品项表
品名 : 香蕉
价格 : 35
产地 : 高雄
小七 seven 超商品项表
品名 : 香蕉
价格 : 35
产地 : 高雄

上述程序最关键的是第 10 行,藉由“=”号,就可以将已经设定的 family 对象内容全部转给 seven 对象。

C#嵌套的结构体

所谓的嵌套结构(nested struct)就是结构内某个数据类型是另一个结构:
struct 结构A
{
    ...
}

struct 结构B
{
    public 数据形态 数据名称1;
    ...
    结构A 变量名称;
}
例如使用结构数据创建数学成绩表,这个程序的 student 结构体内有 score 结构体:
Student stu;
stu.name = "zhangsan";
stu.math.sc = 92;
stu.math.grade = 'A';
Console.WriteLine($"姓名 : {stu.name}");
Console.WriteLine($"数学分数 : {stu.math.sc}");
Console.WriteLine($"数学成绩 : {stu.math.grade}");

struct Score // 内层结构
{
    public int sc;     // 分数
    public char grade; // 成绩
}

struct Student // 外层结构
{
    public string name; // 名字
    public Score math; // 数学成绩
}
执行结果为:

姓名 : zhangsan
数学分数 : 92
数学成绩 : A

上述程序有两个重点:

C# struct结构体的特色

C# 的结构体和 C/C++ 仍是有差异的,C# 结构体的特色如下:
下面是笔者创建 Books 结构体的实例:
struct Books
{
    private string title;      // 书籍名称
    private string author;    // 作者
    private int price;        // 售价

    public void SetValues(string t, string a, int p)
    {
        title = t;    // 设置书名
        author = a;  // 设置作者
        price = p;   // 设置售价
    }

    public void Display()
    {
        Console.WriteLine($"书名 : {title}");
        Console.WriteLine($"作者 : {author}");
        Console.WriteLine($"售价 : {price}");
    }
}
上述 Books 结构体中的数据字段是 private,表示由 New 实例化的对象才可以存取此数据栏的字段。这个结构体同时定义了 public void 的方法 SetValues() 和 Display(),这些方法也必须使用 new 实例化的对象才可以调用引用。

下列是实例化 book 对象的方法:
Books book = new Books();             // 实例化book对象
有了上述实例化的 book 对象后,可以使用下列方式调用 Books 结构体内的方法:
book.SetValues("C#入门教程", "C语言中文网", 199.9);

创建实例化结构对象,然后输出:
Books book = new Books();
// 创建 book 对象数据
book.SetValues("C#入门教程", "C语言中文网", 199.9);
// 输出数据
book.Display();

struct Books
{
    private string title;      // 书籍名称
    private string author;    // 作者
    private int price;        // 售价

    public void SetValues(string t, string a, int p)
    {
        title = t;    // 设置书名
        author = a;  // 设置作者
        price = p;   // 设置售价
    }

    public void Display()
    {
        Console.WriteLine($"书名 : {title}");
        Console.WriteLine($"作者 : {author}");
        Console.WriteLine($"售价 : {price}");
    }
}
执行结果为:

书名 : C#入门教程
作者 : C语言中文网
售价 : 199.9

从上述程序可以看到结构方法虽然好用,但是若和面向对象程序使用的类相比较,则有下列差异:

C# new创建结构体对象

从前文可以看出可以用 new 或不用 new 创建结构对象,这两个方法创建结构对象的另一个差异如下:
例如,不使用 new 创建对象,程序编译错误:
Coordinate point;
Console.WriteLine("设定初值前输出 point 坐标");
Console.WriteLine($"x = {point.x}"); // 输出 point.x 坐标,编译错误
Console.WriteLine($"y = {point.y}"); // 输出 point.y 坐标,编译错误
Console.WriteLine("设定初值后输出 point 坐标");
point.x = 5; // 设定 point.x 坐标
point.y = 10; // 设定 point.y 坐标
Console.WriteLine($"x = {point.x}"); // 输出 point.x 坐标
Console.WriteLine($"y = {point.y}"); // 输出 point.y 坐标

struct Coordinate
{
    public int x;
    public int y;
}
执行程序会报错,提示“使用了可能未赋值的字段 x 和 y

上述程序只要使用 new 声明 point 对象就可以顺利执行程序:
Coordinate point = new Coordinate();
Console.WriteLine("设定初值前输出 point 坐标");
Console.WriteLine($"x = {point.x}"); // 输出 point.x 坐标,编译错误
Console.WriteLine($"y = {point.y}"); // 输出 point.y 坐标,编译错误
Console.WriteLine("设定初值后输出 point 坐标");
point.x = 5; // 设定 point.x 坐标
point.y = 10; // 设定 point.y 坐标
Console.WriteLine($"x = {point.x}"); // 输出 point.x 坐标
Console.WriteLine($"y = {point.y}"); // 输出 point.y 坐标
执行结果为:

设定初值前输出 point 坐标
x = 0
y = 0
设定初值后输出 point 坐标
x = 5
y = 10

C#结构体数组的用法

假设我们创建了员工数据的结构,那员工数据结构一定有许多员工。这时可以将结构数据与数组相结合,假设结构名称是 Employee,此时的结构数组声明如下:
Employee[ ] em = new Employee[3];
上述创建了含有 3 笔数据的数组 em。

例如,创建结构体数组并输出:
Employee[] em = new Employee[3]; // 建立含 3 个元素的结构数组
em[0].SetValues(1001, "zhangsan", 48);
em[1].SetValues(1023, "lisi", 25);
em[2].SetValues(1089, "wangwu", 23);
// 显示结构数组数据
foreach (var e in em)
    e.Display();

public struct Employee
{
    public int Id;       // 员工 ID
    public string Name;  // 员工姓名
    public int Age;     // 员工年龄

    // 创建员工数据方法
    public void SetValues(int id, string name, int age)
    {
        Id = id;
        Name = name;
        Age = age;
    }

    // 显示结构数据
    public void Display()
    {
        Console.WriteLine("员工数据");
        Console.WriteLine($"编号 : {Id}\t姓名 : {Name}\t年龄 : {Age}");
    }
}
执行结果为:
员工数据
编号 : 1001   姓名 : zhangsan   年龄 : 48
编号 : 1002   姓名 : lisi   年龄 : 25
编号 : 1003   姓名 : wangwu  年龄 : 23

C# struct的建构方法

一个结构可以在内部创建与结构相同名称的方法,这个方法就是建构方法又称建构子。

这个建构方法可以实体化结构对象的初始值,此建构方法必须为所有成员设定初始值,设定初始值是使用 this 关键词。

例如:
Coordinate point = new Coordinate(5, 10);
Console.WriteLine($"x = {point.x}");
Console.WriteLine($"y = {point.y}");

struct Coordinate
{
    public int x;
    public int y;

    public Coordinate(int x, int y) // Constructor
    {
        this.x = x; // 设置初始值 x 坐标
        this.y = y; // 设置初始值 y 坐标
    }
}
执行结果为:

x = 5
y = 10

程序第 2 行创建 point 对象时,会将 Coordinate(5, 10) 的参数 5 和 10 分别传给第 10 行的建构方法,然后此建构方法使用 this 关键词设定 x 和 y 的初始值。

C#结构体的set和get

在结构体的应用中,如果要设定结构 private 成员数据,需使用该结构含有参数的 public 方法:
在面向对象的概念中这称为数据封装(encapsulation),可以保护结构体的 private 数据,不被外部程序直接存取。

例如,结构体内含 set 与 get 的应用,这是设定学生编号与姓名的应用。
Student stu = new Student();
stu.ID = 651014;        // 调用 set 设置学号
stu.Name = "zhangsan";    // 调用 set 设置姓名
Console.WriteLine($"学生学号 : {stu.ID}");  // 调用 get 获得学号
Console.WriteLine($"学生姓名 : {stu.Name}"); // 调用 get 获得姓名
struct Student
{
    private int id;       // 学号
    private string name;  // 姓名
    public int ID
    {
        get { return id; }    // 回传学号
        set { id = value; } // 设置学号
    }
    public string Name
    {
        get { return name; } // 回传姓名
        set { name = value; } // 设置姓名
    }
}
执行结果为:

学生学号 : 651014
学生姓名 : zhangsan

程序执行第 2 行 stu.ID=651014 时和第  3 行 stu.Name=“zhangsan” 时,因为有赋值,会自动启动 set。当执行第 4 行的 stu.ID 和第 5 行 stu.Name,因为没有赋值,会自动启动 get。

上述 ID 和 Name 表面上是方法,但是主要目的是可以存取字段的内容,在 C# 程序设计中,我们称此为属性(property)。

简化 get 和 set 的实例:
Student stu = new Student();
stu.ID = 651014;        // 调用 set 设置学号
stu.Name = "zhangsan";    // 调用 set 设置姓名
Console.WriteLine($"学生学号 : {stu.ID}");  // 调用 get 获得学号
Console.WriteLine($"学生姓名 : {stu.Name}"); // 调用 get 获得姓名
struct Student
{
    private int id;       // 学号
    private string name;  // 姓名
    public int ID { get; set; } // 学号
    public string Name { get; set; } // 姓名
}
执行结果和上例相同。上述第 10 行的 get 和 set 并没有程序代码,这时 C# 编译程序会自动定义私有字段执行此 get 和 set 工作,所以上述第 8~9 行的定义已经是多余的。这时可以继续简化上述程序。

继续简化设计,这个程序是 C# 自动实操属性:
Student stu = new Student { ID = 651014, Name = "洪锦魁" };
Console.WriteLine($"学生学号 : {stu.ID}");  // 调用 get 获得学号
Console.WriteLine($"学生姓名 : {stu.Name}"); // 调用 get 获得姓名
struct Student
{
    public int ID { get; set; }
    public string Name { get; set; }
}
执行结果和上例相同。

C# readonly字段

从 C# 9.0 开始,结构体或结构体数据与方法增加了 readonly(只读)概念,如果结构体设定为 readonly 后,除了建构函数(construtor)所设定数据外,则此结构的所有成员数据只能被读取,而无法更改内容。

此外,因为已经设定只读,所以可以将 set 取消,同时因为要让建构函数在创建对象时可以初始化成员数据,所以可以用关键词“init”替换“set”。

例如,使用 readonly 定义结构体,然后只使用建构函数来设定坐标轴 x 和 y 值。
var p1 = new Coords(2, 5);
Console.WriteLine($"{p1.X}, {p1.Y}"); // 输出 : (2, 5)
public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }
    public double X { get; init; }
    public double Y { get; init; }
}
执行结果为:

(2, 5)

C# with关键词

关键词 with 可以复制结构实体的特定字段数据,然后予以修改,这个功能可以用于在创建新的实体对象并执行初始值设定时修改成员内容。

例如,在 readonly 结构体下创建 p2 和 p3 对象时,使用 p1 的值副本修改成员的内容当作新对象的初始值。
var p1 = new Coords(2, 5);
Console.WriteLine($"{p1.X}, {p1.Y}"); // 输出 : (2, 5)
var p2 = p1 with { X = 3 };
Console.WriteLine($"{p2.X}, {p2.Y}"); // 输出 : (3, 5)
var p3 = p1 with { X = 5, Y = 10 };
Console.WriteLine($"{p3.X}, {p3.Y}"); // 输出 : (5, 10)
public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }
    public double X { get; init; }
    public double Y { get; init; }
}
执行结果为:

(2, 5)
(3, 5)
(5, 10)

上述第 3 行的 with 是 p3 点使用 p1 点的数据,但是将 X 设为 3,第 5 行的 with 是 p3 点使用 p1 点的数据,但是将 X 设为 5,将 Y 设为 10。

相关文章