【C言語】軽くprintf関数を自作する

#:white_check_mark:概要
軽くprintf関数を自作します、フラグとかはつけません。
Qiitaの絵文字機能をたくさん使いたいなーと思って書いてみました:japanese_goblin:

#:white_check_mark:実装内容

大体の決まりごとはこんな感じでやっていくよ:airplane:

```
・完成ファイル
　　　　libftprintf.a
・使う関数
　　　　malloc, free, write, va_start, va_arg, va_copy, va_end

・対応するフォーマット指定子
　　　　c, s, p, d, i, u, x, X, %
```

#:white_check_mark:使用するサブ関数の実装

上の使う関数以外は使わないので標準関数から実装していくよ
`libft`ってフォルダーに格納する。

ヘッダーファイルとMakefileはちょっと省略:yum:

```libft.h
#ifndef LIBFT_H
# define LIBFT_H

# include <stdlib.h>
# include <unistd.h>

<以下関数のプロトタイプ宣言>

#endif
```

```Makefile
NAME = libft.a
CC = gcc
INC	 = -I.

SRCS = <以下の関数>

OBJS = $(SRCS:%.c=%.o)

all:	$(NAME)

.c.o:
		$(CC) $(INC) -c $< -o $@

$(NAME):$(OBJS)
		ar rcs $(NAME) $(OBJS)

clean:
		rm -f $(OBJS)

fclean:	clean
		rm -f $(NAME)

re:	fclean all
```

みんな大好き文字数カウントする関数
`\0`が出るまでカウンターを`+1`していく

```ft_strlen.c
#include "libft.h"

size_t	ft_strlen(const char *s)
{
	size_t	return_len;

	return_len = 0;
	while (s[return_len] != '\0')
		return_len ++;
	return (return_len);
}
```

与えられたメモリ領域を`int c`で初期化する。
順番に代入していく

```ft_memset.c
#include "libft.h"

void	*ft_memset(void *dst, int c, size_t n)
{
	size_t			count;
	unsigned char	*str;

	count = 0;
	str = (unsigned char *)dst;
	while (count < n)
	{
		str[count] = (unsigned char)c;
		count++;
	}
	return (dst);
}
```

`ft_memset()`に`\0`を引数として渡せば完成
本家GNUの`bzero()`もこうやって実装されています

```ft_bzero.c
#include "libft.h"

void	ft_bzero(void *s, size_t n)
{
	ft_memset(s, '\0', n);
}
```

`malloc()`を使用して確保した領域を`ft_bzero()`で初期化する

```ft_calloc.c
#include "libft.h"

void	*ft_calloc(size_t count, size_t size)
{
	void	*result;

	if (count == 0 || size == 0)
	{
		count = 1;
		size = 1;
	}
	result = malloc(count * size);
	if (!result)
		return (result);
	ft_bzero(result, count * size);
	return (result);
}
```

`ft_calloc()`で確保した領域に引数を１文字ずつ代入していく。
リターンするのは確保したアドレス。

```ft_strdup.c
#include "libft.h"

char	*ft_strdup(const char *str)
{
	char	*result;
	size_t	i;

	result = (char *)ft_calloc(ft_strlen(str) + 1, sizeof(char));
	if (!result)
		return (0);
	i = 0;
	while (str[i])
	{
		result[i] = str[i];
		i++;
	}
	return (result);
}
```

簡易的に`itoa()`を実装している。
`ft_calloc()`で桁数分確保した領域に`+48`してINTをASCIIに変換した物を代入してく。

```ft_itoa.c
#include "libft.h"

static size_t	ft_count(int num)
{
	int		i;
	size_t	size;

	i = num;
	size = 1;
	if (i < 0)
	{
		i = -i;
		size++;
	}
	while (10 <= i)
	{
		i = i / 10;
		size++;
	}
	return (size);
}

static char	*ft_return_intmin(void)
{
	char	*res;

	res = (char *)ft_calloc(12, sizeof(char));
	if (!res)
		return (NULL);
	ft_memcpy(res, "-2147483648", 11);
	return (res);
}

char	*ft_itoa(int n)
{
	size_t	size;
	char	*result;

	if (n == -2147483648)
		return (ft_return_intmin());
	size = ft_count(n) - 1;
	result = (char *)ft_calloc(size + 2, sizeof(char));
	if (!result)
		return (NULL);
	if (n < 0)
	{
		n = -n;
		result[0] = '-';
	}
	while (10 <= n)
	{
		result[size--] = 48 + (n % 10);
		n = n / 10;
	}
	result[size] = 48 + n;
	return (result);
}
```

