構造体の基本編集

構造体(こうぞうたい)は、複数の型をまとめて1つの型として扱う手法である。

複数の型を扱うことができる配列 (例: char *intが交互に入る配列) を作ることは不可能である。

しかし、構造体の配列を作成することは可能である。 たとえば商品名と価格の2つ組を扱いたいときには、char * 商品名int 価格からなる構造体を作成する。このようにして、

  • 商品1
    • 商品名: 牛乳
    • 価格: 200
  • 商品2
    • 商品名: オレンジジュース
    • 価格: 150

のようなデータを扱えるようになる。


より厳密には、構造体はメンバオブジェクトの集合を順に割り付けたものである。[1]

なお、構造体のかわりにC言語/共用体を用いても異なる型のまざったデータを扱うことができる。


構造体の宣言編集

上述の商品名と価格の2つ組の例をC言語に落とし込むと以下のようになる:

例1. 構造体の宣言

struct 
{
    // 書き換え不可能な商品名
	const char * name;
    // 価格
	const int price;
} product_entry;

のように宣言することになる。

structはstructureの略であり、C言語では構造体に用いるキーワードとなっている。 構造体を使うには、使うより前に宣言しなければならない。

構造体は1つ以上のメンバを持つ。上のproduct_entryの例ではnamepriceがメンバである。

構造体変数編集

構造体の宣言だけをしてもその構造体の構造に沿った変数 (以下、構造体変数と呼ぶ)は作られない。構造体を活用するには、その構造体変数を作成する。

例2. 構造体変数の宣言と初期化

//例 GCCコンパイラでの実行例
//例 構造体変数の宣言

#include <stdio.h>
#include <string.h>

typedef struct
{
    const char * name;
    const int price;
} product_entry;

void print_product_entry(const product_entry product) {
    printf("商品名 : %s, 価格 : %d\n", product.name, product.price);
}

int main(void)
{
    // 無名構造体
	product_entry milk = { .name = "牛乳", .price = 200 };
	product_entry orange_juice = { .name = "オレンジジュース", .price = 150 };

    print_product_entry(milk);
    print_product_entry(orange_juice);
}
実行結果
商品名 : 牛乳 , 価格 : 200 
商品名 : オレンジジュース , 価格 : 150 

構造体変数を宣言するときは、上述のproduct_entry milk;のように宣言する。

また、構造体変数に代入したい場合など、構造体変数にアクセスしたい場合には、「.(ドット演算子)」を用いて、milk.nameのように記述する。

つまり書式は、

構造体名.構造体変数名

のようになる。

構造体の初期化編集

構造体は、宣言と同時に、値のリストで初期化することもできる。 初期化の記述は次のようになっている。

struct タグ名 変数名 = {値のリスト};

値のリストは「,(コンマ)」で区切ったリストである。

例3. 構造体の初期化

int main(void)
{
	struct
	{
		int i;
		double d;
		char c;
		const char * str;
	} my_struct = { 1234, 3.14, 'a', "Hello, World!" };
}

上の例では、iを1234、dを3.14、cを'a'、strを"Hello, World!"で初期化している。

構造体へのアクセス編集

構造体のメンバにアクセスするには、 「.(ドット)演算子」を用いて、次のように記述する。

構造体の変数名.メンバ名

例4. 構造体の読み取り

//例 構造体へのアクセス
#include <stdio.h>

int main(void)
{
	struct
	{
		int i;
		double d;
		char c;
		const char * str;
	} my_struct;

	printf("整数を入力してください。:");
	scanf("%d", &kouzoutai.i); // 整数入力を my_structure.i に格納する。

	printf("浮動小数点数を入力してください。:");
	scanf("%lf", &kouzoutai.d); // 浮動小数点数入力を my_structure.d に格納する。

	printf("文字(半角1文字)を入力してください。:");
	scanf("%c", &kouzoutai.c); // 文字入力を my_structure.c に格納する。

	printf("文字列(半角31文字、全角15文字まで)を入力してください。:");
	scanf("%31s", kouzoutai.str); // 文字列入力を my_structure.str に格納する。

	printf("kouzoutaiのメンバの値は、%d %f %c %sです。\n", my_structure.i, my_structure.d, my_structure.c, my_structure.str);
}

