構造体



構造体の定義

 プログラムを少し勉強した人の中には、関数がどうの、ポインターがどうの、などと大層に言う人がいます。そのような人の話を聞き、プログラミングは難しいという先入観をもってしまっている人がいます。プログラミングやコンピュータに関する概念の多くがそうですが、これらは、すべて、自然な(当たり前の)概念が多いのです。先入観をすて、単純に理解し、いろんな応用を思いつくというのが大切だと思います。

 では、構造体について説明しましょう。
まず、長方形の面積を求めるプログラムを例に考えてみましょう.プログラミングの初心者は、このプログラムを次のように書くでしょう。

  double tate, yoko, menseki;
  tate = 4.0;
  yoko = 6.5
  menseki = tate * yoko;

では、これに、三角形や、円の面積を計算する機能も追加してみましょう。次のプログラムを見るときは、変数名にはアンダースコア('_')(んっ!なんだか人の顔みたいですね!)や数字が使えると書いてあったのを思い出して下さい。

  double tate, yoko, menseki_4kaku;
  double teihen, takasa, menseki_3kaku;
  double hankei, menseki_en;
  tate = 4.0;
  yoko = 6.5
  menseki_4kaku = tate * yoko;
  teihen = 4.0;
  takasa = 6.5
  menseki_3kaku = (teihen * takasa) / 2.0;
  hankei = 4.0;
  menseki_en = hankei*hankei*3.1415;

変数の管理が面倒ですね。それに、プログラムもごちゃごちゃしてますね。さらに、図形が増えると、ますます変数の管理が大変です。もし、ここで、図形ごとに、変数を1つにパックにして扱えたらどうなるのでしょうか。そうです!構造体というのは、ワンセットの変数を1個にパックしてしまい、あたかも1つの変数であるかのように扱えるようにしたものなのです。
 では、具体的に構造体の作り方を説明しましょう。
 その前に、四角形、三角形、円のことを、それぞれ、英語でRectangle, Triangle, Circleと呼ぶことは知っていますよね。個人的には、変数名にローマ字を使いたくないのでこの程度のことは、英語で表現させて下さい! あ、それから、構造体のことを英語ではstructureということも覚えておいて下さい!
 構造体というのは、複数の変数を1つのパックにしたものです。構造体を使うには、まず、どの変数とどの変数を1まとめにしてしまうのかを明確にしなければなりません。これを、構造体の定義といいます。四角形、三角形、円について、定義の例を示します。

 四角形の構造体定義
   struct RECT{
     double tate;
     double yoko;
     double menseki;
   };

 三角形の構造体
   struct TRI{
     double teihen;
     double takasa;
     double menseki;
   };

 円の構造体の定義
  struct CIRC{
    double hankei;
    double menseki;
  };

これら3種の図形の定義例を見れば、もう構造体の定義のしかたは、理解できたと思います。structはこれが構造体の定義であることを意味する決まったことばです。RECT、TRI、CIRCなどは、タグとよばれ、構造体の型を識別する名前で、勝手に決めて構いません。(パッケージにしてしまうのですからタグを付けるのは常識ですよね。ここの意味、わかりますか?)定義中の{ }内には、パックする変数名を記述します。型宣言部を含むことに注意して下さい。構造体の中に宣言された各変数のことをメンバー変数とよびます.



構造体の使い方

では,実際のプログラムの中で構造体をどのように,取り扱うのか,プログラム例をとおして説明しましょう.
 

 #include <stdio.h>

 struct RECT{
   double tate;
   double yoko;
   double menseki;          
 };

 void main(void){
   struct RECT rct;

   rct.tate = 4.0;
   rct.yoko = 6.5;
   rct.menseki = rct.tate * rct.yoko;

   printf("面積は%fです",rct.menseki);       
 }

構造体の定義は,ヘッダー部に記述します.コンパイラーに,以下のプログラムでは,このような構造体を試用しますと教えるわけです.RECT型の構造体を定義したことになります.
intやfloat型の変数と同様,構造体型の変数も変数宣言しなければ,その変数は作られません.main関数の第1行目では,RECT型構造体の変数の宣言を行っています.その宣言で,rctという構造体型変数が作成(?)されます.
 その次の部分,
   rct.tate = 4.0;
   rct.yoko = 6.5;
   rct.menseki = rct.tate * rct.yoko;