引数が`'A' <= c && c <= 'Z'`を満たしたときに`引数+32`した値を返す
`+32`は大文字の場合小文字に変換される
ASCIIコード表を参照してね:thumbsup_tone1:
それ以外はそのまま返す。

```ft_tolower.c
#include "libft.h"

int	ft_tolower(int c)
{
	if ('A' <= c && c <= 'Z')
		return (c + 32);
	return (c);
}
```
１文字だけ出力する関数
ファイルディスクリプタは1で標準出力になる

```ft_putchar_fd.c
#include "libft.h"

void	ft_putchar_fd(char c, int fd)
{
	write(fd, &c, 1);
}
```
文字列を出力する関数
文字数をカウントして、その分`write()`にわたす

```ft_putstr_fd.c
#include "libft.h"

void	ft_putstr_fd(char *s, int fd)
{
	if (!s)
		return ;
	write(fd, s, ft_strlen(s));
}
```

メモリ領域に任意の値をコピーするmemcpy追記しました！

```ft_memcpy.c
#include "libft.h"

void	*ft_memcpy(void *dst, const void *src, size_t n)
{
	size_t			cnt;
	char			*too;
	const char		*frm;

	frm = (const char *)src;
	too = (char *)dst;
	cnt = 0;
	if (frm == NULL && too == NULL)
		return (NULL);
	while (cnt < n)
	{
		too[cnt] = frm[cnt];
		cnt++;
	}
	return (dst);
}
```

#:white_check_mark:フロー

大まかにどんな感じで処理するかを決める:muscle::muscle::muscle:

|ファイル|役目|
|--|--|
|ft_printf.c|可変長引数を受け取る。引数をft_strdupで保存してから、それ以降の引数をft_count_output()に渡す（出力）。戻り値を返す|
|ft_printf.h|使用する関数のリストとインクルードするファイルを書く|
|ft_count_output.c|フォーマット識別子以外を出力。%が来たら引数をft_treat_something()に識別子と一緒に渡す|
|ft_treat_something.c|それぞれの識別子を扱う以下のft_treat_*()に引数を渡す。|
|ft_treat_char.c|フォーマット識別子%cの際に、引数の１文字を出力、文字数をカウントして文字数を返す。（戻り値は必ず１となる）|
|ft_treat_string.c|フォーマット識別子%cの際に、引数の文字列を出力、文字数をカウントして文字数を返す。|
|ft_treat_point.c|フォーマット識別子%pの際に、引数のアドレスを出力、アドレスの文字数をカウントして文字数を返す。|
|ft_treat_int.c|フォーマット識別子%dか%iの際に、引数の値を数で出力、文字数をカウントして文字数を返す。|
|ft_treat_uint.c|フォーマット識別子%uの際に、引数を正の整数で出力、文字数をカウントして文字数を返す。|
|ft_treat_hexa.c|フォーマット識別子%xか%Xの際に、整数の引数を16進数で出力、文字数をカウントして文字数を返す。xは小文字でXは大文字|
|ft_putstr_count.c|出力するための関数。出力した文字数をカウントして返す。|
|ft_change_base.c|進数変換をするために使う|

#:white_check_mark:ft_print.c

引数を`ft_strdup()`に渡して、saveに保存する。
`va_start(args, input)`で`va_list`の`args`に`input`を入れるってことが出来る。
`save`と`args`を`ft_count_output()`に渡して、戻り値は出力した文字数。
`free()`で`save`を開放し忘れないようにっ！

`if (!save) return (0);`を追記しました。

