「C言語/構造体」の版間の差分

M
内容の整理と例示の一貫性
(構造体を '=' でコピーする際の情報漏洩リスクを追加し、各注意点を itemize。)
M (内容の整理と例示の一貫性)
== 構造体の基本 ==
構造体(こうぞうたい)は、複数の型をまとめて1つの型として扱う手法である。
=== 構造体の基本 ===
構造体(こうぞうたい)は、複数の異なる型のデータを、
まとめて1つのデータ型として扱う方法である。
 
複数の型を扱うことができる[[C言語/配列|配列]] (例: {{code|char *}}と{{code|int}}が交互に入る配列) を作ることは不可能である。
いっぽう、構造体をもちいずに配列だけを使った場合では、異なる型をまとめて扱うことは、不可能な仕様である。(『[[C言語/配列]]』)
 
しかし、C言語は、構造体配列を作成することは可能な仕様になっていである。
たとえば商品名と価格の2つ組を扱いたいときには、{{code|char * 商品名}}と{{code|int 価格}}からなる構造体を作成する。このようにして、
* 商品1
** 商品名: 牛乳
** 価格: 200
* 商品2
** 商品名: オレンジジュース
** 価格: 150
 
のようなデータを扱えるようになる。
このため、たとえば「商品名 = 牛乳、価格=200」・「商品名=オレンジ ジュース、価格=150」のように、文字型の商品名と数値型の価格という異なるデータ型のまざったデータを並べたい場合、まず構造体で「char 商品名、 int 価格」 のような内容の構造体をつくり、それを配列などで必要な個数(この場合は2個)ぶん以上を確保することで、異なる型のまざったデータを扱えるようになる。
 
 
より厳密には構造体」とメンバオブジェクトの集合を順に割り付けたものである。<ref name="型">『JISX3010:2003』p.24「6.2.5 型」</ref>
 
なお、構造体のかわりに、本書のタイトルにもある「[[C言語/共用体]]を用いても異なる型のまざったデータを扱うことができる。
 
 
==== 構造体の型枠の宣言 ====
上述の牛乳などの商品リスト名と価格の2つ組の例では、たをC言語に落えばし込むと以下のようになる:
 
例1. 構造体の宣言
<syntaxhighlight lang="C">
struct
//例 構造体の宣言
 
int main(void)
{
// 書き換え不可能な商品名
struct
const char * name;
{
// 価格
char syouhinmei[32]; // 商品名
const int price;
int kakaku; // 価格
} product_entry;
} syouhin_list;
}
</syntaxhighlight>
 
のように宣言することになる。
 
structは'''struct'''ureの略であり、C言語では構造体に用いるキーワードとなっている。
struct は「ストラクト」と読む。英語で、なにかの構造のことを「structure」(ストラクチャー)という。なお、日常英語に struct (ストラクト)という語句は無い。(ストラクト struct はコンピュータ業界の専門用語。ふつうの英和辞典をしらべても、struct は無い。)
構造体を使うには、使うより前に宣言しなければならない。
 
構造体は1つ以上の'''メンバ'''を持つ。上の{{code|product_entry}}の例では{{code|name}}と{{code|price}}がメンバである。
さて、構造体を使うには、まず構造体の型枠を宣言し、
次にその型枠を使って構造体を宣言しなければならない。
<ref>構造体の型枠という用語はこの教科書独自のものであり、『JISX3010:2003』に出てくるわけではないので注意してください。</ref>
 
=== 構造体変数 ===
構造体の型枠の宣言の記述は次のようになっている。
構造体の宣言だけをしてもその構造体の構造に沿った変数 (以下、'''構造体変数'''と呼ぶ)は作られない。構造体を活用するには、その構造体変数を作成する。
<syntaxhighlight lang="C">
struct タグ名
{
データ型 メンバ名;
:
:
};
</syntaxhighlight>
タグ名で、構造体の型枠に名前をつける。
 
例2. 構造体が持つ1つ1つ変数データを'''メンバ'''宣言いう。初期化
使用するメンバの数だけ、
「データ型 メンバ名;」を含める。
構造体の型枠の宣言は、
単に構造体の型枠を作るに過ぎず、
メモリ上に領域は確保されない。
 
C++言語では構造体などのメンバに関数を含むこともでき「メンバ関数」などと呼ぶ。しかし、無印のC言語には、構造体に関数を含むことはできない(つまり、無印Cに「メンバ関数」の機能は無い)。
 
* どうしても構造体と関数をグループにしたい場合
なお、どうしても無印C言語の構造体で、その構造体を関数と関連付けたい場合、たとえば、構造体の中で、
<syntaxhighlight lang="C">
//例 GCCコンパイラでの実行例
struct タグ名
//例 構造体変数の宣言
{
:
:
int kansuu_flag;
:
:
};
</syntaxhighlight>
のように、関数を使うかどうかの判定用の数値を用意する。そして、構造体の外に、その関数の具体的な処理内容を書く。さらにmain関数などの内部においてIf文などで、kansuu_flagが(たとえば)1の場合にのみ、その関数を実行する、などの条件設定を行う。このようにして、ある構造体の中で、間接的に、関数と構造体を、グループ扱いすることができる。(なお、上述の kansuu_flag のように、ある処理を実行するか否かの判定をするための数値のことを、IT業界の用語で「フラグ」と、一般に言う。)
 
 
==== 構造体変数 ====
上述の飲料商品リストのコードのように構造体だけをつくっても、その構造体で示したパターンに従った変数は、まだ作られてはいない。
 
