C言語の理解度を高めるための課題の案
配列シャッフルをC言語(ISO C89)で書くと以下のようになります:
#include <stdlib.h> #include <math.h> #include <stdio.h> static void shuffle(const char ** const array); static void swap(const char ** const array, int i, int j); static const char * names[] = { "taro", "jiro", "saburo", "shiro", "goro", 0}; extern int main(void) { const char * const * cursor; shuffle(names); for (cursor = names; *cursor; ++cursor) puts(*cursor); return 0; } static void shuffle(const char ** const array) { const char ** cursor = 0; int i = 0; for (cursor = array; *cursor; ++cursor) { int dst = (int) floor((rand() / (RAND_MAX + 1.0)) * (i + 1)); swap(array, i , dst); i += 1; } } static void swap(const char ** const array, int i, int j) { const char * const tmp = array[i]; array[i] = array[j]; array[j] = tmp; }
これはvalidなコードで、無警告でコンパイル可能です。
考察ポイントは
- C言語の型の構文
- なるべくconstを厳密に使用するには
- namesの型を「const char ** names = ...」にするとコンパイルが通らないこと
- static, externの意味
- ポインタ配列のラストを0(NULL)にするイディオム、その使い方
- 標準ライブラリを調べること
- rand()の使い方
です。リファレンスマニュアルで調べましょう。
選択ソートもC(ISO C89)で書いておきます
extern int puts(const char *); extern int strcmp(const char *, const char *); static void selection_sort(const char ** const array); static void swap(const char ** const array, int i, int j); static int array_count(const char * const * array); static const char * names[] = { "taro", "jiro", "saburo", "shiro", "goro", 0}; extern int main(void) { const char * const * cursor; selection_sort(names); for (cursor = names; *cursor; ++cursor) puts(*cursor); return 0; } static void selection_sort(const char ** const array) { const int count = array_count(array); int i = 0; for (; i < count; i++) { int min = i; int j = i + 1; for (; j < count; j++) { if (strcmp(array[j], array[min]) < 0) min = j; } swap(array, i, min); } } static int array_count(const char * const * array) { int count = 0; for (; *array; ++array) count++; return count; }
考察ポイントはプリプロセッサincludeを使用していないことです。
なぜ#includeするのか、を調べてください。
プリプロセッサマクロで書き直す
上は、関数でシャッフルや選択ソートを書いたのですが、それをプリプロセッサマクロで書き直したものです。ポインタ配列の型に依存しないようにしています。
シャッフル
#include <stdlib.h> #include <math.h> #include <stdio.h> #define shuffle(ElemType, array) do { \ ElemType * cursor = 0; \ int i = 0; \ for (cursor = array; *cursor; ++cursor) { \ int dst = (int) floor((rand() / (RAND_MAX + 1.0)) * (i + 1)); \ swap(ElemType, array, i , dst); \ i += 1; \ } \ } while (0) #define swap(ElemType, array, i, j) do { \ ElemType tmp = array[i]; \ array[i] = array[j]; \ array[j] = tmp; \ } while (0) static const char * names[] = { "taro", "jiro", "saburo", "shiro", "goro", 0}; extern int main(void) { const char * const * cursor; shuffle(const char *, names); for (cursor = names; *cursor; ++cursor) puts(*cursor); return 0; }
考察ポイントは、マクロをdo while(0)でくくることです。
なぜただのブロックにしていないかは、以下のコンパイルが通らないコードを考えてください
/* parse error code */ #define twoup(c) { \ c++; \ c++; \ } int main() { int count = 0, flag = 1; if (flag) twoup(count); else count += 10; return 0; }
(これはともかく、コーディングスタイルとしては、else節のつくif文では単文でもブロックの{}をつけるべきでしょう)。
同様に選択ソートも書き換えました:
extern int puts(const char *); extern int strcmp(const char *, const char *); #define selection_sort(ElemType, array, compare_to) do { \ int count; \ int i = 0; \ array_count(ElemType, array, count); \ for (; i < count; i++) { \ int min = i; \ int j = i + 1; \ for (; j < count; j++) { \ if (compare_to(array[j], array[min]) < 0) min = j; \ } \ swap(ElemType, array, i, min); \ } \ } while (0) #define swap(ElemType, array, i, j) do { \ ElemType tmp = array[i]; \ array[i] = array[j]; \ array[j] = tmp; \ } while (0) #define array_count(ElemType, array, count) do { \ ElemType * cursor; \ count = 0; \ for (cursor = array; *cursor; ++cursor) count++; \ } while (0) static const char * names[] = { "taro", "jiro", "saburo", "shiro", "goro", 0}; extern int main(void) { const char * const * cursor; selection_sort(const char *, names, strcmp); for (cursor = names; *cursor; ++cursor) puts(*cursor); return 0; }
マクロを使わず関数をvoid**化することでも一般化可能です。記述してみましょう。
ポインタ配列を一般化する場合、void**になります。
ただし、void**はvoid*ではないので、const char **等を直接代入するにはキャストが必要です(gccではキャストしないと警告が出る)。