前回の練習で作成したファイルを準備しよう。まだ作っていない人は、以下のリンクよりファイルを入手しよう。
また、今回の演習の説明のために、以下のリンクよりファイルを入手しよう。
ここの name_list2.txt は、名前の順序がグチャグチャである。linux では、シェル上にソートコマンドがあるので、整えることができる。
# sort < name_list2.txt
不等号 < は、右側のファイルの内容を sort コマンドに入力するという意味である。キーボードから入力したのと同じ動作をする。
タブで区切られたテキストの場合、sort コマンドは次のように使うことができる。
# ./prac0401 < name_list2.txt | sort -k 2
prac0401 が、前回の練習で行ったとおり、タブ区切テキストに変換する。タブ区切りテキストの2列目を基準にしてソートせよという意味である。
縦棒は、パイプと言う。./prac0401 の画面に出力されるものが、sort への入力となる。
C言語には、配列をソートするための関数 qsort が stdlib.h より提供されている。ライブラリを使ったプログラミングは、下手に自分で書くよりも安全かつ高速である。man コマンドで qsort の説明を見てみよう:
#include <stdlib.h> void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
各引数の意味は次のとおりである:
第4引数の関数は次の仕様である:
上記の説明を理解するためには、ネームカードを要素とする配列を準備する必要がある。
list0502.c では、NameCard へのポインタの配列を ncarray という変数が扱っている。malloc により確保しているのは、MAX 個の NameCard へのポインタである。次に、最初の for 文では最大で MAX 回の繰り返しをしている。ここでは、NameCard を1つぶん確保して(nc_open)、標準入力からデータを入力している(nc_get)。nc_get に失敗すれば for 文を抜け、成功すれば配列変数に nc のポインタ値を代入する。
2番目の for 文で、配列変数 ncarray を最初から順に表示する。
なお、実行は、「./prac0401 < name_list2.txt | ./list0502」とせよ。
/* list0502.c */ #include <stdio.h> #include <stdlib.h> #include "namecard.h" #define MAX 30 int main() { NameCard **ncarray; NameCard *nc; int i, sz; // NameCard のポインタを格納する配列 ncarray = (NameCard **)malloc(sizeof(NameCard *) * MAX); if( ncarray == NULL ){ fprintf(stderr,"malloc failed 1\n"); exit(1); } // 標準入力 stdin からデータを入力し、配列に格納 for( sz = 0; sz < MAX; sz += 1 ){ nc = nc_open(); if( nc_get(nc, stdin) != 0 ) break; ncarray[sz] = nc; } // 配列の要素を表示 for( i = 0; i < sz; i += 1 ) nc_put(ncarray[i], stdout); // 終了 for( i = 0; i < sz; i += 1 ) nc_close(ncarray[i]); free(ncarray); return 0; } |
list0502.c で配列の要素を表示する前に、0番目と7番目を入れ替えてみよう。具体的には次の3行を挿入する。
nc = ncarray[0]; ncarray[0] = ncarray[7]; ncarray[7] = nc;
挿入した結果を、list0503.c としてセーブして、実行してみよう。
なお、実行は、「./prac0401 < name_list2.txt | ./list0503」とせよ。
配列の要素の入れ替えを、基準に従って行えば、ソートができる。その基準として、ネームカードのメンバ変数が利用できる。ネームカードのメンバ変数(ドライバーナンバー、名前、年齢)を比較して、カード間を比べる関数を作ろう。
ここで、比較関数の仕様は、既に述べた「第4引数の関数」の仕様とする。具体的には以下のとおりにする(list0502.c の main関数より上で記述)。
int cmp_driver_number(NameCard **nc1, NameCard **nc2) { return nc_get_driver_number(*nc1) - nc_get_driver_number(*nc2); }
ここで、比較する要素が、NameCard * というポインタ型であるので、ポインタのポインタが引数の型となる。
list0502.c で、「配列の要素を表示」のfor文の後(「// 終了」の直前)に次の4行を挿入しよう。
// 比較 printf("%s と %s のドライバーナンバーを比較すると %d です。\n", nc_get_name(ncarray[2]), nc_get_name(ncarray[3]), cmp_driver_number(&ncarray[2], &ncarray[3]));
挿入した結果を、list0504.c としてセーブして、実行してみよう。
なお、実行は、「./prac0401 < name_list2.txt | ./list0504」とせよ。
いよいよ、ソートを行う。qsort の第1引数は、配列の先頭へのポインタなので、ncarray である。第2引数は、要素の数なので、sz である。第3引数は、1要素のサイズなので、sizeof(要素) である。ここで、要素は、NameCard へのポインタなので、sizeof(NameCard *)とする。第4引数は、関数へのポインタなので、関数名 cmp_driver_number を書く。この実行を、「配列の要素を表示」というところの直前で実行する。
ここで、関数へのポインタについて、正しくキャストしなければ、コンパイル時に警告(Warnning)が出る。そこで、qsort の第4引数の関数名 cmp_driver_number の前に次の書式を書く。
(int (*)(const void *, const void *))
ステップ4を完成させ、ドライバーナンバーで昇順になるようにソートせよ。そのプログラムを prac0501.c とせよ。実行は、「./prac0401 < name_list2.txt | ./prac0501」として、正しく動作することを確認せよ。
次に、名前をアルファベットの辞書順に並べるための比較関数 cmp_name を作成し、prac0501.c に追加せよ。そして、ドライバーナンバーでソートする部分(qsortの呼出し部分)をコメントアウトしておき、代りに cmp_name でソートするステートメントを追加せよ。
レポートには、最終的な prac0501.c および実行結果を掲載せよ。