カテゴリー

C言語

【C言語&構造体】複数のデータ型を一つに格納したい(ポインタなし)

2021年7月17日

こんな人にオススメ

C言語で動物の種類とそのIDと匹数とかの複数データを一括で管理する方法ってない?

ということで、今回はC言語の構造体について解説する。イメージはpythonのClassらしいが執筆者はまだClassについては理解できていないので大きいことは言えない。個人的印象はpythonのdictに近い。

構造体を使用することで、intcharなど異なる型を一つの箱に入れることが可能になる。したがって、複数データを一括管理することが簡単になる。

今回も例に漏れず猫Cこと「猫でもわかるC言語プログラミング 第3版」を参考にさせていただいた。第9章がまるまる構造体の章であるのでここを中心に本記事を作成した。

環境設定としてMacのバージョンとgccのバージョンを示す。

  • macOS Big Surバージョン11.4
  • M1 Macbook Pro(2020)
  • Apple clang version 12.0.5 (clang-1205.0.22.9)

なお、今回はポインタについては触れないことにする。またの機会でポインタ自体の解説とかを入れようと思う。

スポンサーリンク
スポンサーリンク

運営者のメガネと申します。TwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

構造体の宣言

構造体の宣言は以下のような構造で行う。構造体も通常の変数と同じで宣言をしないと使用することができない。struct Dataが今までのintのイメージでその中にさらにintとかがあるイメージ。

struct 構造体タグ {
    データ型 メンバ変数名;
    データ型 メンバ変数名;
    データ型 メンバ変数名;
    ...
};

struct Data {
    int data1;
    char data2;
    char data3[32];
};

構造体タグとしてDataを宣言したが、これを変数として扱うためにさらに宣言する。この変数のことを構造体変数と呼ぶ。

struct Data mydata; /* 構造体変数の宣言 */

宣言した後は変数に値を代入するんだけど、その変数がメンバ変数という呼び方になっている。代入の仕方は例えば以下のような形。ここではdata1に数値の10data2に文字'A'、そしてdata3に文字列のcatを代入している。

呼び出し方は構造体変数.メンバ変数。ややこしくなってきた。なお、catだけ=でしていないのは文字列だから。C言語の文字列操作については以下参照。

【C言語&文字列操作】strlenなどのCの文字列操作を試す

続きを見る

構造体変数.メンバ変数 = (代入したい値)

mydata.data1 = 10;           /* data1に数値10を代入 */
mydata.data2 = 'A';          /* data2に文字Aを代入 */
strcpy(mydata.data3, "cat"); /* data3に文字列catを代入 */

ここまでの処理を1つのコードとしたのが以下。

/* 構造体の作成 */
#include <stdio.h>
#include <string.h>
/*
struct 構造体タグ {
    データ型 メンバ変数名;
    データ型 メンバ変数名;
    データ型 メンバ変数名;
    ...
};
 */

struct Data {
    int data1;
    char data2;
    char data3[32];
};

int main() {
    struct Data mydata; /* 構造体変数の宣言 */

    // 構造体変数.メンバ変数 = (代入したい値)
    mydata.data1 = 10;           /* data1に数値10を代入 */
    mydata.data2 = 'A';          /* data2に文字Aを代入 */
    strcpy(mydata.data3, "cat"); /* data3に文字列catを代入 */

    printf("%d, %c, %s\\n", mydata.data1, mydata.data2, mydata.data3);

    return 0;
}
/*
10, A, cat
 */

構造体変数を宣言と同時に初期化

さっきは構造体変数.メンバ変数 = (代入したい値)という方法で値を代入したが、構造体変数は宣言と同時に初期化することが可能。以下ではmydata1, mydata2という構造体変数を宣言と同時に初期化している。

/* 構造体変数を宣言するのと同時に初期化 */
#include <stdio.h>

struct Data {
    int data1;
    char data2;
    char data3[32];
};

int main() {
    struct Data mydata1 = {10, 'A', "cat"}; /* 構造体変数の宣言と初期化 */
    printf("%d, %c, %s\\n", mydata1.data1, mydata1.data2, mydata1.data3);

    struct Data mydata2 = {101, 'B', "dog"}; /* 構造体変数の宣言と初期化 */
    printf("%d, %c, %s\\n", mydata2.data1, mydata2.data2, mydata2.data3);

    return 0;
}