上の例では、ユーザーからの4つの入力を、 構造体の4つのメンバに格納し、 その4つのメンバの値を表示している。

構造体のコピー編集

配列と異なり、構造体は一度にコピーすることができる。 構造体をコピーする時は、構造体の変数名のみを用いる。 すなわち、次のように記述する。

構造体の変数名 = 構造体の変数名;

これにより、右の構造体の全てのメンバが左の構造体の全てのメンバにコピーされるが、以下の点に注意する必要がある。

漏洩への配慮
コピー元の構造体の隙間にメモリ上のデータが入り込んでいた場合、そのデータ毎コピーされるため、コピー先の構造体が外部からアクセス可能な場合、情報漏洩に繋がるリスクがある[2]
型の一致
コピー元の構造体とコピー先の構造体は、構造体の型が一致していなければいけない[3]。(仮にもし、違う構造体型の構造体どうしがコピーや代入を出来たとしても、正常動作するかアヤシイので、避けたほうが良い。)
浅いコピー(シャローコピー)
ここでいう「構造体のコピー」は浅いコピー(シャローコピー)である。そのため、メンバにポインタが含まれている場合そのアドレスだけがコピーされ、その指している先はコピー元と同一になる。これによって、片方の構造体のポインタが指している先を変更すると、もう片方の構造体のポインタ先の値まで変わってしまう。

例5. 構造体のコピー

#include <stdio.h>

int main(void)
{
    typedef struct {
        int i;
        double d;
        char c;
        const char * str;
    } my_struct_t;
    
    my_struct_t my_struct_1 = {1234, 3.14, 'a', "Hello, World!"}, my_struct_2;

	my_struct_2 = my_struct_1;
	printf("コピー先のメンバの値は、%d %f %c %sです。\n", my_struct_2.i, my_struct_2.d, my_struct2_c, my_struct_2.str);
}

typedefによるコードの短縮編集

typedefを構造体に用いて、コードを短縮することができる。

例6. typedefを用いない構造体の宣言

struct my_struct
{
	int i;
	double d;
	char c;
	const char * str;
};

int main(void)
{
	struct my_struct kouzoutai; // structキーワードが必要
}

上の例は typedef を用いて、下の例のように記述することができる。

例7. typedefを用いた構造体の宣言

typedef struct
{
	int i;
	double d;
	char c;
	char str[32];
} my_struct;

int main(void)
{
	my_struct kouzoutai; // structキーワードは不要
}

以下、本項ではコードを短縮するためにtypedefを用いてstructキーワードを省略する。

構造体の応用編集

構造体を引数に持つ関数編集

構造体を関数の引数にしたい場合、下記のように行う。

例8. 構造体を関数の引数にする

#include <stdio.h>
#include <string.h>
#include <stdlib.h> // system関数に必要

typedef struct {
	const char * name;
	const int price;
} product_entry;

void print_product_entry(product_entry product) {
	printf("商品名 : %s , 価格 : %d \n", product.name, product.price);
}

// Windowsならpauseコマンドを、そうでないならUnix互換とみなしてそれと等価なコマンドをそれぞれ実行する。
void press_any_key(void) {
    #ifdef _WIN32
    system("pause");
    #else
    #ifdef _WIN64
    system("pause");
    #else
    system("read -n1 -r -p 'Press any key to continue...'");
    #endif
    #endif
}

int main(void) {
	product_entry milk = { .name = "牛乳", .price = 200 };
    product_entry orange_juice = { .name = "オレンジジュース", .price = 150 };
	print_product_entry(milk);
	print_product_entry(orange_juice);

    press_any_key();
	return 0;
}

解説編集

  • 構造体の宣言は、main関数よりも前でもできる。
  • main関数の前では構造体の初期化は不可能であることが多い[4]
  • product_entry型を使うときにstructキーワードは不要

構造体の配列編集

