データの格納

コンピュータを使って問題を解くときは、必ずしも単純な数値と文字列の 並んだレコードになっているとは限らない。現実社会にあるものをコンピュータ で処理できるようにするには、処理対象のものをうまく変数で表現しなければな らない。ここでは、トランプを例にデータの格納方法を考えよう。

カードの性質

カードの持つ性質をまとめてみよう。

トランプの種類のことをスートという。 カードを表すにはスートと数の2つのフィールドがあれば良さそうである。

カードの表現例

スートと数をを表すために、次のような構造体を考えよう。 スートは「ハート」、「スペード」、「ダイヤ」、「クラブ」と最大 カタカナ4文字で表されるので10バイトで表すものとする。

struct card {
  char suit[10];
  int number;
};

カードは53枚なので、この構造体の配列を53個確保し、 suitに "ハート", "スペード", "ダイヤ", "クラブ" を 13個、それぞれに対して number を1〜13で代入して行こう。

カードの初期化を行なう関数を initialize という名前で作る。

initcard.c

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

#define CARDS	53
#define SUITLEN	10

struct card {
  char suit[SUITLEN];
  int  number;
};

void initialize(struct card c[])
{
  char suits[][SUITLEN] = {
	"ハート", "スペード", "ダイヤ", "クラブ"
  };
  int s, n, i;
  for (i=0; i<CARDS-1; i++) {
    s = i/13;                   /* 13で割ると切捨て */
    n = i%13 + 1;               /* 13で割った余り+1 */
    strlcpy(c[i].suit, suits[s], SUITLEN);
    c[i].number = n;
  }
  strlcpy(c[i].suit, "JOKER", SUITLEN);
  c[i].number = 0;  /* ジョーカーのnumberは何でもいいだろう */
}

int main(int argc, char *argv[])
{
  int i;
  struct card c[CARDS];
  initialize(c);
  for (i=0; i<CARDS; i++) {
    printf("%sの%d\n", c[i].suit, c[i].number);
  }
}

保存して実行してみよう。

gcc -o initcard initcard.c
./initcard
ハートの1
ハートの2
ハートの3
ハートの4
ハートの5
ハートの6
   :
クラブの9
クラブの10
クラブの11
クラブの12
クラブの13
JOKERの0

カード表現の改良

initcard.cにある 以下の不満点を改良しよう。

  1. 構造体の52個の変数全てが「ハート/スペード/ダイヤ/クラブ」を 保持しているのは無駄
  2. 1はA、11から先は J, Q, K と表示させたい
  3. struct card といちいち書くのが面倒

これらを以下のようにして解消する。

  1. 構造体のスート用メンバは、文字列へのポインタのみで済ませる

    ハート/スペード/ダイヤ/クラブ、はどれも同じなので、 固定領域に宣言して、構造体の suit メンバは 固定領域へのポインタにする。具体的には以下のようにする。

    struct card {
      char *suit;
      int  number;
    };
    
    void initialize(Card c[])
    {
      
      static char *suits[] = {"ハート", "スペード", "ダイヤ", "クラブ"};
      int s, n, i;
      for (i=0; i<CARDS-1; i++) {
        s = i/13;                   /* 13で割ると切捨て */
        n = i%13 + 1;               /* 13で割った余り+1 */
        c[i].suit=suits[s];
        c[i].number = n;
      }
      c[i].suit = numbers[0];
      c[i].number = 0;              /* ジョーカーのnumberは何でもいいだろう */
    }
    
    

    char型の2次元配列

    char suits[][SUITLEN] = {
      "ハート", "スペード", "ダイヤ", "クラブ"
    };
    

    と、文字列へのポインタの配列

    char *suits[] = {
      "ハート", "スペード", "ダイヤ", "クラブ"
    };
    

    は以下のように違う。前者は4×10の方形の記憶領域が 確保される。

    \0




    \0


    \0




    \0




    後者は、どこか別の場所に確保された4つの文字列へのポインタが 4つ入る配列が確保される。

    どこかにある"ハート"という文字列のアドレス
    どこかにある"スペード"という文字列のアドレス
    どこかにある"ダイヤ"という文字列のアドレス
    どこかにある"クラブ"という文字列のアドレス
  2. 1はA、11から先は J, Q, K と表示させたい

    以下のような変換テーブルを持った配列を作ると良い。

    文字列
    1"A"
    2"2"
    3"3"
    4"4"
    5"5"
    6"6"
    7"7"
    8"8"
    9"9"
    10"10"
    11"J"
    12"Q"
    13"K"

    これは、以下のような配列を作れば良い。

    char *numbers[] = {"JOKER", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10",
                       "J", "Q", "K"};
    

    0番目の要素 "JOKER" は、ジョーカーのために作成した。 カードの番号(整数)に対応した実際の読み方を構造体に保存しておこう。 構造体に1つメンバーを追加する。

    struct card {
      char *suit;
      int  number;
      char *name;
    };
    
  3. struct card といちいち書くのが面倒

    typedef を使うと、複雑な型を一単語で代理させること ができる。struct card を、一単語 Card で 言い換えるようにさせることができる。typedef は、

    typedef 元々の変数の型 言い換えの一単語

    という型式で利用できる。struct cardCard で言い換えるためには、以下のように記述する。

    typedef struct card {
      char *suit;
      int  number;
      char *name;
    } Card;
    

以上を全て反映させると以下のプログラムになる。

initcard2.c

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

#define CARDS	53
#define SUITLEN	10

typedef struct card {
  char *suit;
  int  number;
  char *name;
} Card;

void initialize(Card c[])
{
  static char *suits[] = {
    "ハート", "スペード", "ダイヤ", "クラブ"
  };
  static char *numbers[] = {
    "JOKER", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10",
    "J", "Q", "K"
  };

  int s, n, i;
  for (i=0; i<CARDS-1; i++) {
    s = i/13;                   /* 13で割ると切捨て */
    n = i%13 + 1;               /* 13で割った余り+1 */
    c[i].suit=suits[s];
    c[i].number = n;
    c[i].name = numbers[n];
  }
  c[i].suit = suits[4];
  c[i].number = 0;              /* ジョーカーのnumberは何でも良い */
  c[i].name = numbers[0];       /* ジョーカーの読み名 */
}

int main(int argc, char *argv[])
{
  int i;
  Card deck[CARDS];
  initialize(deck);
  for (i=0; i<CARDS; i++) {
    printf("%sの%s\n", deck[i].suit, deck[i].name);
  }
}

変更点の要点

変数の記憶クラス

関数内で宣言する変数は、自動変数(auto変数)といい、 コンピュータのメモリの、スタックエリアに置かれる。スタックエリアとは 一時的な値を置いておくための領域で、余り大きなサイズが確保されていない。 また、ある関数内で宣言された変数(自動変数)は、関数を抜けると 消滅する。

大きなデータをいれるための変数、あるいは関数から抜けても消滅して 欲しくない変数は固定(static)変数として宣言する。 変数宣言の前に static というキーワードをつけるだけで良い。


目次