{ }codelper

学ぶ › C

言語ごとの特徴 · 36

構造体

関係のある値を一つにまとめ、そのまとまりがコピーされるのか共有されるのかを知ります。

この単元は C# · C++ · C 向けです。
Python・JavaScript・TypeScript・Java はクラスとオブジェクトでデータをまとめ(クラスの単元)、Craykon は 사전() の辞書でまとめます。
どれも代入すると参照で同じオブジェクトを共有するので、コピーされる構造体とは違います。

点の x・y、生徒の名前・点数のように、いつも一緒に動く値は構造体(struct)にまとめます。
下の例は点をコピーしてコピーだけを変え、元を動かして、コピーと元の違いを見せます。

#include <stdio.h> typedef struct {    int x, y;} Point; void print_point(const char *name, Point p) {    printf("%s = (%d, %d)\n", name, p.x, p.y);} void move(Point *p, int dx, int dy) {  /* 원본을 바꾸려면 포인터로 */    p->x += dx;    p->y += dy;} int len2(Point p) {  /* 읽기만 하니 값으로 받아도 돼요 */    return p.x * p.x + p.y * p.y;} int main(void) {    Point p;    scanf("%d %d", &p.x, &p.y);    print_point("p", p);     Point q = p;  /* 멤버가 통째로 복사돼요 */    q.x = 10;    print_point("q", q);    print_point("p", p);     move(&p, 1, 1);    print_point("p", p);    printf("len2 = %d\n", len2(p));    return 0;}
stdin
3 4
stdout
p = (3, 4)
q = (10, 4)
p = (3, 4)
p = (4, 5)
len2 = 41

ポイント

  • ひとまとまりが新しい型になります。
    変数 1 つとして渡したり、返したり、配列に入れたりできます。
  • 中の値はメンバ(フィールド)といい、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))、変更しない関数には int len2() const のように 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 · C++

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

→ 呼び出し側の p はそのままです。エラーも警告もありません

C は Point *、C++ は Point& で受け取ります。

構造体を == で比べた

if (p == q) { … }

→ C ではコンパイルエラーです。C++ は operator== がなければ、C# の struct は == を定義していなければ(CS0019)コンパイルエラーです

C はメンバを 1 つずつ比べます(memcmp はメンバの間のパディングのせいで間違うことがあります)。
C++20 は bool operator==(const Point&) const = default;、C# は p.Equals(q)(メンバの値を比較)か、== まで作ってくれる record struct。

リストの中の構造体のメンバを変えた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;

言語ごとの違い
構造体を渡したり代入したりするたびに、コピーなのか同じものなのかを確かめましょう。
C・C++・C# の struct はコピーなので、元を変えるにはポインタ(Point *)・参照(Point&)・C# の ref が必要で、C# の class は同じオブジェクトを共有します。

練習問題