printfを実装してみたというお話。
何を作ったか
C言語標準ライブラリのprintf関数を実装。
プロトタイプ宣言 int ft_printf(const char *format, ...);
第一引数 format に%dなど含んだ文字列、その後に引数を受けて、
指定された形式で文字列を標準出力へ出力します。
- 解釈できるformatのルール
%[フラグ][最小フィールド幅].[精度][変換指定子]
- 対応フラグ : '-0'
- 対応変換指定子 : 'cspdiuxX%'
実装する前に
出力はwrite関数を使い、出力した文字数を返り値として返すためにft_printfプログラム内でwrite関数の返り値を受け取るようにします。
可変長引数を扱うので、stdarg系の関数を使います。
プログラムの流れ
- おおまかな流れは、
formatを一文字目から順に見ていき
'%'があれば%以降の文字列解析->第二引数以降を受け取り->指定された形式で文字を出力していく
%がなければ文字をそのまま出力する
例 : ft_printf("%dtokyo", 42)->write("42"), wirte("t"), wirte("o"), write("k"), write("y"), write("o") - 解析結果などの情報は逐次構造体にまとめて処理を進めていく
- 図での流れと、実行例
実行例[ft_printf("%-10.5d", -42)]
出力[-00042 ]
- start.c
- %が指定されているので解析開始、情報を構造体storeに格納
flag = HYPHEN (フラグ)
width = 10 (最小フィールド幅)
prec = 5 (精度)
spec = SPEC_D (変換指定子)
- %が指定されているので解析開始、情報を構造体storeに格納
- prepare_to_put.c
- 引数を受け取る->-42
接頭語を"-"、出力する本体を"42"と考える(こう分けると今後の処理が楽)
ちなみに%pならprefix="0x"、必要ない時はprefix=""にしとく - 出力する文字列の情報を構造体に格納
body = 2 (本体の文字数 : "42"の文字数)
zero = 3 (0の文字数 : 精度の5から"42"の文字数2を引いた数)
prefix = "-"
blank = 4 (空白の文字数 : フィールド幅の10からbody・zero・prefixの文字数を引いた数)
- 引数を受け取る->-42
- put.c
put_flag_hyphen()によりprefix, zero, body, blankの順番で文字を出力していく。まとめ
そもそもprintfで変換指定子以外使ったことがなかったので、
フラグの指定などあらゆる使い方を試してprintfの機能を理解するところから始めました。
enumと関数ポインタ配列の存在を42tokyoの知人に教えてもらい今回初めて使ってみました。
enumを使いだすと、配列の利点(任意のアドレスにランダムアクセスできたり)を使えるようになり、そこから関数ポインタ配列を使ってみることになり、コードがシンプルになっていきました。
コードがまとまっていくと、プログラムの流れもまとめやすくなり、最終的に「変換指定子別で前処理、フラグで出力方法を管理する」方向性が見えてきました。
- start.c