構造体で示したパターンに従った変数を、構造体変数と言う。下記のように宣言する。
 
<syntaxhighlight lang="C">
//例 GCCコンパイラでの実行例
//例 構造体変数の宣言
#include <stdio.h>
#include <string.h>
 
typedef struct
int main(void)
{
const char * name;
struct syouhin_list
const int price;
{
} product_entry;
char syouhinmei[32]; // 商品名
int kakaku; // 価格
};
 
void print_product_entry(const product_entry product) {
printf("商品名 : %s, 価格 : %d\n", product.name, product.price);
}
 
int main(void)
struct syouhin_list gyuunyuu; // 構造体 syouhin の構造体変数 gyuunyuu を宣言
{
struct syouhin_list orenji_jyuuusu; // 構造体 syouhin の構造体変数 orenji_jyuuusu を宣言
// 無名構造体
 
product_entry milk = { .name = "牛乳", .price = 200 };
strcpy(gyuunyuu.syouhinmei, "牛乳"); // C言語では文字列の代入には strcpy( , ) を使わなければならない
product_entry orange_juice = { .name = "オレンジジュース", .price = 150 };
gyuunyuu.kakaku = 200;
 
strcpy(orenji_jyuuusu.syouhinmei,"オレンジジュース");
orenji_jyuuusu.kakaku = 150;
 
print_product_entry(milk);
printf("商品名 : %s , 価格 : %d \n", gyuunyuu.syouhinmei, gyuunyuu.kakaku);
print_product_entry(orange_juice);
printf("商品名 : %s , 価格 : %d \n", orenji_jyuuusu.syouhinmei, orenji_jyuuusu.kakaku);
}
</syntaxhighlight>
商品名 : オレンジジュース , 価格 : 150
 
構造体変数を宣言するときは、上述の{{code|product_entry milk;}}のように宣言する。
::※ Visual Studio だと、strcpy_sを使っても、なぜか「牛乳」のデータが文字化けして失敗する。
 
また、構造体変数に代入したい場合など、構造体変数にアクセスしたい場合には、「.(ドット演算子)」を用いて、{{code|milk.name}}のように記述する。
構造体変数を宣言するときは、上述の
<code> struct syouhin_list gyuunyuu ; </code>
 
のように、
 
<code> struct 呼び出し元の構造体名 変数名 ; </code>
 
の書式で宣言する。
 
 
また、構造体変数に代入したい場合など、構造体変数にアクセスしたい場合には、「.(ドット演算子)」を用いて、
 
<code> gyuunyuu.kakaku </code>
 
のように記述する。
 
つまり書式は、
のようになる。
 
=== 構造体の初期化 ===
 
構造体は、宣言と同時に、値のリストで初期化することもできる。
==== 構造体の宣言 ====
構造体の宣言の記述は次のようになっている。
 
<syntaxhighlight lang="C">
struct タグ名 変数名のリスト;
</syntaxhighlight>
 
タグ名で、構造体の型枠を指定する。
変数名のリストで、構造体に名前をつける。
複数の構造体を宣言する時は、変数名を「,(コンマ)」で区切る。
構造体の宣言で、タグ名が指す構造体の型枠を使って、メモリ上に領域を確保する。
 
なお、構造体の型枠の宣言 と 構造体の宣言 とを、同時に行うこともできる。
次のように記述する。
 
<syntaxhighlight lang="C">
struct タグ名
{
データ型 メンバ名;
:
:
} 変数名のリスト;
</syntaxhighlight>
この場合、タグ名は省略することができる。
 
<syntaxhighlight lang="C">
//例 構造体の宣言
 
int main(void)
{
struct
{
int i;
double d;
char c;
char str[32];
} kouzoutai;
}
</syntaxhighlight>
上の例では、int型の変数i、double型の変数d、
char型の変数c、char型の配列strの4つのメンバを持つ、
kouzoutaiという名前の構造体を宣言している。
 
また、構造体は、宣言と同時に、値のリストで初期化することもできる。
初期化の記述は次のようになっている。
<syntaxhighlight lang="C">
struct タグ名 変数名 = {値のリスト};
</syntaxhighlight>
値のリストは「,(コンマ)」で区切った定数のリストである。
 
例3. 構造体の初期化
<syntaxhighlight lang="C">
//例 構造体の宣言と初期化
int main(void)
{
double d;
char c;
const char * str[32];
} kouzoutaimy_struct = { 1234, 3.14, 'a', "Hello, World!" };
}
</syntaxhighlight>
上の例では、iを1234、dを3.14、cを'a'、strを"Hello, World!"で初期化している。
 
