複数データの格納

成績データ などを 読み込んで処理を行なうときには、プログラムで全てのデータを 記憶しておいた方がその後の処理がしやすい。

山田太郎	50
公益太郎	90
飯森花子	91
鶴岡一人	60
酒田三吉	52
三川一二三	12

これらのデータ全てを、配列変数に格納していく手順を考えよう。

二次元配列

C言語の文字列は char 型(8ビット)整数の配列である のは既に学習したとおりである。複数の文字列をまとめて記憶したい場合は、 さらに文字列を配列にすれば良い。

たとえば、最大50バイトの名前を格納するchar型配列を確保したとしよう。

char name[50];

これは一人の名前しか入れられない。これをさらに300人分にしたかったら、 name という変数を一かたまりと見て、さらにそれを 300個用意する配列にすれば良い。

char name[300][50];

こうすると、50個の箱を持つ配列が、300個できる。

name[0][*]
name[0][0] name[0][1] ……… name[0][48] name[0][49]
1文字目 2文字目 ……… 49文字目 50文字目
name[1][*]
name[1][0] name[1][1] ……… name[1][48] name[1][49]
1文字目 2文字目 ……… 49文字目 50文字目
name[2][*]
name[2][0] name[2][1] ……… name[2][48] name[2][49]
1文字目 2文字目 ……… 49文字目 50文字目
:
:
name[299][*]
name[299][0] name[299][1] ……… name[299][48] name[299][49]
1文字目 2文字目 ……… 49文字目 50文字目

つまり、char name[300][50]の宣言により、

name[0][50]			(50文字分)
name[1][50]			(50文字分)
name[2][50]			(50文字分)
name[3][50]			(50文字分)
   :
name[297][50]			(50文字分)
name[298][50]			(50文字分)
name[299][50]			(50文字分)

50文字分のchar型配列が300個用意される。

このように、配列をさらに複数あつかえるようにした 配列を二次元配列 という。さらに、二次元配列を 複数まとめることもでき、それを三次元配列という。 このように配列をさらにまとめた配列のことを 多次元配列 といい、三次元以上何次元でも積み重ねられる。

二次元配列のアドレス

配列とそのアドレスの関係を思い出そう。

char x[10];

と宣言した場合、char 型(8bit)の入れ物が10個確保される。 最初は中味は未定義。

x[0]x[1]x[2]x[3]x[4]x[5]x[6]x[7]x[8]x[9]
????????????????????

この配列の先頭のアドレスは、&x[0] となるが、 配列の先頭アドレスの場合、&と[0]を取ってしまった

x

だけで、先頭のアドレスを意味する。同様に、

char name[300][50];

と宣言し、50個分の配列が300個できた場合の、300個、それぞれの先頭の アドレスは、

name[0][50]	の先頭は &name[0][0] つまり name[0]
name[1][50]	の先頭は &name[1][0] つまり name[1]
name[2][50]	の先頭は &name[2][0] つまり name[2]
name[3][50]	の先頭は &name[3][0] つまり name[3]
   :
name[297][50]	の先頭は &name[297][0] つまり name[297]
name[298][50]	の先頭は &name[298][0] つまり name[298]
name[299][50]	の先頭は &name[299][0] つまり name[299]

のように表現できる。

データを記憶しながら読み込む

300人分のメモリを確保した上で、データを読んでは記憶して行こう。

store.c

#include <stdio.h>

int main()
{
  /* 1行は100バイト、氏名は50バイトあればいいだろう */
  char buffer[100], name[300][50];/* nameは 50バイト×300人分 */
  int point[300];               /* 得点も300人分 */
  int n=0;                      /* 今何人目? (0から) */
  int i;                        /* ループ用変数 (最初に定義すべし) */

  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    sscanf(buffer, "%49s %d", name[n], &point[n]);
    n++;                        /* nを1増やす */
  }
  puts("全部読み終わったよ!");
  /* では改めて最初から表示*/
  /* この時点で n は最後の配列要素より1大きくなっている */
  for (i=0; i<n; i++) {
    printf("%2d番目: %14s さんは %d 点\n", i+1, name[i], point[i]);
  }
  puts("以上");
}

for構文も復習のこと。

コンパイルして実行してみよう。

% cat score.txt | ./store

ただし、このプログラムも、sscanf を呼ぶ部分に着目すると、

  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    sscanf(buffer, "%49s %d", name[n], &point[n]);
    n++;                        /* nを1増やす */
  }

のように、sscanf が失敗したかどうかのチェックを怠ってい る。これでは、bad-score.txt のような以上データが来たときにプログラムの動作がおかしくなってしまうので、

  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    if (2 == sscanf(buffer, "%49s %d", name[n], &point[n])) {
      n++;                        /* nを1増やす */
    }
  }

のように修正すべきである。


目次