明快!C言語の構造体の使い方~私が構造体を愛する3つの理由~

326, 2021-09-19

目次

構造体の使い方

C言語では構造体という構造を使うことが出来ます。
この構造体を使うと異なるデータを1つにまとめたりできます。
また構造体を使うと設計がシンプルになるのでC言語ではぜひとも押さえておきたいポイントです。

この記事では具体的に構造体についてどのように扱うのかに見ていきます。

構造体の読み方

構造体は「こうぞうたい」と読みます。
「構造を持った体」ということで「構造体」ですね。

英語にすると「structure(ストラクチャー)」という意味になります。
構造体を宣言するときのstructはこのstructureから来ているわけですね。

構造体はどんな時に使うのか?

構造体はどんな時に使うのでしょうか?
構造体を使ってどんなことが便利になるのか?
構造体を使う意味は?

これは大きくまとめると↓の2つになります。

  • 異なるデータを1つにまとめたい時に使う
  • カテゴリを作りたい時に使う

異なるデータを1つにまとめたい時に使う

構造体は異なるデータを1つにまとめたい時に使います。
たとえば名前・年齢・体重と言う動物のデータがあったとします。
こういったデータをまとめるには「動物」という構造体を使ってデータをまとめます。

概念的には↓のような感じです。

人間 {
    名前
    年齢
    体重
}

この人間と言う構造体から変数を作ることで、名前・年齢・体重というデータをまとめた変数を作ることが可能になるのが構造体です。

カテゴリを作りたい時に使う

先ほどの異なるデータ(名前・年齢・体重)をまとめるときに「人間」という構造体の名前をつけました。
このように構造体を作ることでデータのカテゴリを表現することが可能です。
つまり階層化されたデータを定義していくことできるということです。

大規模開発では無数にあるデータを整理して扱う必要があるので、このようなカテゴリを作れる構造体は非常に重宝されます。

構造体の宣言方法は?

構造体の宣言方法はどんなものでしょうか?
構造体を定義するにはどんな文法を用いるのか?
実際の構造体を定義するコードはどんなものか?

これらを見ていきたいと思います。

構造体を宣言する文法は?

まず文法ですが、これは↓のような文法になります。

struct 構造体名 {
    メンバ変数;
    メンバ変数;
    ...
};

構造体の宣言には↑のようにstructというキーワードを使います。
structのあとに構造体名を書き、波カッコを書いてその中にメンバ変数を書きます。

メンバ変数とは、構造体が持つ変数のことです。
これは普通の変数宣言のように書くことが出来ます。

実際の構造体の宣言方法

では実際のC言語のコードで構造体を宣言してみたいと思います。
たとえば名前・年齢・体重というメンバ変数を持った構造体Animalを宣言します。
その場合は↓のようなコードを書きます。

struct Animal {
    char name[100];
    int age;
    double weight;
};

↑の場合、Animalが構造体名です。
そしてname, age, weightという変数がAnimalのメンバ変数になります。

typedefで構造体の型を作るには?

typedefキーワードを使うと構造体を使った型を作ることが出来ます。
その場合は↓のように宣言します。

typedef struct 構造体名 {
    メンバ変数;
    メンバ変数;
    ...
} 型名;

typedefで型を宣言する場合は構造体名を省略することが出来ます。

構造体で型を作る

実際のC言語のコードを見てみたいと思います。
↓のように構造体を使った型を宣言します。

typedef struct {
    char name[100];
    int age;
    double weight;
} Animal;

↑のようにコードを書くとAnimalAnimalという構造体型になります。

型から変数を作る

さきほどのAnimalという構造体型から変数を作る場合を見てみます。
Animaltypedefによってコンパイラに型として扱われます。
そのため↓のように変数を定義することが出来ます。

    Animal animal;

構造体で扱えるデータは?

構造体で扱えるデータはどんなものでしょうか?
intfloat? 答えは変数宣言できるものはおおむね使用可能です。
ですのでint, float, doubleint32_t, int64_tなどの基本的な型はもちろん宣言できます。
また配列やポインタなども宣言することが出来ます。
また構造体変数や共用体変数も宣言することが出来ます。