==== 構造体のメンバへのアクセス ====
構造体のメンバにアクセスするには、
「.(ドット)演算子」を用いて、次のように記述する。
 
</syntaxhighlight>
 
例4. 構造体の読み取り
<syntaxhighlight lang="C">
//例 構造体へのアクセス
double d;
char c;
const char * str[32];
} kouzoutaimy_struct;
 
printf("整数を入力してください。:");
scanf("%d", &kouzoutai.i); // 整数入力を kouzoutaimy_structure.i に格納する。
 
printf("浮動小数点数を入力してください。:");
scanf("%lf", &kouzoutai.d); // 浮動小数点数入力を kouzoutaimy_structure.d に格納する。
 
printf("文字(半角1文字)を入力してください。:");
scanf(" %c", &kouzoutai.c); // 文字入力を kouzoutaimy_structure.c に格納する。
 
printf("文字列(半角31文字、全角15文字まで)を入力してください。:");
scanf("%31s", kouzoutai.str); // 文字列入力を kouzoutaimy_structure.str に格納する。
 
printf("kouzoutaiのメンバの値は、%d %f %c %sです。\n", kouzoutaimy_structure.i, kouzoutaimy_structure.d, kouzoutaimy_structure.c, kouzoutaimy_structure.str);
}
</syntaxhighlight>
その4つのメンバの値を表示している。
 
==== 構造体のコピー ====
配列と異なり、構造体は一度にコピーすることができる。
構造体をコピーする時は、構造体の変数名のみを用いる。
これにより、右の構造体の全てのメンバが左の構造体の全てのメンバにコピーされるが、以下の点に注意する必要がある。
 