```ft_print.c
#include "ft_printf.h"

int	ft_printf(const char	*input, ...)
{
	const char	*save;
	va_list		args;
	int			num;

	num = 0;
	save = ft_strdup(input);
    if (!save)
        return (0);
	va_start(args, input);
	num = ft_count_output(save, args);
	va_end(args);
	free((char *)save);
	return (num);
}
```

#:white_check_mark:ft_count_output.c

もし、`%`が来たら`ft_treat_something()`に`args`とフォーマット識別子を渡す
それ以外なら、出力する
`ft_treat_something()`からは文字数が返ってくるようにしてるからそれをどんどん足していく
返り値は出力した文字数にする。

```ft_count_output.c
#include "ft_printf.h"

int	ft_count_output(const char *save, va_list args)
{
	size_t	i;
	size_t	c;

	i = 0;
	c = 0;
	while (save[i])
	{
		if (save[i] == '%')
		{
			i++;
			c += ft_treat_something(save[i], args);
		}
		else
		{
			ft_putchar_fd(save[i], 1);
			c++;
		}
		i++;
		if (!save[i])
			return (c);
	}
	return (c);
}
```

#:white_check_mark:ft_treat_something.c

`c`の条件によって扱う型が違うから分岐させる
```va_arg(args, <変換したい方>)```で型変換できるから楽ぅぅ！

```ft_treat_something.c
#include "ft_printf.h"

int	ft_treat_something(int c, va_list args)
{
	int	n;

	n = 0;
	if (c == 'c')
		n = ft_treat_char(va_arg(args, int));
	else if (c == 's')
		n = ft_treat_string(va_arg(args, char *));
	else if (c == 'p')
		n = ft_treat_point((unsigned long long)va_arg(args, void *));
	else if (c == 'd' || c == 'i')
		n = ft_treat_int(va_arg(args, int));
	else if (c == 'u')
		n = ft_treat_uint((unsigned long long)va_arg(args, unsigned long long));
	else if (c == 'x')
		n = ft_treat_hexa(va_arg(args, unsigned int), 1);
	else if (c == 'X')
		n = ft_treat_hexa(va_arg(args, unsigned int), 2);
	else if (c == '%')
		n = ft_putstr_count("%");
	return (n);
}
```
#:white_check_mark:ft_change_base.c

進数変換をする。
第一引数に数値もらって、第二引数に変換する進数を受け取る
どんどん割って行って桁数を把握した後、`ft_calloc()`で確保した領域に割った結果を入れる

```ft_change_base.c
include "ft_printf.h"

static char	*change_base(unsigned long long us, int b, char *r, int c)
{
	while (us != 0)
	{
		if ((us % b) < 10)
			r[c - 1] = (us % b) + 48;
		else
			r[c - 1] = (us % b) + 55;
		us /= b;
		c--;
	}
	return (r);
}

char	*ft_point_base(unsigned long long point, int base)
{
	char				*rtn;
	unsigned long long	us;
	int					count;

	count = 0;
	us = point;
	if (point == 0)
		return (ft_strdup("0"));
	while (point != 0)
	{
		point = point / base;
		count++;
	}
	rtn = ft_calloc(count + 1, sizeof(char));
	if (!rtn)
		return (0);
	rtn = change_base(us, base, rtn, count);
	return (rtn);
}
```

#:white_check_mark:ft_tolower_all.c

与えられた文字を全て小文字に変換する
順番に１文字ずつ`ft_tolower()`に突っ込んでるだけですー

```ft_tolower_all.c
#include "ft_printf.h"

char	*ft_tolower_all(char *c)
{
	int	i;

	i = 0;
	while (c[i])
	{
		c[i] = ft_tolower(c[i]);
		i++;
	}
	return (c);
}
```

#:white_check_mark:ft_treat*.c

`char`型を受け取って出力。
必ず１文字なので```return (1);```ってしてる。

```ft_treat_char.c
#include "ft_printf.h"

int	ft_treat_char(char c)
{
	ft_putchar_fd(c, 1);
	return (1);
}
```

`string`型の変数を受け取って出力する。
例外にstrがNULLの時、```(null)```ってしなきゃいけない。
戻り値は文字数なので`ft_strlen()`をリターンする。