では,構造体変数rctの中に納まっているメンバー変数にアクセスする方法を示しています.
   rct.tate
のように記述することで,メンバー変数を指定することができます.rctとtateの間のピリオド(’.’)は,メンバー指定子と呼ばれます.



構造体の配列

 他の変数と同様,構造体型変数も配列変数を作成することができます.プログラム例を次に示します.ここでは,10個の長方形を同時に扱います.メンバー変数の指定のしかたに注意してください.
 

 #include <stdio.h>

 struct RECT{
   double tate;
   double yoko;
   double menseki;            
 };

 void main(void){

   struct RECT rct[10];
   int i;

   rct[0].tate = 4.0;
   rct[0].yoko = 6.5;

   rct[1].tate = 3.0;
   rct[1].yoko = 4.5;

   ・・・
   ・・・

   rct[9].tate = 2.0;
   rct[9].yoko = 8.5;

   for(i=0;i<10;i++){
     rct[i].menseki = rct[i].tate * rct[i].yoko;
     printf("%d番目の長方形の面積は%fです",i, rct[i].menseki);  
   }
}



構造体について気が付いてほしいこと

 先に,図形に関するパラメータをまとめ,RECT型,TRI型,CIRC型などの構造体を定義しました。
もう気が付いた人もいるかもしれませんが,それぞれの構造体は,まるで図形そのものと言う感じがしませんか?
 さらに,個人についてのデータを構造体で表現すると,つぎのような構造体が考えられます。

   個人を表現する構造体
   struct PERSON{
     char name[21];
     int  age:
     float sincho;
     float taijyu;
   }

 これは,1人の人間に対応しますね。
このように,構造体は,定義のしかたをくふうすれば,一個の変数で実際の物体,あるいは物(オブジェクト)を表現することができます。



構造体と関数

 ここは、すでに、関数やポインターについて知っている人を対象にしています。
次のプログラムは、RECT型の構造体変数をmain関数内で宣言し、そのメンバー変数へのデータの代入を関数を使って実行、その後、各メンバー変数の内容を表示専用の関数を使って表示するものです。
 通常の変数の場合と同様、関数を使って構造体型変数、rct へデータを代入するには、引数として、変数 rct のアドレスを渡さなければなりません。main関数の第1行めの文、
   struct RECT rct, *pr;
は、RECT型の変数rctとポインター変数 pr を宣言しています。通常の変数の宣言と対応させて考えれば特別変わったところはありません。
 注意して欲しいのは、
   set_rect_parameter(pr);
のようにポインター変数で構造体rectのアドレスを渡した後の、関数内でのポインター変数の扱い方です。構造体型のポインターを使ってメンバー変数にアクセスするには、アロー(矢)演算子と呼ばれる記号 ー> を使います。
 

 #include <stdio.h>

 struct RECT{
   double tate;
   double yoko;
   double menseki;          
 };

 void set_rect_parameter(struct RECT *pr);
 void print_rect_parameter(struct RECT rct);     

 void main(void)
 {
   struct RECT rct, *pr;     /*構造体型変数とポインターの宣言*/ 
   pr = &rct;
   set_rect_parameter(pr);    /*引数は、&rctでもOKですよ*/
   print_rect_parameter( rct );
 }

 void set_rect_parameter(struct RECT *p)
 {
   p -> tate = 4.0;
   p -> yoko = 3.5;
   p -> menseki  = pr->tate * pr->yoko;
 }

 void print_rect_parameter(struct RECT r)
 {
   printf("たて = %f  よこ=%f\n", r.tate, r.yoko);   
   printf("めんせき = %f\n", r.menseki);   
 }