*; '''漏洩への配慮'''
: コピー元の構造体の隙間にメモリ上のデータが入り込んでいた場合、そのデータ毎コピーされるため、コピー先の構造体が外部からアクセス可能な場合、情報漏洩に繋がるリスクがある<ref>[https://kazkobara.github.io/c-resource-mgmt/struct.html 構造体 –漏洩させないための注意点–]</ref>。
 
*; '''型の一致'''
: コピー元の構造体とコピー先の構造体は、構造体の型が一致していなければいけない<ref>[http://www.wisdomsoft.jp/343.html Wisdomソフト『7.3.1 ポインタにキャストする』、文「異なる構造体形は型に互換性がないため、代入は認められないのです。」]</ref>。(仮にもし、違う構造体型の構造体どうしがコピーや代入を出来たとしても、正常動作するかアヤシイので、避けたほうが良い。)
 
*; '''浅いコピー(シャローコピー)'''
: ここでいう「構造体のコピー」は浅いコピー(シャローコピー)である。そのため、メンバにポインタまれている場合、こ方法ではポインタの値アドレスだけがコピーされ、その指している先はコピー元と同一になる。これによって、片方の構造体のポインタが指しているの値を変更すると、もう片方の構造体のポインタ先の値まで変わってしまう、いわゆる浅いコピー(シャローコピー)となる
 
例5. 構造体のコピー
<syntaxhighlight lang="C">
//例 構造体のコピー
#include <stdio.h>
 
int main(void)
{
typedef struct {
int i;
{
double d;
int i;
char c;
double d;
const char c* str;
} my_struct_t;
char str[32];
} kouzoutai1 = {1234,3.14,'a',"Hello, World!"}, kouzoutai2;
my_struct_t my_struct_1 = {1234, 3.14, 'a', "Hello, World!"}, my_struct_2;
 
my_struct_2 = my_struct_1;
kouzoutai2 = kouzoutai1;
printf("kouzoutai2コピー先のメンバの値は、%d %f %c %sです。\n", kouzoutai2my_struct_2.i, kouzoutai2my_struct_2.d, kouzoutai2.cmy_struct2_c, kouzoutai2my_struct_2.str);
}
</syntaxhighlight>
typedefを構造体に用いて、コードを短縮することができる。
 
例6. typedefを用いない構造体の宣言
<syntaxhighlight lang="C">
struct my_struct
//例 typedefを用いない構造体の宣言
 
struct sKouzoutai
{
int i;
double d;
char c;
const char * str[32];
};
 
int main(void)
{
struct sKouzoutaimy_struct kouzoutai; //構造体の宣言には「struct タグ名」structキーワードが必要
}
</syntaxhighlight>
上の例は typedef を用いて、下の例のように記述することができる。
 
例7. typedefを用いた構造体の宣言
<syntaxhighlight lang="C">
//例 typedefを用いた構造体の宣言
 
typedef struct
{
char c;
char str[32];
} sKouzoutaimy_struct;
 
int main(void)
{
sKouzoutaimy_struct kouzoutai; // 構造体の宣言にstructキーワード「構造体の別名」が必
}
</syntaxhighlight>
以下、本項ではコードを短縮するために{{code|typedef}}を用いて{{code|struct}}キーワードを省略する。
 
=== 構造体の応用 ===
==== 構造体を引数に持つ関数 ====
構造体を関数の引数にしたい場合、下記のように行う。Windowsの Visual C++ での構造体配列のコードの例を下記に示す
 
例8. 構造体を関数の引数にする
<syntaxhighlight lang="C">
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのsystem関数に必要
 
typedef struct syouhin_list {
const char * name;
char syouhinmei[32]; // 商品名
const int kakakuprice; // 価格
} product_entry;
};
 
void print_product_entry(product_entry product) {
printf("商品名 : %s , 価格 : %d \n", product.name, product.price);
}
 
// Windowsならpauseコマンドを、そうでないならUnix互換とみなしてそれと等価なコマンドをそれぞれ実行する。
void kansuu(struct syouhin_list nomimono) // このように、引数では struct から宣言する必要がある
void press_any_key(void) {
{
#ifdef _WIN32
printf("商品名 : %s , 価格 : %d \n", nomimono.syouhinmei, nomimono.kakaku);
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 };
struct syouhin_list gyuunyuu; // 構造体 syouhin の構造体変数 gyuunyuu を宣言
product_entry orange_juice = { .name = "オレンジジュース", .price = 150 };
struct syouhin_list orenji_jyuuusu; // 構造体 syouhin の構造体変数 orenji_jyuuusu を宣言
print_product_entry(milk);
print_product_entry(orange_juice);
 
press_any_key();
strcpy_s(gyuunyuu.syouhinmei, 20, "牛乳"); // C言語では文字列の代入には strcpy( , ) を使わなければならない
gyuunyuu.kakaku = 200;
 
strcpy_s(orenji_jyuuusu.syouhinmei,20, "オレンジジュース");
orenji_jyuuusu.kakaku = 150;
 
kansuu(gyuunyuu); // 引数は構造体変数だけで良い。通常の関数と同様に、型は不要。
kansuu(orenji_jyuuusu); // 引数は構造体変数だけで良い
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
return 0;
}
</syntaxhighlight>
 
==== 解説 ====
* 構造体の宣言は、main関数よりも前でもできる。
* main関数の前では構造体の初期化は不可能であることが多い<ref>例: WindowsのMSVC 2019</ref>。
* {{code|product_entry}}型を使うときに{{code|struct}}キーワードは不要
 
=== 構造体の配列 ===
* 細かなノウハウ1
上の例では商品が2個しかなかったから、都度構造体変数を宣言すればよかった。しかし、商品が100個になったり、数が不定になったりしたらどうだろうか?個々の変数に割り当てる方法では厳しくなってくる。
構造体を呼び出す側の <code>gyuunyuu.kakaku</code> では、int や char などの型を'''つけない'''ように気をつけよう。
そこで、構造体の配列を宣言し使用することでその問題に対処する。
 
実務では、いったん構造体を使わずにモデルとなる(非構造体の)通常の変数を作ってから、あとから手作業で構造体に置き換える場合もよくある。この置き換えの時、呼び出し側の場所でよく int や char などの型宣言が行われている場合があるが、構造体では型宣言は既に構造体の宣言の場所で行われているので不要だし、それに呼び出し側で型宣言するとコンパイルエラーになるので、どちらせにせよ呼び出し側からは重複する型宣言を除去する必要がある。
 
 
* 細かなノウハウ2
構造体の宣言は、main関数よりも上でも可能だが、つまり、
<syntaxhighlight lang="C">
struct syouhin_list {
char syouhinmei[32]; // 商品名
int kakaku; // 価格
};
</syntaxhighlight>
のような宣言はmain関数よりも上で行えるが、
 
しかし、構造体の各要素の初期値代入(たとえば
<syntaxhighlight lang="C">
gyuunyuu.kakaku = 200;
</syntaxhighlight>
など)
 
は一般のコンパイラ(たとえばWindowsならVisual Studio 2019)ではmain関数よりも上では不可能である。
 
 
==== 構造体の配列 ====
たとえば、学校での生徒の成績表のような、文字型と数値型が混ざるデータ構造を作りたい場合、つまり、異なる型を組み合わせたデータ構造をつくる場合には、構造体(または共用体)を要素とした配列をつくる必要がある。このような、配列と構造体を組み合わせたデータ構造は、ネット上では「構造体配列」または「構造体の配列」などと紹介されている。
 
:※ かならずしも配列から構造体を呼び出さなくても良いが、実務上は、もし成績の構造体だけをつくっても、それを配列にしないと、実用的には不便である。なぜなら、配列でない個別の変数(構造体変数)に代入していく方式だと、たとえば成績表なら、クラスの人数ぶんだけ構造体変数を用意したりするハメになりかねない。たとえば1クラス当たり35人の学級なら、35個の構造体変数を用意するのは煩雑である。
:よって成績表をつくるためには、まず構造体の機能で1人ぶんの氏名と成績とのデータの結びつけを作り、それを配列で生徒数のぶんだけ複製するのである。
 
学校の成績表なら、まだクラスの生徒数がせいぜい数十名なので、構造体変数を数十個書くという方法でも、なんとか対応できる。だが、もし、大企業による提供サービスの登録者の会員名簿とかだと、会員数が数万名とかになるので、もはや構造体変数を1個ずつ書いていく方式は、不可能である。
 
 
Windowsの Visual C++ での構造体配列のコードの例を下記に示す。
 
(※ 他のコンパイラだとエラーになる場合もある。)
 
例9. 構造体の配列を作る
<syntaxhighlight lang="cpp">
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
typedef struct {
struct seisekihyou {
const char seitomei[100]* name;
const int syusseki_bangouprice;
} product_entry;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
intvoid mainpress_any_key(void) {
// 例8と同様
{
struct seisekihyou student[2]; // 構造体配列の宣言
 
strcpy_s( student[0].seitomei, 10 ,"山田");
student[0].kokugo_tensuu = 80;
student[0].sugaku_tensuu = 70;
 
strcpy_s(student[1].seitomei, 10 , "佐藤");
student[1].kokugo_tensuu = 60;
student[1].sugaku_tensuu = 90;
 
int i = 0;
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].seitomei, student[i].kokugo_tensuu, student[i].sugaku_tensuu);
}
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
 
return 0;
}
</syntaxhighlight>
 