上の例では商品が2個しかなかったから、都度構造体変数を宣言すればよかった。しかし、商品が100個になったり、数が不定になったりしたらどうだろうか?個々の変数に割り当てる方法では厳しくなってくる。 そこで、構造体の配列を宣言し使用することでその問題に対処する。

例9. 構造体の配列を作る

#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

typedef struct {
	const char * name;
	const int price;
} product_entry;

void press_any_key(void) {
    // 例8と同様
}

int main(void)
{
	const product_entry products[] = {
        {
            .name = "牛乳",
            .price = 200 
        },
        {
            .name = "オレンジジュース",
            .price = 150
        },
        {
            .name = "トマトジュース",
            .price = 180
        },
        {
            .name = "りんごジュース",
            .price = 220
        }
    };

    for (size_t i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
        printf("商品名: %s, 価格: %d円\n", products[i].name, products[i].price);
    }
    
	press_any_key();
	return 0;
}
実行結果
商品名: 牛乳, 価格: 200円
商品名: オレンジジュース, 価格: 150円
商品名: トマトジュース, 価格: 180円
商品名: りんごジュース, 価格: 220円

書式編集

構造体の配列を宣言する時は、 変数名の後に「[要素数]」をつける。 すなわち、次のように記述する。

struct タグ名 配列の変数名[要素数];

構造体の配列のメンバにアクセスする時は、 変数名の後に[添字]をつける。 すなわち、次のように記述する。

配列の変数名[添字].メンバ名

[]演算子と.演算子は、共に同じ優先順位の演算子で、左から右へと評価されるため (左結合)、次の式と等価である。

(配列の変数名[添字]).メンバ名

例10. 構造体の配列

#include <stdio.h>

int main(void)
{
	struct
	{
		int i;
		double d;
		char c;
		const char * str;
	} struct_array[4] = {
		{.i = 12, .d = 1.2, .c = 'a', .str = "abc"},
		{.i = 34, .d = 3.4, .c = 'b', .str = "def"},
		{.i = 56, .d = 5.6, .c = 'c', .str = "ghi"},
		{.i = 78, .d = 7.8, .c = 'd', .str = "jkl"},
	};

    for (size_t i = 0; i < sizeof(struct_array) / sizeof(struct_array[0]); ++i) {
        printf("struct_array[%zu]のメンバの値は、%d %f %c %sです。\n",
        i, kouzoutai[i].i, kouzoutai[i].d, kouzoutai[i].c, kouzoutai[i].str);
    }
}

上の例では構造体の配列に初期値を与え、 そのメンバの値を表示している。


構造体のネスト編集

構造体のメンバに、構造体を指定することができる。 本項ではこれを「構造体のネスト」と呼ぶことにする。

ネストされた構造体にアクセスする時は、 ネストの最も外側から内側に向かって、 参照しなければならない。

例11. 「構造体のネスト」

#include <stdio.h>

int main(void)
{
	struct my_struct1
	{
		int i;
		double d;
		char c;
	};
	
	struct my_struct2
	{
		int i;
		double d;
		char c;
		struct my_struct1 s;
	} my_struct = {
        .i = 1234,
        .d = 1.23,
        .c = 'a',
        .s = {
            .i = 5678,
            .d = 5.67,
            .c = 'b'
        }
    };

	printf("%d %f %c\n", my_struct.i, my_struct.d, my_struct.c);
	printf("%d %f %c\n", my_struct.s.i, my_struct.s.d, my_struct.s.c);
}

上の例では、my_structのメンバであるsが「構造体のネスト」である。

構造体配列のネスト編集

商品リストの話に戻る。一つの店舗だけでなく、複数の店舗の商品リストを扱いたくなったときはどうすればよいのだろう。

結論から言えば、店舗の構造体を新しく作成し、それに商品の構造体の配列を持たせれば良い。さっそくやってみよう。

例12. 「構造体配列のネスト」

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
	const char * name;
	const int price;
} product_entry;

typedef struct {
  const char * name;
  // const size_t product_count;
  const product_entry products[100];
} store;

void press_any_key(void) {
    // 例8と同様
}