/*
10, A, cat
101, B, dog
 */

こうすることでスッキリ見えるし、初期化漏れとか代入もれが減るような気がする。

構造体の宣言と0での初期化

上ではメンバ変数の初期化を行ったが、初期化のためのデータが足りないときは0で埋められる。例えば以下では全ての要素が0で初期化した構造体変数が得られる。

struct Data mydata = {0};

一方で、データが足りない部分は0で補うだけなので足りている分に関しては0では埋められない。例えば以下の例だと初めのデータは1で初期化されるけど、それ以外が0で初期化される。

struct Data mydata = {1};

ちゃんと0で埋められているのかを調べるのは以下のようにすればいい。

/* 先頭のみ1、他を0と初期化した構造体の作成 */
#include <stdio.h>

struct Data {
    int data1;
    int data2;
    int data3;
};

int main() {
    struct Data mydata = {1};
    printf(
        "data1: %d\\n"
        "data2: %d\\n"
        "data3: %d\\n",
        mydata.data1, mydata.data2, mydata.data3);
    return 0;
}

/*
data1: 1
data2: 0
data3: 0
 */

scanfでデータを入力

これまでは予めデータを決めて代入していたが、もちろんscanfでデータを代入することも可能。scanfの場合は&をつけるんだけど、構造体の場合は構造体変数の前につける。なお、data2に関しては&がいらんっぽい。ポインタ関連。

/* scanfで値を入れる */
#include <stdio.h>

struct Data {
    int data1;
    char data2[32];
    int data3;
};

int main() {
    struct Data mydata = {0, "a", 0};
    printf("%d, %s, %d\\n", mydata.data1, mydata.data2, mydata.data3);

    printf("入力1: ");
    scanf("%d", &mydata.data1); /* &は構造体変数の前につける */
    printf("入力2: ");
    // 実際には構造体のメンバの名前のアドレスを示しているらしい...?
    scanf("%s", mydata.data2);
    printf("入力3: ");
    scanf("%d", &mydata.data3);

    printf("%d, %s, %d\\n", mydata.data1, mydata.data2, mydata.data3);

    return 0;
}

/*
0, a, 0
入力1: 100
入力2: mega
入力3: 9
100, mega, 9
 */

typedefを使った構造体変数の定義

これまでは構造体変数宣言時にstructとつけていた。多少面倒。そこでtypedefなるキーワードを使用することで省略することができる。typedefを使用することで新しい方を定義することができるためだ。

typedefなしの場合はstruct Dataと書いているが、ありの場合は最後にDataを書くらしい。

// typedefなし
struct Data {
    int data1;
    char data2;
    char data3[32];
};

// typedefあり
typedef struct {
    int data1;
    char data2;
    char data3[32];
} Data;

構造体変数の宣言時にはstructを書かずにいきなりDataと書くことが可能。

// typedefなし
struct Data mydata = {10, 'A', "cat"};

// typedefあり
Data mydata = {10, 'A', "cat"}; /* structを書かなくても良くなる */

typedefを使用した構造体のコードは以下。

/* typedefで構造体を定義 */
#include <stdio.h>

// typedefなし
// struct Data {
//     int data1;
//     char data2;
//     char data3[32];
// };

// typedefあり
typedef struct {
    int data1;
    char data2;
    char data3[32];
} Data;

int main() {
    // typedefなし
    // struct Data mydata = {10, 'A', "cat"};

    // typedefあり
    Data mydata = {10, 'A', "cat"}; /* structを書かなくても良くなる */
    
    printf("%d, %c, %s\\n", mydata.data1, mydata.data2, mydata.data3);

    return 0;
}

/*
10, A, cat
 */

構造体の配列

ここまでは構造体のメンバに数値や文字を使用したが、ここでは配列を使用することにする。といっても少し内容を変更すればいいだけで、構造体変数の定義の際に要素数の情報を加えればいい。

struct Seiseki {
    char name[32];
    int kokugo;
    int sugaku;
    int eigo;
};

struct Seiseki myclass[5] = {
    {"田中", 80, 80, 55},  {"佐藤", 75, 90, 70}, {"大田", 50, 45, 30},
    {"鈴木", 100, 55, 90}, {"加藤", 60, 85, 35},
};