:※ 上記のコードの動作確認として、 Visual Studio 2017 のCommunity 無料版でのC++で動作を確認ずみである。
 
 
;実行結果
名前: 山田, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
 
 
いっぽう、gccコンパイラの場合、strcpy_s ではなく strcpy にしないと行けないので、上記のコードは使えないので、gccで動作させるには下記のようなコードになる。
 
<syntaxhighlight lang="cpp">
// gcc での構造体配列の例
 
#include <stdio.h>
#include <string.h>
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
int main(void)
{
const product_entry products[] = {
struct seisekihyou student[2]; // 構造体配列の宣言
{
 
.name = "牛乳",
strcpy( student[0].seitomei ,"山田");
.price = 200
student[0].kokugo_tensuu = 80;
},
student[0].sugaku_tensuu = 70;
{
 
.name = "オレンジジュース",
strcpy(student[1].seitomei, "佐藤");
.price = 150
student[1].kokugo_tensuu = 60;
},
student[1].sugaku_tensuu = 90;
{
 
.name = "トマトジュース",
int i = 0;
.price = 180
 
for (i = 0; i<2; i = i + 1) },
{
{
.name = "りんごジュース",
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].seitomei, student[i].kokugo_tensuu, student[i].sugaku_tensuu);
.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;
}
</syntaxhighlight>
:※ 上記のコードの動作確認として、Fedora 28 (Linuxの一種)上での gcc で動作を確認ずみ。
 
;実行結果
<pre>
商品名: 牛乳, 価格: 200円
商品名: オレンジジュース, 価格: 150円
商品名: トマトジュース, 価格: 180円
商品名: りんごジュース, 価格: 220円
</pre>
 
==== 構造体の配列の書式 ====
構造体の配列を宣言する時は、
変数名の後に「[要素数]」をつける。
 
構造体の配列のメンバにアクセスする時は、
変数名の後に{{code|[添字]}}をつける。
すなわち、次のように記述する。
 
</syntaxhighlight>
 
{{code|[]}}演算子と{{code|.}}演算子は、共に同じ優先順位の演算子で、左から右へと評価されるため (左結合)、次の式と等価である。
「[]」と「.」とは、
同じ優先順位の演算子で、
左から右へと評価されるため、
次のような意味になる。
 
<syntaxhighlight lang="C">
</syntaxhighlight>
 
例10. 構造体の配列
<syntaxhighlight lang="C">
//例 構造体の配列
#include <stdio.h>
 
double d;
char c;
const char * str[32];
} kouzoutaistruct_array[4] = {
{.i = 12, .d = 1.2, .c = 'a', .str = "abc"},
{
{12.i = 34, 1.2d = 3.4, .c = 'ab', .str = "abcdef"},
{34.i = 56, 3.4d = 5.6, .c = 'bc', .str = "defghi"},
{56.i = 78, 5.6d = 7.8, '.c = 'd', .str = "ghijkl"},
};
{78, 7.8, 'd', "jkl"},
};
 
for (size_t i = 0; i < sizeof(struct_array) / sizeof(struct_array[0]); ++i) {
int n;
printf("struct_array[%zu]のメンバの値は、%d %f %c %sです。\n",
for(n=0; n<4; ++n)
i, kouzoutai[i].i, kouzoutai[i].d, kouzoutai[i].c, kouzoutai[i].str);
printf("kouzoutai[%d]のメンバの値は、%d %f %c %sです。\n",
}
n, kouzoutai[n].i, kouzoutai[n].d, kouzoutai[n].c, kouzoutai[n].str);
}
</syntaxhighlight>
 
 
==== 構造体のネスト ====
構造体のメンバに、構造体を指定することができる。
本項ではこれを構造体のネストと呼ぶことにする
 
ネストされた構造体にアクセスする時は、
参照しなければならない。
 
例11. 「構造体のネスト」
<syntaxhighlight lang="C">
//例 構造体のネスト
#include <stdio.h>
 