int main(void) {
    store stores[5] = {
        { .name = "ウィキペディア店", .products = {
          { .name = "牛乳", .price = 200 }
        } },
        { .name = "ウィクショナリー店", .products = {
          { .name = "牛乳", .price = 250 }
        } },
        { .name = "ウィキブックス店", .products = {
          { .name = "牛乳", .price = 170 }
        } },
        { .name = "ウィキソース店", .products = {
          { .name = "牛乳", .price = 210 }
        } },
        { .name = "ウィキクォート店", .products = {
          { .name = "牛乳", .price = 190 }
        } }
    };

    const size_t store_count = sizeof(stores) / sizeof(stores[0]);
	for (size_t i = 0; i < store_count; i++) {
        const store st = stores[i];
		printf("名前: %s, 商品:\n", st.name);
        for (size_t j = 0; j < sizeof(st.products) / sizeof(st.products[0]); j++) {
            const product_entry pr = st.products[j];
            if (pr.name == NULL) { 
                break; 
            }
            printf("  名前: %s, 値段: %d", pr.name, pr.price);
        }
        printf("\n");
	}
    press_any_key();
    return 0;
}

例12では、確保される1店舗の商品の配列の要素数(商品数に相当)は、どの店舗も同じである。

C99で可変長配列がサポートされたが、高度なプログラムのための限定的なものであり、様々な制約がある。malloc等の関数とポインタを組み合わせることで、可変長配列のような機能を実装する事もできるが、コードが難しくなり、メンテナンスが難しくなるだろう。[5] [6]

二重配列の構造体変数編集

構造体配列は、二重配列であっても良い。つまり、構造体変数には、二重配列を宣言する事も可能である。

よって構造体のネストを使わなくても、下記コードのように二重配列でも代用できる。

コード例
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
#include <string.h>

struct seisekihyou {
    char seitomei[100];
    int syusseki_bangou;
    int kokugo_tensuu;
    int sugaku_tensuu;
};

int main(void) {
    struct seisekihyou student[3][2]; // 構造体配列の宣言   [組][組内番号]

    strcpy_s(student[0][0].seitomei, 10, "山田123"); // 上記コードとの区別のため
    student[0][0].kokugo_tensuu = 80;
    student[0][0].sugaku_tensuu = 70;

    strcpy_s(student[0][1].seitomei, 10, "佐藤");
    student[0][1].kokugo_tensuu = 60;
    student[0][1].sugaku_tensuu = 90;

    strcpy_s(student[1][0].seitomei, 10, "安部");
    student[1][0].kokugo_tensuu = 78;
    student[1][0].sugaku_tensuu = 65;

    strcpy_s(student[1][1].seitomei, 10, "小泉");
    student[1][1].kokugo_tensuu = 50;
    student[1][1].sugaku_tensuu = 100;

    for (int kumi = 0; kumi <= 1; kumi = kumi + 1) {
        for (int i = 0; i < 2; i = i + 1) {
            printf("名前: %s, 国語: %d点, 数学: %d点\n",
                   student[kumi][i].seitomei, student[kumi][i].kokugo_tensuu,
                   student[kumi][i].sugaku_tensuu);
        }
    }

    system("pause"); // 「続行するには何かキーを押してください . . .」の待機命令


    return 0;
}
実行結果
名前: 山田123, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
名前: 安部, 国語: 78点, 数学: 65点
名前: 小泉, 国語: 50点, 数学: 100点


構造体へのポインタ編集

構造体へのポインタを宣言する時は、 変数名の前に「*」をつける。 すなわち、次のように記述する。

struct タグ名 *ポインタの変数名;

ポインタの指す構造体のメンバにアクセスする時は、 変数名の前に「*」をつけ、それらを「()」で囲む。 すなわち、次のように記述する。

(*ポインタの変数名).メンバ名

「*」と「.」とでは、「.」の方が優先順位が高い演算子であるため、 「()」が必要である。

また「->」(アロー演算子)を用いて、 次のように記述でき、 こちらが一般的に使われる。