メンバにアクセスするにはmyclass[1].kokugoのようにインデックスとメンバを指定すればいい。myclassに入れた各人の科目得点を出力するコードは以下。

/* メンバとして配列を持つ構造体の作成 */
#include <stdio.h>

struct Seiseki {
    char name[32];
    int kokugo;
    int sugaku;
    int eigo;
};

int main() {
    int i = 0;

    struct Seiseki myclass[5] = {
        {"田中", 80, 80, 55},  {"佐藤", 75, 90, 70}, {"大田", 50, 45, 30},
        {"鈴木", 100, 55, 90}, {"加藤", 60, 85, 35},
    };

    printf("%s\\n", myclass[0].name); /* 要素のインデックスでメンバにアクセス */

    printf("氏名, 国語, 数学, 英語\\n");
    for (i = 0; i < 5; i++) {
        printf("%s, %d, %d, %d\\n", myclass[i].name, myclass[i].kokugo,
               myclass[i].sugaku, myclass[i].eigo);
    }

    return 0;
}

/*
田中
氏名, 国語, 数学, 英語
田中, 80, 80, 55
佐藤, 75, 90, 70
大田, 50, 45, 30
鈴木, 100, 55, 90
加藤, 60, 85, 35
 */

構造体のサイズを自動取得

最後はmyclassの要素数を自動取得する方法について解説。sizeof(構造体配列)でその構造体全体のサイズ(バイト数)が、sizeof(struct 構造体名)でその構造体1つで必要になるサイズが分かる。構造体全体のサイズを構成要素1つのサイズで割ることで、要素数を計算することが可能。

先ほど同様、点数の構造体の場合は以下のようにサイズを確認することが可能。

struct Seiseki myclass[] = {
    {"田中", 80, 80, 55},  {"佐藤", 75, 90, 70}, {"大田", 50, 45, 30},
    {"鈴木", 100, 55, 90}, {"加藤", 60, 85, 35},
};

printf("sizeof(myclass): %lu\\n", sizeof(myclass));
// sizeof(myclass): 220
printf("sizeof(struct Seiseki): %lu\\n", sizeof(struct Seiseki));
// sizeof(struct Seiseki): 44

要素数が分かれば前章のループの5を手動で設定する必要がなくなる。楽ちん。

/* 構造体のメンバの数を自動取得 */
#include <stdio.h>

struct Seiseki {
    char name[32];
    int kokugo;
    int sugaku;
    int eigo;
};

int main() {
    int i = 0;
    int count = 0;

    struct Seiseki myclass[] = {
        {"田中", 80, 80, 55},  {"佐藤", 75, 90, 70}, {"大田", 50, 45, 30},
        {"鈴木", 100, 55, 90}, {"加藤", 60, 85, 35},
    };

    // 構造体のサイズは各メンバのサイズの合計以上
    printf("sizeof(myclass): %lu\\n", sizeof(myclass));
    // sizeof(myclass): 220
    printf("sizeof(struct Seiseki): %lu\\n", sizeof(struct Seiseki));
    // sizeof(struct Seiseki): 44

    // 造体変数myclassのサイズを計算
    count = sizeof(myclass) / sizeof(struct Seiseki);
    printf("count: %d\\n", count);
    // count: 5

    printf("氏名, 国語, 数学, 英語\\n");
    for (i = 0; i < count; i++) {
        printf("%s, %d, %d, %d\\n", myclass[i].name, myclass[i].kokugo,
               myclass[i].sugaku, myclass[i].eigo);
    }

    return 0;
}

/*
sizeof(myclass): 220
sizeof(struct Seiseki): 44
count: 5
氏名, 国語, 数学, 英語
田中, 80, 80, 55
佐藤, 75, 90, 70
大田, 50, 45, 30
鈴木, 100, 55, 90
加藤, 60, 85, 35
 */

データの一括管理で簡潔な記述

今回は簡単ではあるがC言語の構造体について解説した。まだまだC言語は勉強中で今回の内容もなるほどと思いながら作成していた。

データの一括管理ができるとコードを簡潔に書けるだけではなくて、移植生や保守性が上がるからとても重要。しかも.kokugoのようにパッと見で中身もわかるし知っておいて損はないだろう。

これからもC言語頑張る。

関連記事

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-C言語
-,