int main(void)
{
struct sKouzoutai1my_struct1
{
int i;
};
struct sKouzoutai2my_struct2
{
int i;
double d;
char c;
struct sKouzoutai1my_struct1 ks;
} my_struct = {
}kouzoutai;
.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);
kouzoutai.i=1234;
printf("%d %f %c\n", my_struct.s.i, my_struct.s.d, my_struct.s.c);
kouzoutai.d=1.23;
kouzoutai.c='a';
kouzoutai.k.i=5678;
kouzoutai.k.d=5.67;
kouzoutai.k.c='b';
 
printf("%d %f %c\n",kouzoutai.i,kouzoutai.d,kouzoutai.c);
printf("%d %f %c\n",kouzoutai.k.i,kouzoutai.k.d,kouzoutai.k.c);
}
</syntaxhighlight>
 
上の例では、kouzoutai{{code|my_struct}}のメンバに、である{{code|s}}が「構造体のネスト」である。
kという構造体がネストされていて、
それらのメンバに値を代入したり、
メンバの値を表示している。
 
==== 構造体配列のネスト ====
商品リストの話に戻学級。一つ生徒店舗だけでなく、複数データベー店舗の商品リ作りたたくら、前節のよったときはどに構造体配列を使えすれだろう。
 
結論から言えば、店舗の構造体を新しく作成し、それに商品の構造体の配列を持たせれば良い。さっそくやってみよう。
だが、1つの学年に学級が幾つもある場合、別別に構造体を作るのは避けたい。
 
例12. 「構造体配列のネスト」
さらに、実用的な事を考えれば、1つの学校に、幾つもの学年があるわけであるので、それに対応したい。
 
この事から、構造体配列を、構造体でネストする必要性が生じてくる。
 
まず、前節の構造体配列の内容を、さらにネスト化してみよう(表示結果はまだ同じ)。
 
 
;コード例(windows版)
<syntaxhighlight lang="cpp">
// windows版 gcc
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
typedef struct {
struct seisekihyou {
const char seitomei[100]* name;
const int syusseki_bangouprice;
} product_entry;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
typedef struct {
const char * name;
// const size_t product_count;
const product_entry products[100];
} store;
 
void press_any_key(void) {
struct gakkyuu {
// 例8と同様
struct seisekihyou kumi;
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
strcpy_s( student[0].kumi.seitomei, 10 ,"山田");
student[0].kumi.kokugo_tensuu = 80;
student[0].kumi.sugaku_tensuu = 70;
 
strcpy_s(student[1].kumi.seitomei, 10 , "佐藤");
student[1].kumi.kokugo_tensuu = 60;
student[1].kumi.sugaku_tensuu = 90;
 
int i = 0;
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi.seitomei, student[i].kumi.kokugo_tensuu, student[i].kumi.sugaku_tensuu);
}
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
 
return 0;
}
</syntaxhighlight>
 
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++) {
<pre>
const store st = stores[i];
名前: 山田, 国語: 80点, 数学: 70点
printf("名前: 佐藤%s, 国語商品: 60点\n", 数学: 90点st.name);
for (size_t j = 0; j < sizeof(st.products) / sizeof(st.products[0]); j++) {
</pre>
const product_entry pr = st.products[j];
:(Windows7上で、gccコンパイラで確認。)
if (pr.name == NULL) {
である。(※ 得点は最初の例と同じまま)
break;
 
}
とりあえず、構造体配列をネスト化することは、これで可能である事が実験できた。
printf(" 名前: %s, 値段: %d", pr.name, pr.price);
 
}
なお、Linux版gccでは、コードは下記のようになる。strcpy()関数がwindowsと違っている(windowsでは、ここは <code>strcpy_s() </code>関数になっている 。また <code>system("pause");</code> がコメントアウト)。
printf("\n");
 
 
;Linux版のコード例
<syntaxhighlight lang="cpp">
// linux版gcc
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
 
struct gakkyuu {
struct seisekihyou kumi;
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
strcpy( student[0].kumi.seitomei,"山田");
student[0].kumi.kokugo_tensuu = 80;
student[0].kumi.sugaku_tensuu = 70;
 
strcpy(student[1].kumi.seitomei , "佐藤");
student[1].kumi.kokugo_tensuu = 60;
student[1].kumi.sugaku_tensuu = 90;
 
int i = 0;
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi.seitomei, student[i].kumi.kokugo_tensuu, student[i].kumi.sugaku_tensuu);
}
press_any_key();
 
// system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
 
return 0;
}
 
</syntaxhighlight>
:(※ 上記コードの動作確認については、Fedora33 にて 2011年1月24日 に動作確認ずみ。)
 
