C言語/構造体

構造体(こうぞうたい; structure)は、構造体は、記憶域が順番に割り当てられたメンバーの列からなる型です 。 複合的なデータ構造という意味では配列と似ていますが、配列の要素はすべて同じ型で添字によってアクセスするのに対し、構造体のメンバーは任意の型を取ることが出来、メンバー名によるアクセスするところが異なります。 また、構造体の配列を作ることも出来ます。
たとえば、商品名と価格の2つ組を扱いたいときには、char * 商品名 と int 価格 からなるデーター構造が考えられます。
上述の商品名と価格の2つ組の例をC言語のコードでは、
もように表すことが出来ます。
また、上記の例の ProductEntry のように構造体に付ける名前は、構造体タグ( structure tags )と呼ばれます。
構造体は1個以上のメンバー( member )を持ちます。上の struct ProductEntry の例では name と price がメンバーです。
構造体を定義しても、構造体を型として持つの変数 (以下、構造体変数と呼ぶ)は作られません。 構造体を利用するには、構造体の構造体変数( structure variable )を宣言します。
構造体変数を宣言するときは、上述の struct ProductEntry milk; のように宣言します。
また、構造体変数の要素に代入したい場合など、構造体変数を参照したい場合には、「.」(ドット演算子)を用いて、milk.name のように記述します。
になります。
構造体変数は、宣言と同時に、値のリストで初期化することができます。
値のリストは「,」(コンマ)で区切ったリストです。
複合リテラルを使うと、初期化のコンテキストでなくても(キャストに似た構文で)リテラルによる一括代入が出来ます。 ただ、複合リテラルはスコープの中で一度しか生成されないので、ループの内側で使うと期待とは違った値を得る結果になります。
構造体のメンバーにアクセスするには、 「.(ドット)演算子」を用いて、次のように記述します。
配列とは異なり、構造体は一度に代入することができます。構造体を代入する時は、構造体の変数名のみを用います。 構造体の代入のことを「コピー」という場合もあります。
これにより、右の構造体の全てのメンバーが左の構造体の全てのメンバーに代入されますが、以下の点に注意する必要があります。
一方、メンバーに構造体が無い場合は、コピー元とコピー先の構造体はそれぞれ別々に保存されます。
要するに、構造体のコピーといえども、ポインタの指し示す先を書き換える権限はない、というだけのことです。よって、メンバにポインタを含まないなら、個別に書き換えることができます。
なお、大きなサイズの構造体をあつかう場合、構造体全体をコピーするため時間がかかってしまう。 その場合の対策として、構造体をコピーせずに(引数として)参照できるようにすれば良いので、そのためにはポインタを活用する必要がある。 詳しくは、構造体のポインタに関する節で説明する。
大きなサイズの構造体でないなら、特に気にせずにコピーして構わない。実際、上述の構造体コピーのコードは、正常にコンパイルできて動作する。
typedefを構造体に用いて、コードを短縮することができます。
上の例は typedef を用いて、下の例のように記述することができます。
おそらく、いちばん有名な typedef された構造体は、 <stdio.h> で定義されている FILE でしょう。
構造体を関数の引数にしたい場合、下記のように行います。
上の例では商品が2個しかなかったから、都度構造体変数を宣言すればよかった。しかし、商品が100個になったり、数が不定になったりしたらどうだろうか?個々の変数に割り当てる方法では厳しくなってくる。 そこで、構造体の配列を宣言し使用することでその問題に対処する。
構造体の配列を宣言する時は、変数名の後に「[要素数]」をつけます。
構造体の配列のメンバーにアクセスする時は、変数名の後に[添字]をつけます。
[]演算子と.演算子は、共に同じ優先順位の演算子で、左から右へと評価されるため (左結合)、次の式と等価です。
上の例では構造体の配列に初期値を与え、そのメンバーの値を表示しています。 メンバー .str にNULLがセットされた要素が終端を示します。 この例では、配列の大きさを N_ARRAY() で得ることが出来ますが、関数呼び出しの引数に渡された時には呼ばれた関数では長さがわからないので終端の約束事を決める必要があります。 これは、C言語の配列プログラムを扱うプログラムでは広く使われる手法で、Hello World でも。
の "Hello World!\n" も '\0' で終端された文字型の配列を引数にしています。
構造体のメンバーに、構造体を指定することができます。 本項ではこれを「構造体のネスト」と呼ぶことにします。
ネストされた構造体にアクセスする時は、ネストの最も外側から内側に向かって参照しなければなりません。
上の例では、my_structのメンバーであるsが「構造体のネスト」です。
構造体変数を宣言する際、宣言する構造体の型は、一番外側に相当する構造体になります。なぜなら一番外側の構造体は、内側の構造体についての情報も含んでいるので、よって外側の構造体の変数さえ宣言すれば連鎖的に内側の構造体についても宣言できるからです。
また、ネストの場合のメンバ変数へのアクセス方法の書式は
です。
商品リストの話に戻ります。 一つの店舗だけでなく複数の店舗の(可変長の)商品リストを扱う場合はどうすればよいでしょう? 1つのやり方として十分大きな配列で確保する方法がありますが、自由領域に長大な(疎な)オブジェクトを置くことになりメモリー(より厳密にいうとスタック領域)を使い果たしてしまう可能性があります。 そこで、複合リテラルで構造体配列を用意することにします。
複合リテラルで構造体の配列を作り、特定のメンバーを(文字列の '\0'のように)終端の意味に使い、長さの違う配列を扱っています。
構造体配列は、二重配列であっても良い。つまり、構造体変数には、二重配列を宣言する事も可能です。
よって構造体のネストを使わなくても、下記コードのように二重配列でも代用できます。
構造体へのポインターを宣言する時は、変数名の前に「*」をつけます。
ポインターの指す構造体のメンバーにアクセスする時は、「->」(アロー演算子)を用いて次のように記述できます。
これは、次と同じ意味です。
またでは、下記コードで実験してみましょう。
上の例では、構造体へのポインターを介して構造体のメンバーに値を代入したり、その値を参照したりしています。
なお、strcpyは、文字列をコピーするための関数。strcpyを使うには、ヘッダー <string.h> のインクルードが必要です。
また、文字列の宣言は、(上記の char str[32]; のように)文字の配列として宣言します。
Cでは、配列同士は代入できず、それぞれの要素ごとに代入を行います。 加えて、Cの文字列は '\0' で終端するという約束事があり、"ABC" という文字列リテラルは、{ 'A', 'B', 'C', '\0'}というcharの配列です。
自分自身と同じ型へのポインターを構造体のメンバーにできます。 このような構造体を自己参照構造体( self-referential structure )と呼びます。
上の例では、3つの構造体a、b、cのメンバーをポインター変数pを用いてトラバースしながら表示しています。
C言語に、「Go/メソッドとインターフェース」の「都市間の大圏距離を求めるメソッドを追加した例」を移植してみました。
移植にあたって、変数名 long がキーワードと衝突するというマイナーなものもありますが、
この二点は厄介で、次のような実装上の工夫で解決しました。