2012/05/03

C言語 構造体 struct

構造体とは、異なった型の変数をひとつの名前でまとめたようなもの。Javaから入った身としては、どう使うか疑問に感じるところ。構造体は関数を内部に入れられないので、クラス的に使うことは難しい。でもクラスを使うよりも負荷が少ないようだ。新しいGo言語でも構造体は健在なので、パフォーマンスを重視すると捨てがたいのだろう。まずは基本的な扱い方からスタート。

CDのリストを構造体で表現してみる

#include <stdio.h>
#include <string.h>
/* 構造体の宣言 メモリ割当ては行われない*/
struct cd{
  int no;
  char title[20];
  int price;
};
int main(void){
/* クラシック リスト作成 メモリに展開 */
  struct cd classic[] ={
    {1,"Bach",2600},
    {2,"Mozart",2500},
    {0,"",0}
  };
/* ジャズ リスト作成 メモリに展開 */
  struct cd jazz ={
    1,"Coltrane",1800
  };
/* クラシック リスト表示 */
  int i;
  for(i=0;i<2;i++){
    printf("%-6s No.%d %-9s Price:%d\n",
       "Classc",
       classic[i].no,
       classic[i].title,
       classic[i].price);
  }
/* ジャズ リスト表示 */
  printf("%-6s No.%d %-9s Price:%d\n",
       "Jazz",
       jazz.no,
       jazz.title,
       jazz.price);  
  return 0;
}
まずジャンルごとにClassicとJazzに分けて考える。使う構造体は同じで、ナンバーとタイトルと価格から構成される。main関数でクラシックとジャズ、それぞれ定義して中身を入れる。そして中身を表示してみる。もっともシンプルなパターンがこんな感じ。慣れるまで時間かかりそうな気配。
Classc No.1 Bach      Price:2600
Classc No.2 Mozart    Price:2500
Jazz   No.1 Coltrane  Price:1800


構造体の入れ子

#include <stdio.h>
#include <string.h>
/* 構造体の宣言 */
struct cd{
  int no;
  char title[20];
  int price;
};
/* 構造体の拡張 */
struct cdEx{
  struct cd excd;
  char exshop[20];
};
/* 表示用関数 値渡し*/
void cdview(struct cdEx cdlistview[]){
  int i;
  for(i=0; i<sizeof(cdlistview)-1; i++){
    printf("No:%d Title:%s Price:%d Shop:%s\n",
       cdlistview[i].excd.no,
       cdlistview[i].excd.title,
       cdlistview[i].excd.price,
       cdlistview[i].exshop);
  }
}
/* メイン関数 */
int main(void){
  struct cdEx cdlist[] ={
    {1,"Bach",2600,"amazon"},
    {2,"Moazrt",2500,"ishimaru"},
    {3,"Coltrane",1800,"amazon"},
    {0,"",0,""}
  };
  cdview(cdlist);
  return 0;
}
入れ子にすることで構造体を拡張してみる。こうすることで、元々の構造体はそのままなので、プログラムに大きな変更を加えなくても拡張が出来るようになる。表示する関数を別にしてみた。ただ表示用関数に構造体をコピーしているので、余計なメモリを食い、処理も遅くなっている。次にポインタを使って無駄をなくしてみる。
No:1 title:Bach Price:2600 Shop:amazon
No:2 title:Moazrt Price:2500 Shop:ishimaru
No:3 title:Coltrane Price:1800 Shop:amazon


構造体のポインタ

#include <stdio.h>
#include <string.h>
/* 構造体の宣言 */
struct cd{
  int no;
  char title[20];
  int price;
};
/* 構造体の拡張 */
struct cdEx{
  struct cd excd;
  char exshop[20];
};
/* 表示用関数 ポインタ 参照渡し*/
void cdview(struct cdEx *cdp){
  int i;
  for(i=0; i<sizeof(cdp)-1; i++){
    printf("No:%d title:%s Shop:%d\n",
      /* 構造体へのアクセスには「->」アロー演算子を使う */
      (cdp+i)->excd.no,
      (cdp+i)->excd.title,
      (cdp+i)->excd.price,
      (cdp+i)->exshop);
  }
}
/* メイン関数 */
int main(void){
  struct cdEx cdlist[] ={
    {1,"Bach",2600,"amazon"},
    {2,"Moazrt",2500,"ishimaru"},
    {3,"Coltrane",1800,"amazon"},
    {0,"",0,""}
  };
  cdview(cdlist);
  return 0;
}
結果は同じだが、表示関数に構造体を丸々コピーすることはせずに、ポインタによって、参照渡ししている。こうすることで、余計なメモリも食わずに、処理のロスも少なくなる。
No:1 title:Bach Price:2600 Shop:amazon
No:2 title:Moazrt Price:2500 Shop:ishimaru
No:3 title:Coltrane Price:1800 Shop:amazon


別の宣言方法

#include <stdio.h>
#include <string.h>
/* 構造体宣言と同時にメモリに割当て */
struct {
  char id[5];
  short channels;
  int samplerate;
  } FormatChunk1,FormatChunk2;