ポインタの変数名->メンバ名
//例 構造体へのポインタ
#include <stdio.h>
#include <string.h>

int main(void)
{
	struct
	{
		int i;
		double d;
		char c;
		char str[32];
	} kouzoutai, *pkouzoutai;

	pkouzoutai=&kouzoutai;

	pkouzoutai->i=1234;
	pkouzoutai->d=3.14;
	pkouzoutai->c='a';
	strcpy(pkouzoutai->str,"Hello, World!");

	printf("kouzoutaiのメンバの値は、%d %f %c %sです。\n",
		pkouzoutai->i, pkouzoutai->d, pkouzoutai->c, pkouzoutai->str);
}

上の例では、構造体へのポインタを使って、 構造体のメンバに値を代入したり、 その値を表示したりしている。


なお、strcpyは、文字列をコピーするための命令。strcpyを使うには、string.hのインクルードが必要である。

また、文字列の宣言は、(上記の char str[32]; のように)配列として宣言するのが一般的である。

構造体のポインタを引数にした関数編集

(アロー演算子を使わずに、そのまま)大きな構造体を引数に指定すると、 実引数から仮引数へその構造体全体をコピーするため、 時間がかかってしまう。

高速化したい場合、構造体を引数として呼び出す際に、構造体をコピーせずに(引数として)参照できるようにすれば良いので、そのためにはポインタを活用する必要がある。

構造体へのポインタを引数に指定すると、引数のコピーをせずに元々の構造体を参照するので、コピーの時間が省略されるしメモリも節約されるので、高速に処理できる。

このような処理において、構造体を指すポインタのメンバにアクセスする場合には、(前の節で上述した)アロー演算子を使う。

//例 引数に大きな構造体を指定する
#include <stdio.h>

//この構造体が非常に大きな構造体であるとする
typedef struct
{
	int i;
	double d;
	char c;
	char str[32000];
} sKouzoutai;

void function(const sKouzoutai *kouzoutai) //構造体へのポインタを引数に指定する
{
	printf("kouzoutaiのメンバの値は、%d %f %c %sです。\n", kouzoutai->i, kouzoutai->d, kouzoutai->c, kouzoutai->str);
}

int main(void)
{
	sKouzoutai kouzoutai = {1234, 3.14, 'a', "Hello World!" };
	function(&kouzoutai) ;
}

function(sKouzoutai *kouzoutai) がポインタで呼び出せるようにするために、渡す側 function(&kouzoutai) はアドレスを渡している。

メンバに同じ型へのポインタを持つ構造体編集

構造体のメンバに、自分自身と同じ型へのポインタを指定することができる。 このような構造体を自己参照構造体と呼ぶ。

//例 自己参照構造体
#include <stdio.h>

int main(void)
{
	struct sKouzoutai
	{
		int i;
		double d;
		char c;
		char str[32];
		struct sKouzoutai *next;
	};
	
	struct sKouzoutai a={123, 1.23, 'a', "abc"},
			b={456, 4.56, 'b', "def"},
			c={789, 7.89, 'c', "ghi"};
	struct sKouzoutai *p;	

	a.next=&b;
	b.next=&c;
	c.next=NULL;

	for(p=&a; p; p=p->next)
		printf("%d %f %c %s\n",
			p->i, p->d, p->c, p->str);
}

上の例では、 3つの構造体a、b、cのメンバを、 ポインタpを用いて、表示している。

脚注編集

  1. ^ 『JISX3010:2003』p.24「6.2.5 型」
  2. ^ 構造体 –漏洩させないための注意点–
  3. ^ Wisdomソフト『7.3.1 ポインタにキャストする』、文「異なる構造体形は型に互換性がないため、代入は認められないのです。」
  4. ^ 例: WindowsのMSVC 2019
  5. ^ teratail(webサイト)『C 入れ子構造の構造体にて配列を可変長にしたい』、投稿 2016/04/29 11:56 2020年1月23日に閲覧して確認.
  6. ^ https://stackoverflow.com/questions/32311269/can-we-have-a-struct-element-of-type-variable-length-array 2021年4月13日に閲覧して確認.