结构

结构体(Struct)是一种值类型(Value Type),它可以包含不同类型的成员,例如字段、属性、方法和有参构造函数等成员,类似于类(Class),这些成员可以像类一样进行访问和使用。

与类不同的是,结构体是一种轻量级的类型,它被设计为适合用于存储小型数据结构的情况,例如坐标、颜色、日期等。

结构体和类的区别

值类型和引用类型

结构体是值类型,而类是引用类型。

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;
    }
}

默认构造函数

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

从输出结果可以看出:

  1. 结构体的实例分配在栈中,而类的实例分配在堆中,所以结构体的实例大小可能会比类的实例小。
  2. 结构体是值类型,类是引用类型。在传递结构体实例时,会进行复制传递;在传递类实例时,会进行引用传递。
  3. 结构体和类都可以定义构造函数,但结构体的构造函数不能有参数 less 构造函数。
  4. 当使用结构体实例调用方法时,如果方法修改结构体的字段,则实际上修改的是方法内的一个副本,而不是原始结构体实例。因此,对结构体实例进行修改的方法必须是值类型方法(即不修改实例字段的方法)或者使用 refout 关键字传递结构体实例。
  5. 类的实例是通过引用访问的,因此对类实例的修改会直接反映在原始实例上。
  6. 结构体比类更适合存储简单数据类型,例如数值类型或坐标点等。
  7. 类比结构体更适合用于创建大型对象或维护状态,例如用户界面或数据库记录。

在选择结构体或类时,应根据其特性和使用场景进行评估。如果需要存储一组简单的数据,那么结构体可能是更好的选择;如果需要创建一个复杂的对象,并且需要维护状态,那么类可能是更好的选择。

总结

总结来说,结构体和类在 C#中都是用于定义自定义数据类型的语言构造,它们在定义方式和语法上很相似,但在使用时有很多差异,包括但不限于:

最后,让我们再次总结一下结构体和类的区别,同时加上上述讨论的内容:

属性 结构体
存储位置
是否支持继承 不支持 支持
传递方式 复制传递 引用传递
适用场景 小型数据类型 大型数据类型
实例修改方式 值类型方法 引用类型方法(直接反映在原始实例上)
字段赋初始值 不可以 可以
是否需要显式定义构造函数 不需要 可以不定义
显式定义无参构造函数 不可以 可以
有参构造方法中为所有字段赋值 必须 可以不赋值
结构类型的对象是否需要实例化后才能调用里面的方法 不需要 需要