C# struct结构体的用法(非常详细)
C# 语言除了提供给用户基本数据类型之外,还使用户可以通过一些功能,如结构体(struct),创建属于自己的数据类型。
结构体常应用在小数据值上,如坐标点、组织小数据或数据结构。C# 语言编译程序会将这个自建的结构数据类型视为一般数据类型,也可以为此数据类型创建变量、数组或将其作为参数传递给函数等。
因为所使用的关键词是 struct,因此我们依据其中文译名将其称为结构体类型。声明 struct 的语法如下所示:
例如,我们可以将学生的名字、性别和成绩组成一个结构的数据类型。下面是结构 Student 的声明,此结构内有 3 笔数据,分别是姓名 name、性别 gender、分数 score 等 3 个数据成员,它的声明方式与内存的说明图如下所示:
在上面的结构体声明中使用的 struct 是系统关键词,告诉 C# 语言编译程序,程序定义了一个结构的数据,结构数据名称是 Student(建议开头字母用大写),结构的内容有字符串 name(姓名)、字符 gender(性别)和整数 score(分数)。
创建好结构体后,下一步是声明结构体变量,声明方式如下:
以前面所创建的结构体 struct Student 为例,想要声明 stu1 和 stu2 变量,则声明方式如下所示:
此外,也可以使用 var 关键词来声明结构对象,当使用 new 关键词实体化此对象时,可以省略结构名称,细节可以参考下列方案。
例如,使用结构体名称和 var 关键词初始化结构数据,然后输出。
例如创建一个 fruit 结构体,这个结构体有 family 和 seven 两个对象,其中先设定 family 的对象内容,然后将 family 对象内容设定给 seven 对象。
下面是笔者创建 Books 结构体的实例:
下列是实例化 book 对象的方法:
创建实例化结构对象,然后输出:
例如,不使用 new 创建对象,程序编译错误:
上述程序只要使用 new 声明 point 对象就可以顺利执行程序:
例如,创建结构体数组并输出:
这个建构方法可以实体化结构对象的初始值,此建构方法必须为所有成员设定初始值,设定初始值是使用 this 关键词。
例如:
在面向对象的概念中这称为数据封装(encapsulation),可以保护结构体的 private 数据,不被外部程序直接存取。
例如,结构体内含 set 与 get 的应用,这是设定学生编号与姓名的应用。
上述 ID 和 Name 表面上是方法,但是主要目的是可以存取字段的内容,在 C# 程序设计中,我们称此为属性(property)。
简化 get 和 set 的实例:
继续简化设计,这个程序是 C# 自动实操属性:
此外,因为已经设定只读,所以可以将 set 取消,同时因为要让建构函数在创建对象时可以初始化成员数据,所以可以用关键词“init”替换“set”。
例如,使用 readonly 定义结构体,然后只使用建构函数来设定坐标轴 x 和 y 值。
例如,在 readonly 结构体下创建 p2 和 p3 对象时,使用 p1 的值副本修改成员的内容当作新对象的初始值。
结构体常应用在小数据值上,如坐标点、组织小数据或数据结构。C# 语言编译程序会将这个自建的结构数据类型视为一般数据类型,也可以为此数据类型创建变量、数组或将其作为参数传递给函数等。
C#结构体类型的创建
C#语言提供了 struct 关键词,其可以将相关的数据组织起来,成为一组新的复合数据类型,此数据类型称为记录(record),这些相关的数据可以是不同类型,未来我们可以使用一个变量存取所定义的相关字段数据。因为所使用的关键词是 struct,因此我们依据其中文译名将其称为结构体类型。声明 struct 的语法如下所示:
struct 结构名称 { 修饰词 数据类型 数据名称1; ... 修饰词 数据类型 数据名称n; }上述语法中的存取修饰词是指字段(field)数据的访问权限,C# 常见的结构访问权限有 public、private 和 internal。C# 是面向对象的程序语言,其数据有分级的存取限制,可以有下列修饰词:
- private:成员的数据默认是此数据类型,只有类或是结构本身才可以存取。
- public:这类数据可以让类或是结构的对象存取。
- internal:可以供 C# 同一个项目的程序存取。
- protected:可以让类或是其子类存取。
- protected internal:相同项目的程序可以存取,其他项目若是有继承此类也可以存取。
例如,我们可以将学生的名字、性别和成绩组成一个结构的数据类型。下面是结构 Student 的声明,此结构内有 3 笔数据,分别是姓名 name、性别 gender、分数 score 等 3 个数据成员,它的声明方式与内存的说明图如下所示:

- 结构体 struct Student 省略声明,默认是 internal 声明。
- 上述结构内的数据 name、gender、score 若是省略 public 声明,会被视为是 private 声明。
- 上述 3 笔成员数据 name、gender 和 score 又叫作结构体 Student 的字段。
在上面的结构体声明中使用的 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
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
将结构体对象的内容设置给另一个结构体对象
如果有两个相同结构的对象,分别是 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
产地 : 高雄
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
- 设定结构体内有结构体的声明方式,读者可以参考第 18 行;
- 设定结构体内有结构体的数据方式,读者可以参考第 2~3 行。
C# struct结构体的特色
C# 的结构体和 C/C++ 仍是有差异的,C# 结构体的特色如下:- 结构体内可以有方法(也可称函数);
- 结构体成员可以声明为 public 或 private,但是不能声明为 abstract、virtual 或 protected;
- 使用 new 创建结构对象,未来此对象可以调用结构体内的方法;
- 使用 new 创建结构对象,未来可以存取结构体内的 private 字段;
- 可以有自动定义的建构(Constructor)方法。
下面是笔者创建 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
- 类(class)是引用类型,结构(struct)是值类型。
- 类(class)支持继承特性,结构(struct)不支持继承特性。
- 类可以更有弹性声明建构(constructor)方法对象。
C# new创建结构体对象
从前文可以看出可以用 new 或不用 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
C#结构体的set和get
在结构体的应用中,如果要设定结构 private 成员数据,需使用该结构含有参数的 public 方法:- 结构体方法的 set 特性可以让我们直接更新结构的 private 成员数据;
- 结构体的 get 特性可以让我们直接取得结构体的 private 成员数据。
在面向对象的概念中这称为数据封装(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
上述 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)