```ft_treat_string.c
#include "ft_printf.h"

int	ft_treat_string(char *str)
{
	int	n;

	if (str == NULL)
		str = "(null)";
	n = ft_strlen(str);
	ft_putstr_fd(str, 1);
	return (n);
}
```
渡された引数のアドレスを16進法に変えて全て小文字で出力する。
受け取る時点でアドレスになっているからここでは16進数に変更して小文字にすればいいってこと。
`0x`を出力してから出力する。

```ft_treat_point.c
#include "ft_printf.h"

int	ft_treat_point(unsigned long long point)
{
	char	*p;
	int		n;

	p = ft_tolower_all(ft_point_base(point, 16));
	n = ft_putstr_count("0x");
	n += ft_putstr_count(p);
	free(p);
	return (n);
}
```
整数値を出力する。
`ft_itoa()`で渡された値を文字に変換して、出力。
カウントしてリターン。

```ft_treat_int.c
#include "ft_printf.h"

int	ft_treat_int(int i)
{
	char	*str;
	int		n;

	str = ft_itoa(i);
	n = ft_putstr_count(str);
	free(str);
	return (n);
}
```

正の整数を出力する。
~~要修正）INTの範囲を超すと、あまりうまくいってない.....:cold_sweat:~~

修正済み）ft_itoa()を使用せずにそのまま出力する関数作りました。
他のところでこの関数を使用しない理由は、こっちはint範囲超えても回らずに、他は回したいからです！

```ft_treat_uint.c
#include "ft_printf.h"

static int	ft_put_unbr_count(unsigned long long n)
{
	char	str;
	unsigned long long tmp;
	size_t	t;

	tmp = n;
	t = 0;
	while (9 < tmp)
	{
		tmp = tmp / 10;
		t++;
	}
	if (9 < n)
		ft_put_unbr_count(n / 10);
	str = '0' + n % 10;
	write(1, &str, 1);
	return (t+1);
}

int	ft_treat_uint(unsigned long long uint)
{
	int		n;

	n = 0;
	n = ft_put_unbr_count(uint, 1);
	return (n);
}
```
第二引数のfが１だった場合には`ft_tolower_all()`で全て小文字にする
つまり、`%x`の場合に全て小文字にして`%X`の場合は大文字でおっけってこと:ok_hand:
`free(hex)`で開放も忘れないように！メモリリークします:scream:

```ft_treat_hexa.c
#include "ft_printf.h"

int	ft_treat_hexa(unsigned int i, int f)
{
	char	*hex;
	int		n;

	if (!i)
		i = 0;
	hex = ft_point_base((unsigned long long)i, 16);
	if (f == 1)
		hex = ft_tolower_all(hex);
	n = ft_putstr_count(hex);
	free(hex);
	return (n);
}
```

#:white_check_mark:ft_putstr_count.c

文字数をカウントして出力
その分を返す関数になってます！

```ft_putstr_count.c
#include "ft_printf.h"

int	ft_putstr_count(char *c)
{
	int	n;

	if (!c)
		return (0);
	n = ft_strlen(c);
	ft_putstr_fd(c, 1);
	return (n);
}
```


#:white_check_mark:Makefile

```Makefile
NAME = libftprintf.a
CC = gcc
FLAGS = -c
LIBFT = ./libft/libft.a

SRCS = ft_change_base.c \
	ft_count_output.c \
	ft_printf.c \
	ft_tolower_all.c \
	ft_treat_hexa.c \
	ft_treat_int.c \
	ft_treat_point.c \
	ft_treat_something.c \
	ft_treat_string.c \
	ft_treat_uint.c \
	ft_treat_char.c \
	ft_putstr_count.c

SURPL_O = ft_change_base.o \
	ft_count_output.o \
	ft_printf.o \
	ft_tolower_all.o \
	ft_treat_hexa.o \
	ft_treat_int.o \
	ft_treat_point.o \
	ft_treat_something.o \
	ft_treat_string.o \
	ft_treat_uint.o \
	ft_treat_char.o \
	ft_putstr_count.o

OBJS = $(SRCS:.c=.o)

$(NAME): $(OBJS)
	$(MAKE) -C ./libft
	cp libft/libft.a $(NAME)
	$(CC) $(FLAGS) $(SRCS)
	ar -rcs $(NAME) $(OBJS)

all : $(NAME)

clean :
	$(MAKE) clean -C ./libft
	rm -rf $(SURPL_O) 
	rm -rf $(OBJS)

fclean : clean
	$(MAKE) fclean -C ./libft
	rm -rf $(NAME)

re : fclean all
```