上記コードの動作結果はwindows版と同じであるので、説明を省略する。
:※ 以降、Linux版の例については、本節の最後の例を除いて省略する。つまり、上記の最初の例と、最後の例だけLinux版コードを紹介する。それ以外の場合については、その紹介例から容易にコードを作れる。
 
 
しかし、私たちが作成したいのは、これを2学級以上に対応させる事である。(上記コードは、まだ1学級ぶんしか対応していない。
 
これを2学級以上に対応させるには、単に下記のように
 
<syntaxhighlight lang="cpp">
struct gakkyuu {
struct seisekihyou kumi[2];
};
</syntaxhighlight>
のように、呼び出し元のほうの構造体で配列変数を使えばいいだけである。
 
論より証拠、下記のコードは実行できる。
 
;コード例(windows版)
<syntaxhighlight lang="cpp">
// windows版
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
 
struct gakkyuu {
struct seisekihyou kumi[2];
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
strcpy_s( student[0].kumi[0].seitomei, 10 ,"山田gggg"); // 前コードと区別のため、文字を追加している
student[0].kumi[0].kokugo_tensuu = 80;
student[0].kumi[0].sugaku_tensuu = 70;
 
strcpy_s(student[1].kumi[0].seitomei, 10 , "佐藤");
student[1].kumi[0].kokugo_tensuu = 60;
student[1].kumi[0].sugaku_tensuu = 90;
 
int i = 0;
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi[0].seitomei, student[i].kumi[0].kokugo_tensuu, student[i].kumi[0].sugaku_tensuu);
}
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
 
return 0;
}
</syntaxhighlight>
 
;実行結果
<pre>
名前: 山田gggg, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
</pre>
 
となる。(※ 得点は最初の例と同じまま)
 
 
では、実際に2学級目のデータを入れたコードを作成してみて、実行して試してみよう。
 
 
;コード例
<syntaxhighlight lang="cpp">
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
 
struct gakkyuu {
struct seisekihyou kumi[2];
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
strcpy_s( student[0].kumi[0].seitomei, 10 ,"山田gggg");
student[0].kumi[0].kokugo_tensuu = 80;
student[0].kumi[0].sugaku_tensuu = 70;
 
strcpy_s(student[1].kumi[0].seitomei, 10 , "佐藤");
student[1].kumi[0].kokugo_tensuu = 60;
student[1].kumi[0].sugaku_tensuu = 90;
 
int i = 0;
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi[0].seitomei, student[i].kumi[0].kokugo_tensuu, student[i].kumi[0].sugaku_tensuu);
}
 
 
// 2学級目
strcpy_s( student[0].kumi[1].seitomei, 10 ,"安部");
student[0].kumi[1].kokugo_tensuu = 78;
student[0].kumi[1].sugaku_tensuu = 65;
 
strcpy_s(student[1].kumi[1].seitomei, 10 , "小泉");
student[1].kumi[1].kokugo_tensuu = 50;
student[1].kumi[1].sugaku_tensuu = 100;
 
// int i = 0; // 宣言済みなのでコメントアウト
 
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi[1].seitomei, student[i].kumi[1].kokugo_tensuu, student[i].kumi[1].sugaku_tensuu);
}
 
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
 
return 0;
}
</syntaxhighlight>
 
;実行結果
<pre>
名前: 山田gggg, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
名前: 安部, 国語: 78点, 数学: 65点
名前: 小泉, 国語: 50点, 数学: 100点
</pre>
 
このように、たしかに2学級目も、データベースを作成できている。
 
 
for文を使って短くすれば、下記のようになる。
;コード例
<syntaxhighlight lang="cpp">
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
 
struct gakkyuu {
struct seisekihyou kumi[2];
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
 
// 1学級目
strcpy_s( student[0].kumi[0].seitomei, 10 ,"山田gggg");
student[0].kumi[0].kokugo_tensuu = 80;
student[0].kumi[0].sugaku_tensuu = 70;
 
strcpy_s(student[1].kumi[0].seitomei, 10 , "佐藤");
student[1].kumi[0].kokugo_tensuu = 60;
student[1].kumi[0].sugaku_tensuu = 90;
 
 
// 2学級目
strcpy_s( student[0].kumi[1].seitomei, 10 ,"安部test"); // 前コードとの実行結果の区別のため、文字を追加
student[0].kumi[1].kokugo_tensuu = 78;
student[0].kumi[1].sugaku_tensuu = 65;
 
strcpy_s(student[1].kumi[1].seitomei, 10 , "小泉");
student[1].kumi[1].kokugo_tensuu = 50;
student[1].kumi[1].sugaku_tensuu = 100;
 
int i = 0;
 
for(int temp = 0; temp < 2 ; temp = temp +1 ){
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi[temp].seitomei, student[i].kumi[temp].kokugo_tensuu, student[i].kumi[temp].sugaku_tensuu);
}
} // for temp の終わり
 
system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
return 0;
}
</syntaxhighlight>
 
例12では、確保される1店舗の商品の配列の要素数(商品数に相当)は、どの店舗も同じである。
;実行結果
<pre>
名前: 山田gggg, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
名前: 安部test, 国語: 78点, 数学: 65点
名前: 小泉, 国語: 50点, 数学: 100点
</pre>
 
 
上述のような方法だと、確保される1学級あたりの要素数(生徒数に相当)は、どの学級も同じである。
 
