「ふつりな!」第6章の練習問題(1)

# けいおん!風に

『ふつうのLinuxプログラミング』に入門中です。

ありのまま今起こったことを話すと、「ファイル入出力の勉強をしていたと思ったら、C言語の勉強になっていた」。

何をいっているのか、分かりたくないけれども、そんなカンジに。

問題

タブ文字('\t')を「\t」、改行を「'$'+改行」として出力するcatコマンドを書きなさい。

とりあえず、タブ文字だけやることにしました。

わからないこと

  • 文字を1個ずつint型の値として読み込んだ後、文字列にする方法について
    • itoa関数ってホントにあるの? 手元のライブラリには含まれてない
    • sprintfを使うの? 使ってみたけど文字にならない

データを取り込む関数をいくつか知ったものの、イディオムをよく知らない状態です。勉強します。

自分の解答

誤答を堂々と貼る等します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void do_cat(const char *path);
static char *rep_str(char *str, const char *before, const char *after);
static char *str_add(char *str, const char *original, const char *after, int s);
static void die(const char *s);

int main(int argc, char *argv[])
{
	int i;

	if(argc < 2){
		fprintf(stderr, "%s: file name not given.\n", argv[0]);
		exit(1);
	}

	for(i = 1; i < argc; i++){
		do_cat(argv[i]);
	}
	return 0;
}

static void do_cat(const char *path)
{
	FILE *f;
	int c;
	char str[256];
	char before_t[] = "\t";
	char after_t[] = "\\t";
	int no = 10;
	char tmp[10];

	f = fopen(path, "r");
	if(!f){
		die(path);
	}

	str[0] = '\0';
	for(;;){
		tmp[0] = '\0';
		if((c = fgetc(f)) == EOF){
			break;
		}
		sprintf(tmp, "%d", c);
		strncat(str, tmp, no);

		if(c == '\n'){
			rep_str(str, before_t, after_t);
			fprintf(stdout, "%s", str);
			str[0] = '\0';
		}
	}
}

static char *rep_str(char *str, const char *before, const char *after)
{
	int i, j;
	char *result = str;
	int orig_len = strlen(str);
	int before_len = strlen(before);
	char orig[256];

	orig[0] = '\0';
	strcpy(orig, str);

	for(i = 0; i < orig_len - before_len + 1; i++){
		for(j = 0; j < before_len; j++){
			if(orig[i++] != before[j++]){
				break;
			}else{
				if(j == (before_len - 1)){
					str_add(result, orig, after, i);
				}
			}
		}
	}
	return result;
}

static char *str_add(char *str, const char *original, const char *after, int s)
{
	int i;
	char *result = str;
	char orig[256];

	orig[0] = '\0';
	strcpy(orig, str);
	str[0] = '\0';	

	for(i = 0; i < sizeof str; i++){
		str++;
	}
	for(i = 0; i < s - 1; i++){
		if(!(*str++ = *original++)){
			break;
		}
	}
	for(i = 0; i < sizeof after; i++){
		if(!(*str++ = *after++)){
			break;
		}
	}
	return result;
}

static void die(const char *s)
{
	perror(s);
	exit(1);
}

(もうヤケ)

覚えたこと

  • ポインタで表現される文字列と、配列で表現される文字列の違い
    • 前者は、ポインタ自体の格納場所が必要
    • 後者は、要素が短いときに後ろが余る
  • 配列で表現される文字列には、初期化子を代入できない
    • 文字列リテラルを変更してはいけない、らしい
  • 文字列のポインタを返す関数
    • 引数で渡した文字列を参照することになる
    • 関数名の頭に「*」がつくことと、仮引数にconstがつかないことに注意

リベンジしたい! リベンジ!