int main(void){
  strcpy(FormatChunk1.id,"fmt ");
  FormatChunk1.channels = 1;
  FormatChunk1.samplerate = 44100;

  strcpy(FormatChunk2.id,"fmt ");
  FormatChunk2.channels = 2;
  FormatChunk2.samplerate = 48000;

  printf("FormatChunk1\n[%s]\n"
         "channels:%d\n"
         "samplerate: %d Hz\n",
          FormatChunk1.id,
          FormatChunk1.channels,
          FormatChunk1.samplerate);

  printf("FormatChunk2\n[%s]\n"
         "channels:%d\n"
         "samplerate: %d Hz\n",
          FormatChunk2.id,
          FormatChunk2.channels,
          FormatChunk2.samplerate);

  return 0;
}
構造体の宣言と同時にメモリに割り当てることもできる。他で構造体宣言しないなら、こういう書き方もある。
FormatChunk1
[fmt ]
channels:1
samplerate: 44100 Hz
FormatChunk2
[fmt ]
channels:2
samplerate: 48000 Hz


構造体の引数

#include <stdio.h>
#include <string.h>
/* 構造体 初期設定を入れる場合等に使えそう */
struct setup{
  char name[10];
  float param0;
  float param1;
};
/* 初期設定ファイルなどを入れておく */
struct setup reverb_room(void){
  struct setup room;
  strcpy(room.name,"Room");
  room.param0 = 0.3255;
  room.param1 = 0.12041;
  return room;
}
/* 表示用 ポインタを使って参照*/
void view_param(struct setup *p){
    printf("name: %s\n"
         "param0: %f\n"
         "param1: %f\n",
          p -> name,
          p -> param0,
          p -> param1
          );
}
/* メイン関数 */
int main(void){
  struct setup rev;
  rev = reverb_room();
  view_param(&rev);
  return 0;
}
構造体を引数として扱ってみる。複数の設定ファイルを使い分けるときなどに使えそうだ。
name: Room
param0: 0.325500
param1: 0.120410


typedefを使ってみる

上記のプログラムと同じ内容でtypedefを使ったのが下記ソースとなる。
#include <stdio.h>
#include <string.h>
/* 構造体 初期設定を入れる場合等に使えそう */
typedef struct {
  char name[10];
  float param0;
  float param1;
}setup_t;
/* 初期設定ファイルなどを入れておく */
setup_t reverb_room(void){
  setup_t room;
  strcpy(room.name,"Room");
  room.param0 = 0.3255;
  room.param1 = 0.12041;
  return room;
}
/* 表示用 */
void view_param(setup_t *p){
    printf("name: %s\n"
         "param0: %f\n"
         "param1: %f\n",
          p -> name,
          p -> param0,
          p -> param1
          );
}
/* メイン関数 */
int main(void){
  setup_t rev;
  rev = reverb_room();
  view_param(&rev);
  return 0;
}

typedefは「typedef int integer」とすると、integerがintと同じように使えるというもの。初心者から見るとかえって混乱するような機能なのだけど、構造体にはよく使われているので慣れたほうがよさそうだ。上記のプログラムの場合、structは構造体宣言で使っただけで、他はsetup_tとしか書いていない。記述が減って便利ということらしい。初心者からすると、常にstructと書いてあった方が構造体だと判断できてよいのだが・・・ 自分のプログラムでは使わないとしても、他の人のプログラムを理解するためにも覚えておくことにする。
name: Room
param0: 0.325500
param1: 0.120410


構造体に関数ポインタ

これを駆使すればオブジェクト指向的なプログラミングが可能。
#include <stdio.h>
/* プロトタイプ */
void add(int addn);
void sub(int subn);
void clear(void);
int get(void);

/* 構造体 */
struct box{
  void (*addp)(int addn); /* 関数ポインタ 抽象メソッド */
  void (*subp)(int subn); 
  void (*clearp)(void); 
  int (*getp)(void); 
  int n;
}box1 = {add,sub,clear,get,10}; /* インスタンス化&初期化 */

/* 構造体のメンバ関数的扱い 抽象メソッドの実装 */
void add(int addn){
  box1.n += addn;
}
void sub(int subn){
  box1.n -= subn;
}
void clear(void){
  box1.n=0;
}
int get(void){
  return box1.n;
}
/* メイン関数 */
int main(void){
  printf("初期 box1.getp: %d\n",box1.getp());
  box1.addp(440);
  printf("+440 box1.addp getp: %d\n",box1.getp());
  box1.subp(200);
  printf("-200 box1.subp getp : %d\n",box1.getp());
  box1.clearp();
  printf("クリア box1.clearp getp: %d\n",box1.getp());
  return 0;
}
混沌としないようにカプセル化はしなかった。変数ひとつと簡単な関数(抽象メソッド)を4つだけ使った入出力をひとつの構造体で実現したプログラム。これを発展させれば、オブジェクト指向もどきはできるのだが、やたらソースが見にくくなってしまう。この手のことはC++でやったほうがよさそうだ。
初期 box1.getp: 10
+440 box1.addp getp: 450
-200 box1.subp getp : 250
クリア box1.clearp getp: 0


C言語 ANSI C89 Meadow & MinGW GCC 目次