どうせなら、もう少し潰しがきくように書いてみた。
はこべにっき# - 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