结构
结构体(Struct)是一种值类型(Value Type),它可以包含不同类型的成员,例如字段、属性、方法和有参构造函数等成员,类似于类(Class),这些成员可以像类一样进行访问和使用。
与类不同的是,结构体是一种轻量级的类型,它被设计为适合用于存储小型数据结构的情况,例如坐标、颜色、日期等。
结构体类型通过使用
struct关键字进行声明。csharp// 定义了一个名为 `Point` 的结构体 // 它有两个整型的成员 `X` 和 `Y` // 并定义了一个构造函数,用于初始化结构体的成员 struct Point { public int X; public int Y; public Point(int x, int y) { X = x; Y = y; } }结构体的实例化和访问成员与类类似。
csharpPoint p = new Point(10, 20); // 创建了一个 `Point` 类型的变量,并传递了初始化参数 `10` 和 `20` // Point point = new Point(); // point.X = 10; // point.Y = 20; Console.WriteLine($"X: {p.X}, Y: {p.Y}"); // 访问了它的成员 `X` 和 `Y`结构是值类型 (类是引用类型)
结构中可以定义字段、属性和方法
不能为结构中的字段赋初始值
结构的构造方法中必须为所有字段赋值
不能为结构显式定义无参数的构造方法
结构类型的对象可以不实例化
结构体和类的区别
值类型和引用类型
结构体是值类型,而类是引用类型。
- 当您声明一个结构体变量时,实际上是在栈中为其分配内存,并将其值直接存储在该内存中。
- 当您声明一个类变量时,只是在栈中分配一个引用,该引用指向在堆中分配的对象。
- 因此,当您对结构体变量进行操作时,将使用其实际值,而不是引用。
- 而对类变量进行操作时,将使用引用,而不是实际值。
public struct MyStruct
{
public int x;
public int y;
public MyStruct(int x, int y)
{
this.x = x;
this.y = y;
}
}public class MyClass
{
public int x;
public int y;
public MyClass(int x, int y)
{
this.x = x;
this.y = y;
}
}默认构造函数
- 当您声明一个类时,C#编译器将为您自动生成一个默认构造函数,该构造函数将初始化类的所有成员变量。
- 而在声明结构体时,如果您没有提供自己的构造函数,则 C# 编译器将为您生成一个默认构造函数,该构造函数将初始化结构体的所有字段。
struct Point {
public int x;
public int y;
}
public class Program
{
static void Main(string[] args)
{
Point p1 = new Point();
p1.x = 10;
p1.y = 20;
}
}class Person {
public string name;
public int age;
}
public class Program
{
static void Main(string[] args)
{
Person person1 = new Person();
person1.name = "John";
person1.age = 30;
}
}成员访问修饰符
- 在结构体和类中,您可以使用公共,私有和受保护等访问修饰符来限制成员的可见性和可访问性。
- 结构体的默认访问修饰符为公共。这意味着结构体的所有字段都可以从外部访问,而不管您是否将它们标记为公共。
struct Point {
public int x;
public int y;
}class Person {
private string name;
public int age;
}继承
类可以继承其他类,但结构体不能。这意味着您可以使用类来实现多态和基类/子类关系,但不能使用结构体。
class Animal {
public virtual void MakeSound() {
Console.WriteLine("The animal makes a sound");
}
}
class Dog : Animal {
public override void MakeSound() {
Console.WriteLine("The dog barks");
}
}Dog类继承自Animal类,并覆盖了MakeSound方法以提供自己的实现。
性能
- 由于结构体是值类型,因此它们在内存中的分配通常比类更快。这是因为它们的值存储在栈中,而不是堆中。
- 在一些情况下,使用结构体可以提高应用程序的性能。
/// <summary>
/// 定义结构体`Point`来表示二维坐标系中的点
/// </summary>
struct Point {
public int x;
public int y;
/// <summary>
/// 计算两个点之间距离的方法`Distance`
/// 通过将两个点作为参数传递给`Distance`方法来计算它们之间的距离
/// </summary>
public double Distance(Point other) {
int xDiff = x - other.x;
int yDiff = y - other.y;
return Math.Sqrt(xDiff * xDiff + yDiff * yDiff);
}
}
Point p1 = new Point() { x = 10, y = 20 };
Point p2 = new Point() { x = 30, y = 40 };
double distance = p1.Distance(p2);定义时字段赋初始值
结构体中的字段不能在定义字段时赋值,但可以在声明时或构造函数中进行赋值。
- 结构体中的字段不能在字段定义时直接赋初值,这是因为结构体是值类型,而值类型的初始化是通过构造函数进行的。在结构体中定义字段时,编译器不会为字段生成默认的构造函数,因此在字段定义时不能为字段赋初始值。
- 如果您想要在结构体中的字段定义时为字段赋初始值,您可以创建一个带有参数的构造函数,并在其中为字段赋值。
类中的字段可以在定义时赋初始值,也可以在构造函数中赋值。
struct Person
{
public string name;
public int age;
public bool isMarried;
}
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
name = "Alice",
age = 30,
isMarried = true
};
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}class Person
{
public string name = "Alice";
public int age = 30;
public bool isMarried = true;
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}显式定义无参构造
- 结构体中不能显式定义无参构造方法。
- 类中可以显式定义无参构造方法。
struct Person
{
public string name;
public int age;
public bool isMarried;
public Person(string name, int age, bool isMarried)
{
this.name = name;
this.age = age;
this.isMarried = isMarried;
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person("Alice", 30, true);
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}class Person
{
public string name;
public int age;
public bool isMarried;
public Person()
{
name = "Alice";
age = 30;
isMarried = true;
}
public Person(string name, int age, bool isMarried)
{
this.name = name;
this.age = age;
this.isMarried = isMarried;
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}调用前是否需要实例化
- 结构体类型的对象可以不实例化
- 类类型的对象必须实例化
struct Person
{
public string name;
public int age;
public bool isMarried;
public void Print()
{
Console.WriteLine("{0}, {1}, {2}", name, age, isMarried);
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Print();
}
}class Person
{
public string? name;
public int age;
public bool isMarried;
public void Print()
{
Console.WriteLine("{0}, {1}, {2}", name, age, isMarried);
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.name = "Alice";
person.age = 30;
person.isMarried = true;
person.Print();
}
}有参构造必须为所有字段赋值
- 结构体中的有参构造方法必须为所有字段赋值。
- 类中的有参构造方法可以为所有字段赋值,也可以为部分字段赋值。
struct Person
{
public string name;
public int age;
public bool isMarried;
public Person(string name, int age, bool isMarried)
{
this.name = name;
this.age = age;
this.isMarried = isMarried;
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person("Alice", 30, true);
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}class Person
{
public string name;
public int age;
public bool isMarried;
public Person(string name, int age, bool isMarried)
{
this.name = name;
this.age = age;
this.isMarried = isMarried;
}
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person("Alice", 30, true);
// Output: Alice, 30, True
Console.WriteLine("{0}, {1}, {2}", person.name, person.age, person.isMarried);
}
}示例代码
对结构体/类变量进行操作
/// <summary>
/// 在控制台上打印结构体和类的坐标值
/// </summary>
class Program
{
static void Main(string[] args)
{
MyStruct structObj = new MyStruct(1, 2);
MyClass classObj = new MyClass(3, 4);
Console.WriteLine($"structObj.x: {structObj.x}, structObj.y: {structObj.y}");
Console.WriteLine($"classObj.x: {classObj.x}, classObj.y: {classObj.y}");
UpdateValues(structObj, classObj);
Console.WriteLine($"structObj.x: {structObj.x}, structObj.y: {structObj.y}");
Console.WriteLine($"classObj.x: {classObj.x}, classObj.y: {classObj.y}");
Console.ReadKey();
}
static void UpdateValues(MyStruct structObj, MyClass classObj)
{
structObj.x = 5;
structObj.y = 6;
classObj.x = 7;
classObj.y = 8;
}
}
// 输出
// structObj.x: 1, structObj.y: 2
// classObj.x: 3, classObj.y: 4
// structObj.x: 1, structObj.y: 2
// classObj.x: 7, classObj.y: 8可以看到,尽管我们在 UpdateValues 方法中更改了类对象的属性,但 Main 方法中的 classObj 对象的属性值也随之更改了。这是因为类是引用类型,传递给 UpdateValues 方法的是指向同一个对象的引用。在方法内部修改该对象的属性值,也就会影响到 Main 方法中的该对象。
而对于结构体,情况则不同。在 UpdateValues 方法中修改 structObj 的属性值并不会影响到 Main 方法中的 structObj 对象,因为结构体是值类型,传递给 UpdateValues 方法的是 structObj 的副本,而不是指向同一个对象的引用。
结构体和类的构造函数的使用
/// <summary>
/// 定义类 `StudentClass` 来表示学生
/// </summary>
public class StudentClass
{
public string name;
public int age;
public StudentClass(string name, int age)
{
this.name = name;
this.age = age;
}
/// <summary>
/// 在控制台上打印学生姓名和年龄
/// </summary>
public void PrintDetails()
{
Console.WriteLine($"Student name: {name}, age: {age}");
}
}/// <summary>
/// 定义结构体 `StudentStruct` 来表示学生
/// </summary>
public struct StudentStruct
{
public string name;
public int age;
public StudentStruct(string name, int age)
{
this.name = name;
this.age = age;
}
/// <summary>
/// 在控制台上打印学生姓名和年龄
/// </summary>
public void PrintDetails()
{
Console.WriteLine($"Student name: {name}, age: {age}");
}
}static void Main(string[] args)
{
StudentStruct structObj = new StudentStruct("Tom", 20); // 创建一个学生结构体对象
StudentClass classObj = new StudentClass("Jerry", 21); // 创建一个学生类对象
// 调用结构体和类的 PrintDetails 方法
structObj.PrintDetails();
classObj.PrintDetails();
// 等待用户输入
Console.ReadKey();
}
// Student name: Tom, age: 20
// Student name: Jerry, age: 21可以看到,结构体和类都可以有自己的构造函数和方法,并且它们的行为非常相似。
比较值类型和引用类型的存储位置
- 比较值类型和引用类型的存储位置
- 结构体和类中定义构造函数
- 比较值类型和引用类型在传递方式上的差异
using System;
/// <summary>
/// 定义结构体 `PointStruct` 来表示坐标点
/// </summary>
public struct PointStruct
{
public int x;
public int y;
}
/// <summary>
/// 定义类 `PointClass` 来表示坐标点
/// </summary>
public class PointClass
{
public int x;
public int y;
}
/// <summary>
/// 定义结构体 `MyStruct` 来表示学生并包含一个坐标点结构体 `PointStruct`
/// </summary>
public struct MyStruct
{
public PointStruct point;
public string name;
public int age;
/// <summary>
/// 更新结构体的属性值
/// </summary>
public void UpdateValues()
{
point.x = 10;
point.y = 20;
name = "New Name";
age = 30;
}
}
/// <summary>
/// 定义类 `MyClass` 来表示学生并包含一个坐标点类 `PointClass`
/// </summary>
public class MyClass
{
public PointClass point;
public string name;
public int age;
/// <summary>
/// 更新类的属性值
/// </summary>
public void UpdateValues()
{
point.x = 10;
point.y = 20;
name = "New Name";
age = 30;
}
}
/// <summary>
/// 定义结构体 `StudentStruct` 来表示学生
/// </summary>
public struct StudentStruct
{
public string name;
public int age;
public StudentStruct(string name, int age)
{
this.name = name;
this.age = age;
}
/// <summary>
/// 在控制台上打印学生姓名和年龄
/// </summary>
public void PrintDetails()
{
Console.WriteLine($"Student name: {name}, age: {age}");
}
}
/// <summary>
/// 定义类 `StudentClass` 来表示学生
/// </summary>
public class StudentClass
{
public string name;
public int age;
public StudentClass(string name, int age)
{
this.name = name;
this.age = age;
}
/// <summary>
/// 在控制台上打印学生姓名和年龄
/// </summary>
public void PrintDetails()
{
Console.WriteLine($"Student name: {name}, age: {age}");
}
}
class Program
{
static void Main(string[] args)
{
// 示例 1:比较值类型和引用类型的存储位置
PointStruct structObj1 = new PointStruct();
PointClass classObj1 = new PointClass();
Console.WriteLine($"Struct object1: {sizeof(PointStruct)} bytes, Class object1: {sizeof(PointClass)} bytes");
PointStruct structObj2 = new PointStruct();
PointClass classObj2 = new PointClass();
Console.WriteLine($"Struct object2: {sizeof(PointStruct)} bytes, Class object2: {sizeof(PointClass)} bytes");
Console.WriteLine();
// 示例 2:比较值类型和引用类型在传递方式上的差异
MyStruct structObj = new MyStruct();
MyClass classObj = new MyClass();
structObj.point.x = 1;
structObj.point.y = 2;
structObj.name = "Old Name";
structObj.age = 20;
classObj.point = new PointClass { x = 1, y = 2 };
classObj.name = "Old Name";
classObj.age = 20;
Console.WriteLine("Before UpdateValues method:");
Console.WriteLine($"Struct point: ({structObj.point.x}, {structObj.point.y}), name: {structObj.name}, age: {structObj.age}");
Console.WriteLine($"Class point: ({classObj.point.x}, {classObj.point.y}), name: {classObj.name}, age: {classObj.age}");
structObj.UpdateValues();
classObj.UpdateValues();
Console.WriteLine("After UpdateValues method:");
Console.WriteLine($"Struct point: ({structObj.point.x}, {structObj.point.y}), name: {structObj.name}, age: {structObj.age}");
Console.WriteLine($"Class point: ({classObj.point.x}, {classObj.point.y}), name: {classObj.name}, age: {classObj.age}");
Console.WriteLine();
// 示例 3:结构体和类中定义构造函数
StudentStruct structStudent = new StudentStruct("John", 20);
structStudent.PrintDetails();
StudentClass classStudent = new StudentClass("John", 20);
classStudent.PrintDetails();
}
}
// 输出:
// Struct object1: 8 bytes, Class object1: 8 bytes
// Struct object2: 8 bytes, Class object2: 8 bytes
// Before UpdateValues method:
// Struct point: (1, 2), name: Old Name, age: 20
// Class point: (1, 2), name: Old Name, age: 20
// After UpdateValues method:
// Struct point: (10, 20), name: New Name, age: 30
// Class point: (10, 20), name: New Name, age: 30
// Student name: John, age: 20
// Student name: John, age: 20从输出结果可以看出:
- 结构体的实例分配在栈中,而类的实例分配在堆中,所以结构体的实例大小可能会比类的实例小。
- 结构体是值类型,类是引用类型。在传递结构体实例时,会进行复制传递;在传递类实例时,会进行引用传递。
- 结构体和类都可以定义构造函数,但结构体的构造函数不能有参数 less 构造函数。
- 当使用结构体实例调用方法时,如果方法修改结构体的字段,则实际上修改的是方法内的一个副本,而不是原始结构体实例。因此,对结构体实例进行修改的方法必须是值类型方法(即不修改实例字段的方法)或者使用
ref或out关键字传递结构体实例。 - 类的实例是通过引用访问的,因此对类实例的修改会直接反映在原始实例上。
- 结构体比类更适合存储简单数据类型,例如数值类型或坐标点等。
- 类比结构体更适合用于创建大型对象或维护状态,例如用户界面或数据库记录。
在选择结构体或类时,应根据其特性和使用场景进行评估。如果需要存储一组简单的数据,那么结构体可能是更好的选择;如果需要创建一个复杂的对象,并且需要维护状态,那么类可能是更好的选择。
总结
总结来说,结构体和类在 C#中都是用于定义自定义数据类型的语言构造,它们在定义方式和语法上很相似,但在使用时有很多差异,包括但不限于:
- 结构体是值类型,而类是引用类型;
- 结构体不能被继承,而类可以被继承;
- 结构体可以没有构造函数;
- 在传递给方法时,结构体会被复制,而类会被引用。
- 当定义一个结构体时,它的实例将被分配在栈中,而当定义一个类时,它的实例将被分配在堆中。
- 在选择使用结构体或类时,需要考虑数据类型的大小、生命周期、使用方式等因素。
- 一般来说,当需要表示一个小型数据类型时(如坐标、颜色等),使用结构体可能更合适;
- 当需要表示一个大型数据类型时(如人、订单等),使用类可能更合适。
最后,让我们再次总结一下结构体和类的区别,同时加上上述讨论的内容:
| 属性 | 结构体 | 类 |
|---|---|---|
| 存储位置 | 栈 | 堆 |
| 是否支持继承 | 不支持 | 支持 |
| 传递方式 | 复制传递 | 引用传递 |
| 适用场景 | 小型数据类型 | 大型数据类型 |
| 实例修改方式 | 值类型方法 | 引用类型方法(直接反映在原始实例上) |
| 字段赋初始值 | 不可以 | 可以 |
| 是否需要显式定义构造函数 | 不需要 | 可以不定义 |
| 显式定义无参构造函数 | 不可以 | 可以 |
| 有参构造方法中为所有字段赋值 | 必须 | 可以不赋值 |
| 结构类型的对象是否需要实例化后才能调用里面的方法 | 不需要 | 需要 |