構造体に関するその他の文法

 値を設定された構造体 rcta と値未設定の同型の構造体変数 rctb あるとき、
   rctb = rcta;
  と書いて、rctb のメンバー変数に rcta のメンバー変数の値を代入することができます。
  
 構造体のメンバーに構造体を含めることもできます。

 構造体の定義部で続けて宣言することも出来ます。
 struct RECT{
   double tate;
   double yoko;
   double menseki;          
 }rct;

    但し、この場合はグローバル変数として扱われます。

 多次元配列を構造体で表記しなおすと分かりやすくなるときがあります。
 
   char name_of_friend[101][5];
 
      これは、名前の長さが最大100バイトのデータが5つまで格納できる2次元配列です。
    しかし、[X] [Y]とやるとどうもややこしく見えます。こんなときは構造体を使いましょう!

    struct NAMEOFFRIEND{
        char name[101];
    };

    struct NAMEOFFRIEND name_of_friend[5];
   
      こちらの方がスッキリして見やすいと思いませんか?



構造体の大きな利点 (ポインタとかがある程度分かる人は読んでください!!)

メモリの構造とポインタを理解し、関数も上手く使いこなせるようになると分かる時がやってくると思いますが、構造体を用いるととても便利な場面があります。
まず、構造体を使うとその構造体には連続したメモリ空間が割り当てられます。例えば、次のような例を考えてみます。

struct ADDRESS_DATA{
    char name[20];
    char address[100];
    char tel[20];
    long zip_code;
}address_of_me;

このとき、address_of_me構造体の先頭アドレス(&address_of_me)が6080番地だとします。
便宜上、メンバ変数nameの先頭アドレス &name[0] も結果的に同じ6080番地であったと考えます。(実際に同じになります)
&name[19] は計算すると 6080+20=6100番地となります。
次のメンバ変数addressの先頭アドレス(&address[0])は「連続したメモリ空間が割り当てる」というルールにのっとり、6101番地となります。
この構造体の大きさはというと、次の方法で求められます。

sizeof(ADDRESS_DATA);  もしくは sizeof(address_of_me);

この結果はバイト単位です。手計算では 20+100+20+4(long型は4バイトですよ!)=144バイトとなります。これと同じ結果がsizeof()で求められます。
(C言語の規定では、メンバ変数のサイズの合計がその構造体のサイズになるとは限らないとなっていますが。どちらにしても正しいサイズはsizeof( )としてやることで知ることが出来ます) よって、この構造体の先頭アドレスは6080番地、最後尾は 6080+144= 6224番地となり、この区間がこの構造体のためのメモリ空間となります。

これがどういう風に役に立つのでしょうか?

世の中には関数がたくさん存在します。それらの中でも重要な位置を占める入出力関数群の利用に特に役立つのです。
「構造体で"パック”したデータをバラバラにせず一度に処理する」という場面を考えてみてください。

例えば、ネットワーク上(インターネットとかね)で文字列を送信する代表的な関数にsend( )関数があります。
この関数は標準では文字列の送信を行うものです。プロトタイプは

send( 宛先 , 送る文字列の先頭アドレス , 送信するサイズ(単位:バイト) , 送信モードの設定値 );

となっています。しかし、ネットワークでデータを送るのに、例えば数値のint型「4」を文字列のchar型「’4’」に置き換えるのは効率的ではありません。
しかも、メンバ変数1つの為にsend( )関数を1つ使っていたのではもったいない気がします(^_^;
ということで、送信したいデータをまず構造体としてパックします。(ここでは先のADDRESS_DATA構造体を送信すると考えますね)
次に、

send( 宛先 , (char*)&address_of_me , sizeofaddress_of_me) , 送信モードの設定値 );

とします。おっ、見るからにややこしそうな型変換を行ってますね。ここで(char*)を使うのには訳があります。このsend()関数は文字列の先頭アドレスを受け取り、1バイト(char型ですから)ずつ送信する処理を行います。よって、構造体をそのままsend( )関数に渡すとコンパイラに「型が違いますよ!!」と怒られてしまいます。
「address_of_me構造体の先頭アドレス&address_of_meから始まるsizeofaddress_of_me) バイトは文字列ですよ!とコンパイラにウソをつくのです(笑)実際はもちろん構造体ですよ!でも、コンパイラはまんまと文字列型と判断します。(この場合、ウソをついてももちろん動作に悪影響はありません)
そして、文字列の如くデータを相手に送ることが出来ます。(この場合、データを受け取る相手もADDRESS_DATA型構造体を使います)

同様に、ファイル操作などでも「パック」したデータを「パックしたまま」扱うことが出来ます。これがメモリ上で連続するということから得られる構造体の利点の1つなのです。