どうせなら、もう少し潰しがきくように書いてみた。
はこべにっき# - C言語でtailっぽいものを書くまた,明日学科のC言語のテストがある.C言語なぞ普段まったく使わないもんだから,思い出さねば.てことで,10行固定版tailを書いてみた.以下のソース.
まずは、main()の方から。再発明だけでは芸が無いので、行数もオプションとして指定できるようにしてみた。
tail.c#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_CHARS_IN_LINE 1024 #include "queue.h" char *new_string(const char *str){ char *new = (char *)malloc(strlen(str)+1); if (new == NULL) DIE_OUT_OF_MEMORY; strcpy(new, str); /* it's okay not to use strncpy */ return new; } void usage(const char *str){ fprintf(stderr, "usage: %s [-n] inputfile\n", str); exit(2); } int print_atom(void *str){ return printf("%s", (char *)str); } int main(int argc, char **argv){ char buf[MAX_CHARS_IN_LINE]; int nline = 10; queue *q; FILE *fp; if (argc < 2) usage(argv[0]); if (argv[1][0] == '-'){ nline = atoi(argv[1]+1); if (!nline) usage(argv[0]); } if ((fp = fopen(argv[argc-1],"r")) == NULL){ fprintf(stderr, "%s : %s\n", argv[argc-1], strerror(2)); exit(2); } q = new_queue(nline); while (fgets(buf, MAX_CHARS_IN_LINE, fp)) { enqueue(q, new_string(buf)); } traverse_queue(q, print_atom); del_queue(q); exit(0); }
見ての通り、
- 今時の端末なら、一画面に収まる大きさに。
- -Wallをつけてcompileしてもちゃんとwarningが出ないよう、きっちりcast。
- queueの関わる関数定義は、queue.hに全部追い出してある。
- traverse_queue()に注目。Cでも一応関数型っぽく出来なくもないという例として。もっともCにはlambdaはないのだけど。
それで、queue.hは以下のとおり。
queue.h#define DIE_OUT_OF_MEMORY { fprintf(stderr, "Out of memory!\n"); exit(-1); } struct list { void *car, *cdr; }; typedef struct list list; struct queue{ int size, max; list *head, *tail; }; typedef struct queue queue; list *new_list(void *atom){ list *new = (list *)malloc(sizeof(list)); if (new == NULL) DIE_OUT_OF_MEMORY; new->car = atom; new->cdr = NULL; new->head = new->tail = NULL; /* shiro-san, thanx */ return new; } void del_list(list *l){ if (l == NULL) return; if (l->car) free(l->car); free(l); } queue *new_queue(int max){ queue *new = (queue *)malloc(sizeof(queue)); if (new == NULL) DIE_OUT_OF_MEMORY; new->max = max; new->size = 0; return new; } list *dequeue(queue *q){ list *head = q->head; if (head) { q->head = q->head->cdr; q->size--; } return head; } int enqueue(queue *q, void *atom){ list *l = new_list(atom); if (q->size == q->max) del_list(dequeue(q)); if (q->size == 0) { q->head = q->tail = l; q->head->cdr = q->tail; }else{ q->tail->cdr = l; q->tail = l; } q->size++; return q->size; } void del_queue(queue *q){ list *l; if (q == NULL) return; while((l = dequeue(q)) != NULL){ del_list(l); } free(q); } void traverse_queue(queue *q, int (*callback)(void *)){ list *l; for (l = q->head; l; l = l->cdr) callback(l->car); }
- car,cdrというのは趣味:)
- void *なので、潰しが利く。これならtail以外にも使えるだろう。
- ただし、carにlistをぶら下げることは考慮していない。が、ここではqueueを管理できればよいのでこの程度でいいだろう。
- queueにまつわる操作は一通り出来るので、使う方は構造を気にする必要があまりない。強いて言えば、enqueueされるelementが必ずmalloc()されたものであるということか。
- traverse_queue()のcallback()はvoidでもよかったかな。
ちなみに、「ふつうのHaskell本」のtailは、メモリー浪費型。この程度だとまだHaskellのご利益はあまりなくて、Perlでは
perl -e 'print reverse((reverse <>)[0..9])'
で出来てしまう。
Dan the Occasional C Programmer
>strdup はあえて使っていないのでしょうか?
確かにstrdup()便利ですが、K&Rに載ってないというのはちょっと躊躇しちゃいます。
man 2 strdup
> HISTORY
> The strdup() function first appeared in 4.4BSD.
ともありますし。それいやstrncmp()もそうですが、こちらは
buffer overflowを防ぐという重要な目的もありますし。
>char * ではなく、 const char * のほうがいい
これはおっしゃる通りですね。直しときます。
# 余談ですが、Perl6の引数は、デフォルトでconst扱いになります。
Dan the Occasional C Programmer