変数に出来る型は扱うことが出来る

つまり普通にコードを書いていて、変数に出来る文脈のコードは構造体内でも宣言できると言うことになります。
たとえば↓のような変数宣言(一例)はすべて構造体で使えます。

    int i;
    short s;
    char c;
    long l;
    double d;
    float f;
    int a[10];
    int *p;

関数ポインタも扱える

構造体のメンバ変数には関数ポインタも宣言することができます。
関数ポインタもポインタなので宣言できるんですね。
関数ポインタの宣言は↓のように行います。

struct Animal {
    void (*walk)(struct Animal *);
};

↑の場合、walkというのがメンバ変数になります。
walkvoid型の返り値を返し、引数にstruct Animal *を取る関数になります。

↑を見てクラスのメソッド

構造体変数の作り方は?

構造体を使った変数、構造体変数はどのように定義するのでしょうか?
構造体の宣言と構造体変数の定義のかかわりは?
構造体変数の初期化方法とは?
どんなものか見ていきたいと思います。

構造体変数には変数ごとにメンバ変数のメモリーが確保されます。
つまり変数Aと変数Bのメンバは違うメモリー上のオブジェクトになります。

宣言と同時に変数を定義する

構造体は宣言と同時に変数を定義することができます。
たとえばAnimalという構造体の変数catdogを定義する場合は↓のようになります。

struct Animal {
    int age;
} cat, dog;

↑の場合、catdogはグローバル変数になります。

宣言とは別に変数を定義する

構造体の宣言とはまた別に変数を定義することも出来ます。
たとえば先ほどのAnimalという構造体の変数を定義するには↓のようにします。

    struct Animal cat;
    struct Animal dog, bird;
    struct Animal pigs[10];

↑の場合、catdogbirdも構造体Animalの変数です。
そしてpigsAnimal型の配列になります。

構造体変数の初期化方法

構造体変数の初期化方法については↓の記事を参照してください。
個別にまとめてあります。

C言語で構造体を初期化する方法 - なるぽのブログ

構造体変数へのアクセス方法は?

構造体変数へのアクセス方法はどうすればいいのでしょうか?
変数のメンバ(構造体のメンバ変数に)アクセスするにはどうしたら?
結論から言うとドット演算子アロー演算子を使います。
構造体のポインタを使う場合はアロー演算子を使いますが、こちらは後述します。

ドット演算子で参照する方法

たとえば↓のような構造体があるとします。

struct Animal {
    int age;
    double weight;
};

この構造体のメンバageweightを出力するコードは↓になります。

    struct Animal cat = { 20, 30.3 };

    printf("age: %d\n", cat.age);  // 20
    printf("weight: %lf\n", cat.weight);  // 30.3

↑のように構造体変数.メンバ変数と書くとその構造体変数のメンバを参照できます。
↑のコードを実行すると結果は

age: 20
weight: 30.300000

になります。

ドット演算子で代入する方法

先ほどの構造体の変数のメンバに値を代入するには同じくドット演算子を使います。
たとえばメンバ変数ageに値20を代入するには↓のようなコードを書きます。

    cat.age = 30;
    printf("age: %d\n", cat.age);  // 30

↑では構造体変数catのメンバ変数age30を代入しています。
そしてその結果をprintf()で出力しています。
結果は「age: 30」になります。

構造体のポインタ変数の作り方は?

構造体のポインタ変数の作り方は普通のポインタ変数と変わりありません。
↓のように定義します。

    struct Animal *cat = NULL;

↑の場合、変数catは構造体Animalのポインタ変数になります。

構造体のポインタ変数はどうやって使う?

構造体のポインタ変数には他のポインタ変数とはちょっと違う使い方があります。
それがアロー演算子による参照です。
構造体のポインタ変数のメンバ変数にアクセスするには基本的にはこのアロー演算子を使います。

アロー演算子で参照する方法

