コンピュータを使って問題を解くときは、必ずしも単純な数値と文字列の 並んだレコードになっているとは限らない。現実社会にあるものをコンピュータ で処理できるようにするには、処理対象のものをうまく変数で表現しなければな らない。ここでは、トランプを例にデータの格納方法を考えよう。
カードの持つ性質をまとめてみよう。
トランプの種類のことをスートという。 カードを表すにはスートと数の2つのフィールドがあれば良さそうである。
スートと数をを表すために、次のような構造体を考えよう。 スートは「ハート」、「スペード」、「ダイヤ」、「クラブ」と最大 カタカナ4文字で表されるので10バイトで表すものとする。
struct card {
char suit[10];
int number;
};
カードは53枚なので、この構造体の配列を53個確保し、
suit
に "ハート", "スペード", "ダイヤ", "クラブ" を
13個、それぞれに対して number
を1〜13で代入して行こう。
カードの初期化を行なう関数を initialize という名前で作る。
#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
にある
以下の不満点を改良しよう。
これらを以下のようにして解消する。
ハート/スペード/ダイヤ/クラブ、はどれも同じなので、
固定領域に宣言して、構造体の 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つ入る配列が確保される。
どこかにある"ハート"という文字列のアドレス |
どこかにある"スペード"という文字列のアドレス |
どこかにある"ダイヤ"という文字列のアドレス |
どこかにある"クラブ"という文字列のアドレス |
以下のような変換テーブルを持った配列を作ると良い。
数 | 文字列 |
---|---|
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;
};
typedef
を使うと、複雑な型を一単語で代理させること
ができる。struct card
を、一単語 Card
で
言い換えるようにさせることができる。typedef
は、
typedef 元々の変数の型 言い換えの一単語
という型式で利用できる。struct card
を Card
で言い換えるためには、以下のように記述する。
typedef struct card {
char *suit;
int number;
char *name;
} Card;
以上を全て反映させると以下のプログラムになる。
#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
というキーワードをつけるだけで良い。