#:white_check_mark:動作チェック

今回作成した関数は、静的ライブラリにするからちゃんと実装できているか確認する必要あり
メイン関数も作っていく:muscle:

```main.c
#include "ft_printf.h"
#include <stdio.h>

int	main(void)
{
	int	num1 = ft_printf("m_c char	> %c			%c	<	", 'a', 'b');
	printf("%d \n", num1);
	int	num2 = printf("o_c char	> %c			%c	<	", 'a', 'b');
	printf("%d \n", num2);

	printf("\n");
	static char str = '\0';
	num1 = ft_printf("m_c char	> %c			%c	<	", str, 'b');
	printf("%d \n", num1);
	num2 = printf("o_c char	> %c			%c	<	", str, 'b');
	printf("%d \n", num2);

	printf("\n");
	num1 = ft_printf("m_s string	> %s			%s	<	", "hey", "siri");
	printf("%d \n", num1);
	num2 = printf("o_s string	> %s			%s	<	", "hey", "siri");
	printf("%d \n", num2);
	
	printf("\n");
	num1 = ft_printf("m_s string	> %s			%s	<	", "", "\0");
	printf("%d \n", num1);
	num2 = printf("o_s string	> %s			%s	<	", "", "\0");
	printf("%d \n", num2);

	printf("\n");
	int num = 1;
	int	*p_num1 = &num;
	num1 = ft_printf("m_p poiner	> %p %p	<	", "hey", p_num1);
	printf("%d \n", num1);
	num2 = printf("o_p poiner	> %p %p	<	", "hey", p_num1);
	printf("%d \n", num2);
	
	printf("\n");
	num1 = ft_printf("m_d digit	> %d			%d	<	", -123, 0);
	printf("%d \n", num1);
	num2 = printf("o_d digit	> %d			%d	<	", -123, 0);
	printf("%d \n", num2);
	
	printf("\n");
	num1 = ft_printf("m_i int 	> %i			%i	<	", -123, 42);
	printf("%d \n", num1);
	num2 = printf("o_i int 	> %i			%i	<	", -123, 42);
	printf("%d \n", num2);
	
	printf("\n");
	num1 = ft_printf("m_u uint	> %u			%u	<	", 1, 4321);
	printf("%d \n", num1);
	num2 = printf("o_u uint	> %u			%u	<	", 1, 4321);
	printf("%d \n", num2);
	
	printf("\n");
	num1 = ft_printf("m_x hex 	> %x		%x	<	", -1, 4321);
	printf("%d \n", num1);
	num2 = printf("o_x hex 	> %x		%x	<	", -1, 4321);
	printf("%d \n", num2);

	printf("\n");
	num1 = ft_printf("m_x hex 	> %X		%X	<	", -1, 42);
	printf("%d \n", num1);
	num2 = printf("o_x hex 	> %X		%X	<	", -1, 42);
	printf("%d \n", num2);

	printf("\n");
	num1 = ft_printf("m_%% %%    	> %c			%s	<	", 'a', "abc");
	printf("%d \n", num1);
	num2 = printf("o_%% %%    	> %c			%s	<	", 'a', "abc");
	printf("%d \n", num2);

	return (0);
}
```

そしたらコマンド実行

```
$ make
$ gcc main.c libftprintf.a
$ ./a.out
```

うおおおおお！
できましたーー:v::v::v::v::v::v:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1523062/0b428784-528d-45f5-7de9-f27fd4b5478c.png)

#:white_check_mark:振り返り

C言語の`printf()`を自作しました
30時間くらいかかったかもしれません。大変でした。
ちょっと修正点があります（カモシレマセン）
温かい目で見てくださってありがとうございます:bow::bow::bow:


#:white_check_mark:おまけ

:eye:　:eye::ear:
　:tongue:  < see you!