{ }codelper

学习 › C#

各语言特有 · 36

结构体

把相关的值打包成一个整体,并弄清这个整体是被复制还是被共享。

本单元适用于 C# · C++ · C。
Python、JavaScript、TypeScript 和 Java 用类和对象来组织数据(见类一课),Craykon 用 사전() 字典。
它们在赋值时都通过引用共享同一个对象,这和会被复制的结构体不同。

总是一起出现的值,比如点的 x、y,学生的姓名、分数,就用结构体(struct)打包。
下面的例子先复制一个点、只改副本,再移动原来的点,以展示副本和原件的区别。

using System; struct Point  // 값 타입: class로 바꾸면 q = p 가 같은 객체를 가리켜요{    public int X, Y;    public void Move(int dx, int dy) { X += dx; Y += dy; }    public int Len2() => X * X + Y * Y;    public override string ToString() => $"({X}, {Y})";} class Program{    static void Main()    {        string[] t = Console.ReadLine().Split();        var p = new Point { X = int.Parse(t[0]), Y = int.Parse(t[1]) };        Console.WriteLine($"p = {p}");         Point q = p;  // 데이터가 복사돼요        q.X = 10;        Console.WriteLine($"q = {q}");        Console.WriteLine($"p = {p}");         p.Move(1, 1);        Console.WriteLine($"p = {p}");        Console.WriteLine($"len2 = {p.Len2()}");    }}
stdin
3 4
stdout
p = (3, 4)
q = (10, 4)
p = (3, 4)
p = (4, 5)
len2 = 41

要点

  • 一个包就成了一种新类型。
    可以作为一个变量来传递、返回、放进数组。
  • 里面的值叫成员(字段),用点来取,比如 p.x。
  • 给结构体赋值或把它传给函数时,所有成员都会整体复制。
    所以 q = p 之后再 q.x = 10,p 仍然是 (3, 4)。
  • 要修改原来的,就用指针或引用传递。
    大结构体复制代价高,所以即使只读,也常用 const 指针或引用传递。

各语言的写法

C#struct 是值类型,Point q = p; 会复制数据;class 是引用类型,同样的赋值会指向同一个对象(把例子改成 class Point,q.X = 10 之后 p 也变成 (10, 4))— int、double、DateTime 也都是结构体。
坐标、颜色、日期这类小值用 struct(想不可变就用 readonly struct 或 record struct),其余用 class。
传原件用 ref
C++不用 typedef,直接写 Point。
里面可以放成员函数(void move(int dx, int dy)),不修改成员的函数加 const,如 int len2() const — struct 和 class 只差默认访问权限(struct 是 public,class 是 private)。
传原件用 Point& 或 const Point&
Cstruct Point { int x, y; }; 每次使用都要写 struct Point p;,所以通常写 typedef struct { int x, y; } Point; — 修改原件用 move(&p, 1, 1),通过指针访问成员用 p->x 代替 (*p).x,只读时用 const Point *

常见错误

修改列表里结构体的成员C#

var list = new List<Point> { new Point() };
list[0].X = 5;

→ 编译错误 CS1612:索引器返回的是副本。数组的 arr[0].X = 5 可以,但 foreach 取出的结构体成员也不能改(CS1654)

取出副本,改完再放回去:var t = list[0]; t.X = 5; list[0] = t;

用 == 比较结构体

if (p == q) { … }

→ C 中是编译错误。C++ 没有 operator==、C# 的 struct 没有定义 ==(CS0019)时,同样编译失败

C 要逐个比较成员(memcmp 可能因为成员之间的填充字节而出错)。
C++20 可写 bool operator==(const Point&) const = default;;C# 用 p.Equals(q)(比较成员值),或连 == 都会生成的 record struct。

按值接收,只移动了副本C · C++

void move(Point p, int dx, int dy) {
    p.x += dx;
    p.y += dy;
}

→ 调用方的 p 没变。没有错误,也没有警告

C 用 Point * 接收,C++ 用 Point&。

各语言的差异
每次传递或赋值结构体时,都要想一想这是副本还是同一个东西。
C、C++、C# 的 struct 都会被复制,想修改原来的就要用指针(Point *)、引用(Point&)或 C# 的 ref;而 C# 的 class 共享同一个对象。

练习题