アロー演算子でメンバ変数を参照するには↓のにアロー(->)を書きます。

    struct Animal cat = { 20 };
    struct Animal *p = &cat;

    printf("age: %d\n", p->age);  // 20

↑の場合、pが構造体Animalのポインタ変数です。
そのpcat変数のアドレスを代入しています。
アロー演算子でpからメンバ変数ageを参照して出力しています。

アロー演算子で代入する方法

アロー演算子を使ったメンバ変数への代入も基本的にはドット演算子と同じです。
↓のようにアロー演算子を伸ばしてメンバ変数に値を代入します。

    p->age = 30;
    printf("age: %d\n", p->age);  // 30

アロー演算子を使わないで参照する方法

ポインタなのでアスタリスク(*)で変数を実体化することができます。
その場合はドット演算子でメンバにアクセス可能です。

    printf("age: %d\n", (*p).age);  // 30

アスタリスクとドット演算子ではドット演算子の方が優先順位が高いです。
そのため↑のようにカッコをつけてアスタリスクで参照してからドット演算子を使います。

関数に構造体変数を渡す方法は?

関数で構造体変数を使うにはどうしたら良いのでしょうか?
関数に構造体変数を渡す方法は?
結論から言うと使い方は普通の変数と変わりません。

関数の引数に構造体変数を使う

関数の引数に構造体変数を使うには↓のように関数を定義します。

#include <stdio.h>

struct Animal {
    int age;
};

void func(struct Animal cat) {
    printf("age: %d\n", cat.age);  // 20
}

int main(void) {
    struct Animal cat = { 20 };
    func(cat);
    return 0;
}

関数の引数は構造体変数の宣言と同じように書きます。
↑の場合、関数funcが構造体Animalの変数を引数に持つ関数です。
main関数からfuncを呼び出すとき、引数に構造体変数catを渡しています。
これはコピー渡しになります。ポインタで渡したい場合はポインタ変数を使います。

構造体のサイズの取得方法は?

構造体と構造体変数のサイズを取得したい場合はどうしたらいいのでしょうか?
動的メモリ確保時などに構造体のサイズが必要になります。
結論から言うとこれも普通の変数と同じようにsizeof演算子を使います。

sizeofで構造体と変数のサイズを取得する

sizeof演算子を使って構造体と構造体変数のサイズを取得するには↓のようにします。

#include <stdio.h>

struct Animal {
    int age;
    double weight;
};

int main(void) {
    struct Animal cat;

    printf("sizeof(struct Animal): %d byte\n", sizeof(struct Animal));
    printf("sizeof(cat): %d byte\n", sizeof(cat));

    return 0;
}

↑のコードを実行した場合、結果は↓のようになります。

sizeof(struct Animal): 16 byte
sizeof(cat): 16 byte

私が構造体を愛する3つの理由

最後に筆者が構造体を愛する理由を3つ挙げたいと思います。
これは↓のような理由からです。

  • コードを書くのが楽になる
  • データの持ち運びが楽
  • 設計がシンプルになる

コードを書くのが楽になる

構造体を使うとコードを書くのが楽になります。
雑多な変数の群れが1つのまとまりになって定義することができるので、扱いが非常に楽です。
その結果、コードの量も減ってC言語でコードを書くのが楽になります。

データの持ち運びが楽

複数のデータをまとめて扱えるので、データの持ち運びが非常に楽になります。
関数引数なども構造体にすれば引数爆発などを防ぐことができて非常に良いです。
なにより構造体にすれば動的メモリ確保の手間がぐんと減ります。

設計がシンプルになる

構造体を使うと設計がシンプルになります。
たとえばfopen()系の関数などが具体例です。
FILEという構造体に変数をまとめることで、その構造体と関連する関数の定義もシンプルなものになります。
結果的に使い勝手が良くなり、その関数を使うユーザーからの評価が上がることでしょう。

おわりに

今回はC言語の構造体について解説しました。
C言語の構造体は扱えるようになると劇的にC言語の生産性を上げることが出来る機能です。
せひともモノにしたいところです。

構造体で描く未来

構造体バンザイ