一つの学級だけ多くの要素数を確保しようと思っても、不可能である。
 
C言語の配列はそもそも、標準的な方法では可変長配列を確保していない(C99で可変長配列のサポートとあるが、高度なプログラムのための限定的なものであり、様々な制約があり、決して初心者むけではない。)。それ以前からも malloc 等の関数を使って動的にメモリを確保することとポインタとを組み合わせることで、可変長配列のような機能を実装する事もできるが、コードが難しくなり、メンテナンスが難しくなるだろう。<ref>[https://teratail.com/questions/33699 teratail(webサイト)『C 入れ子構造の構造体にて配列を可変長にしたい』、投稿 2016/04/29 11:56] 2020年1月23日に閲覧して確認.</ref> <ref> https://stackoverflow.com/questions/32311269/can-we-have-a-struct-element-of-type-variable-length-array 2021年4月13日に閲覧して確認.</ref>特に、日本語環境などのメモリ確保領域を計算して管理するのは、かなり困難であろう。
 
このため、構造体配列でも、mallocなどを用いない簡単な方法では、可変長配列を実装することは無理そうである。
 
よって、あきらめて、すべての構造体配列の1つの配列あたりの要素数を同じにするのが良い。
 
どうしても異なる要素数の構造体配列が必要なら、ネストとは別々に新規の構造体配列として、その配構造体列を分けるべきだろう。
 
 
なお、Linux版のコード例を、本節の最後の例なので紹介する。
 
;Linux版のコード例
<syntaxhighlight lang="cpp">
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
 
struct seisekihyou {
char seitomei[100];
int syusseki_bangou;
int kokugo_tensuu;
int sugaku_tensuu;
};
 
 
struct gakkyuu {
struct seisekihyou kumi[2];
};
 
 
int main(void)
{
struct gakkyuu student[2]; // 構造体配列の宣言
 
 
// 1学級目
strcpy( student[0].kumi[0].seitomei, "山田gggg");
student[0].kumi[0].kokugo_tensuu = 80;
student[0].kumi[0].sugaku_tensuu = 70;
 
strcpy(student[1].kumi[0].seitomei, "佐藤");
student[1].kumi[0].kokugo_tensuu = 60;
student[1].kumi[0].sugaku_tensuu = 90;
 
 
// 2学級目
strcpy( student[0].kumi[1].seitomei, "安部test"); // 前コードとの実行結果の区別のため、文字を追加
student[0].kumi[1].kokugo_tensuu = 78;
student[0].kumi[1].sugaku_tensuu = 65;
 
strcpy(student[1].kumi[1].seitomei, "小泉");
student[1].kumi[1].kokugo_tensuu = 50;
student[1].kumi[1].sugaku_tensuu = 100;
 
int i = 0;
 
for(int temp = 0; temp < 2 ; temp = temp +1 ){
for (i = 0; i<2; i = i + 1)
{
printf("名前: %s, 国語: %d点, 数学: %d点\n", student[i].kumi[temp].seitomei, student[i].kumi[temp].kokugo_tensuu, student[i].kumi[temp].sugaku_tensuu);
}
} // for temp の終わり
 
// system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
return 0;
}
</syntaxhighlight>
:※(動作確認ずみ。Fedora33上の gcc および ./a.out にて2021年1月24日に動作確認。)
 
C99で可変長配列がサポートされたが、高度なプログラムのための限定的なものであり、様々な制約がある。{{code|malloc}}等の関数とポインタを組み合わせることで、可変長配列のような機能を実装する事もできるが、コードが難しくなり、メンテナンスが難しくなるだろう。<ref>[https://teratail.com/questions/33699 teratail(webサイト)『C 入れ子構造の構造体にて配列を可変長にしたい』、投稿 2016/04/29 11:56] 2020年1月23日に閲覧して確認.</ref> <ref> https://stackoverflow.com/questions/32311269/can-we-have-a-struct-element-of-type-variable-length-array 2021年4月13日に閲覧して確認.</ref>
実行結果はwindows版と同じであるので省略。
 
==== 二重配列の構造体変数 ====
構造体配列は、二重配列であっても良い。つまり、構造体変数には、二重配列を宣言する事も可能である。
 
-->
 
==== 構造体へのポインタ ====
構造体へのポインタを宣言する時は、
変数名の前に「*」をつける。
「()」が必要である。
 
また「<{{code>|-></code>}}」(アロー演算子)を用いて、
次のように記述でき、
こちらが一般的に使われる。
char c;
char str[32];
} kouzoutai, *pkouzoutai;
 
pkouzoutai=&kouzoutai;
また、文字列の宣言は、(上記の <code>char str[32];</code> のように)配列として宣言するのが一般的である。
 
===== 構造体のポインタを引数にした関数 =====
(アロー演算子を使わずに、そのまま)大きな構造体を引数に指定すると、
実引数から仮引数へその構造体全体をコピーするため、
double d;
char c;
char str[3232000];
} 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', "Hellol.Hello World!" };
function(&kouzoutai) ;
}
733

回編集