ある試験を受けた生徒の結果を処理する場合を考えよう。このような場合、 データを格納するには構造体を使って行くと良い。数学、英語、国語 の3科目を行なうのであれば、以下のような構造体定義にすると良いだろう。
struct student {
char name[30];
int mathpt;
int engpt;
int jpnpt;
};
あるテストの全員の得点を記録したデータファイルが 以下のようになっているものとする。
山田太郎 50 70 20
公益太郎 90 80 70
飯森花子 91 79 72
鶴岡一人 60 60 40
酒田三吉 52 70 80
三川一二三 12 34 99
これを、構造体変数に読み込んで行くプログラムは以下のようになる。 起動時にデータファイルをコマンドライン引数として与えられるように なっている。
#include <stdio.h>
#define NMAX 100
struct student {
char name[30];
int mathpt;
int engpt;
int jpnpt;
};
int main(int argc, char *argv[])
{
FILE *stream;
char buf[500];
struct student pt[NMAX+1];
int i, n;
void dispall(struct student[]);
if (NULL == argv[1]) {
stream = stdin;
} else {
stream = fopen(argv[1], "r");
if (NULL == stream) {
fprintf(stderr, "Cannot open data file [%s]\n", argv[1]);
exit(1);
}
}
i = 0;
while (NULL != fgets(buf, sizeof buf, stream)) {
if (i >= NMAX) {
fprintf(stderr, "Reached to maximum number(%d)\n", NMAX);
break;
}
n = sscanf(buf, "%29s %d %d %d",
pt[i].name, &pt[i].mathpt, &pt[i].engpt, &pt[i].jpnpt);
if (n == 4) {
i++;
}
}
if (stdin != stream)
fclose(stream);
pt[i].name[0] = 0; /* 終端の印 */
dispall(pt);
}
void dispall(struct student pt[])
{
int i, sum;
for (i=0; pt[i].name[0] != 0; i++) {
sum = pt[i].mathpt+pt[i].engpt+pt[i].jpnpt;
printf("%2d: %-20s, %d, %d, %d\n",
i+1, pt[i].name, pt[i].mathpt, pt[i].engpt, pt[i].jpnpt);
}
}
このプログラムでは、各生徒の氏名、数学得点、英語得点、国語得点
を含む構造体がいくつも集まった配列 pt[]
にデータを格納している。
この配列に格納されているのは
pt[0]
「山田太郎」のデータ pt[1]
「公益太郎」のデータ pt[2]
「飯森花子」のデータ pt[3]
「鶴岡一人」のデータ pt[4]
「酒田三吉」のデータ pt[5]
「三川一二三」のデータ
であり、これはデータの読み込み順になっている。これを並べ換えよう。
qsort()
関数
qsort()
関数を使うと任意の型の配列に格納された
データを昇順(小さい順)、または降順(大きい順)に並べ換えることができる。
qsort()
関数は、並べ換えたい配列を用意した後、
以下の書式で利用する。
#include <stdlib.h> qsort(配列の先頭アドレス, 配列の要素数, 配列1要素のバイトサイズ, 大小比較のための関数ポインタ);
大小比較のための関数 は、各要素へのポインタを引数として 受け取り、それぞれの大小比較の結果をintで返すように作る必要がある。 2つの引数を比べて、最初のものが大きかったら「正の値」、 等しかったらゼロ、最初のものが小さかったら「負の値」を返すようにする。 もしintどうしの比較なら
int hikaku(const void *x, const void *y)
{
int *a = (int*)x;
int *b = (int*)y;
return (*a - *b);
}
とする。比較関数は必ず2つの引数を受け取り、どちらも
const void *
型で宣言するように書かねばならない。
const void *
型とは「どんな型の値が渡されるか分からない」
ときに宣言する型のことで、そのままでは実際の値を取り出せない。
int *a = (int*)x;
int *b = (int*)y;
の2行で、「x
をint型のポインタだとみなしてa
に
代入」し、「y
をint型のポインタだとみなしてb
に
代入」している。こうすることで *a, *b
が比べるべき2つの
intの値を表すようになる。
もっとも簡単な例として、int
型配列に格納されたデータを
昇順に並べ換えるプログラムを見てみよう。
#include <stdio.h>
#include <stdlib.h> /* qsort()のために必要 */
int compare_int(const void *x, const void *y)
/* 必ず2引数を const void* でもらう */
{
int *a = (int*)x; /* intへのポインタに変換 */
int *b = (int*)y;
return (*a - *b);
}
int main()
{
int data[] = {10, 20, 3, 48, 7, 2};
int n = sizeof data / sizeof (int);
int i;
qsort(data, n, sizeof (int), compare_int);
for (i=0; i<n; i++) {
printf("%d\n", data[i]);
}
}
構造体の配列も、int
型配列の並べ換えと同様の手順で
行なえば良い。変わるのは比較関数を作る部分だけである。
struct student
の配列を並べ換える場合、比較関数は
以下のようにすれば良い。
int comp_point(const void *x, const void *y)
{
struct student *a = (struct student *)x;
struct student *b = (struct student *)y;
/* a と b は構造体へのポインタ
したがって、メンバへのアクセスは ピリオドでなく -> を使う */
int sum_a, sum_b;
sum_a = a->mathpt + a->engpt + a->jpnpt;
sum_b = b->mathpt + b->engpt + b->jpnpt;
/* 引算の順番に注意 */
return sum_b - sum_a;
}
以上をまとめると、読み込んだ試験結果ファイルをもとに、 合計点の大きい順に並べ換えるプログラムは以下のようになる。
#include <stdio.h>
#include <stdlib.h>
#define NMAX 100
struct student {
char name[30];
int mathpt;
int engpt;
int jpnpt;
};
int main(int argc, char *argv[])
{
FILE *stream;
char buf[500];
struct student pt[NMAX+1];
int i, n;
/* プロトタイプ宣言 */
void dispall(struct student[]);
int comp_point(const void*, const void*);
if (NULL == argv[1]) {
stream = stdin;
} else {
stream = fopen(argv[1], "r");
if (NULL == stream) {
fprintf(stderr, "Cannot open data file [%s]\n", argv[1]);
exit(1);
}
}
i = 0;
while (NULL != fgets(buf, sizeof buf, stream)) {
if (i >= NMAX) {
fprintf(stderr, "Reached to maximum number(%d)\n", NMAX);
break;
}
n = sscanf(buf, "%29s %d %d %d",
pt[i].name, &pt[i].mathpt, &pt[i].engpt, &pt[i].jpnpt);
if (n == 4) {
i++;
}
}
if (stdin != stream)
fclose(stream);
pt[i].name[0] = 0; /* 終端の印 */
qsort(pt, i, sizeof pt[0], comp_point);
dispall(pt);
}
void dispall(struct student pt[])
{
int i, sum;
for (i=0; pt[i].name[0] != 0; i++) {
sum = pt[i].mathpt+pt[i].engpt+pt[i].jpnpt;
printf("%2d: %-20s, %d, %d, %d | %3d\n",
i+1, pt[i].name, pt[i].mathpt, pt[i].engpt, pt[i].jpnpt, sum);
}
}
/* 比較関数 */
int comp_point(const void *x, const void *y)
{
struct student *a = (struct student *)x;
struct student *b = (struct student *)y;
int sum_a, sum_b;
sum_a = a->mathpt + a->engpt + a->jpnpt;
sum_b = b->mathpt + b->engpt + b->jpnpt;
return sum_b - sum_a;
}