成績データ などを
読み込んで処理を行なうときには、プログラムで全てのデータを
記憶しておいた方がその後の処理がしやすい。
山田太郎 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人分のメモリを確保した上で、データを読んでは記憶して行こう。
#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増やす */ } }
のように修正すべきである。