printf("%d + $d = %d", 1, 1, 1);
위와 같이 우리가 printf를 사용하다 보면 자연스럽게 함수에 인자를 1개만 넣는 상황도 있고, 아니면 넣지 않거나 혹은 위와 같이 다수의 인자를 넣어도 문제없이 함수는 작동한다. 이것을 이해하기 위해 가변인자의 개념을 알아야 한다.
int ft_printf(const char *, ...)
위와 같이 과제의 프로토타입에서 보이는 …이 가변인자라고 할 수 있다. 파라미터로 아무것도 넘겨주지 않을 수도 있으며, 여러 개를 넘겨주는 것 또한 가능하다.
va_list
변수를 생성한다. 해당 변수는 각 가변인자의 시작 주소를 가리키는 포인터이다.va_start
함수를 이용한다. 해당 함수는 가변인자를 가져올 수 있다록 포인터를 설정(초기화) 하는 함수이다.va_start(ap, str)
에서 ap는 가변인자(va_list) 변수, 그 뒤 고정인자를 넣는다. 이 때 가변인자는 고정인자의 뒤에 위치하게 된다.고정인자 | 가변인자1 | 가변인자2 | 가변인자3 |
---|
👆🏻
va_list
va_arg
함수를 이용하여 가변인자를 가져온다. va_arg
(va_list, 자료형)은 va_list
가 가리키는 값을 자료형의 크기
만큼 리턴한 후, va_list를 자료형 크기 만큼 뒤로 옮긴다
.va_end
함수를 이용하여 va_list를 초기화
한다.각 가변 인자의 시작 주소를 가리킬 포인터이다.
I void va_start(va_list ap, var_name);
ap => va_list 로 만든 포인터가 담긴다.
var_name => 마지막 고정된 필수 인수가 담긴다.
va_start는 매크로 함수이고, va_list로 만들어진 포인터에게 가변 인자 중 첫 번째 인자의 주소를 가르쳐주는 매크로이다.
va_arg, va_copy, va_end에 대한 후속 호출에 대해 ap 포인터 값을 초기화한다.
var_type va_arg(va_list ap, var_type);
ap => va_list 로 만든 포인터가 담긴다.
var_type => int or long과 같은 타입 이름이 담긴다.
va_arg를 호출하면 하나의 매개변수를 리턴하고, ap가 다음 매개변수를 가리키게 한다. va_list의 포인터를 다음 가변 인자로 이동시키는 매크로이다.
void va_copy(va_list dest, va_list src);
va_start를 dest에 적용한 뒤 src
va_end(va_list arg_ptr);
va_end는 매크로 함수이고, 사용했던 가변 인자 목록을 비워준다. 함수가 리턴하기 전에 반드시 호출해야 한다.
표준 출력에 데이터들을 형식 문자열에 지정된 형태로 출력한다.
형식 문자열 다음에는 출력한 데이터를 나열한다. 형식 문자열 다음으로 오는 인자들의 개수는 반드시 형식 문자열 속의 형식 태그보다 같거나 많아야 한다.
서식 지정자 (format specifier)
형식 태그 (format tag)
%[flag][width][precision]서식지정자
ft_printf의 형식 태그는 위와 같이 생겼다
서식지정자에 옵션을 추가하려면 이미 규약된 형식태그를 지켜야 한다. ft_printf의 파라미터에 들어오는 값들을 형식 태그가 지정한 형태로 변환되어 출력하는 것이다. 그래서 위에서 말한 “인자의 개수” ≥ “형식 태그의 개수” 라는 조건이 성립되는 것이다.
- | 0 | + | 공백 | # | 정밀도(.) | |
---|---|---|---|---|---|---|
c | 공백을 뒤로 | UB | x | x | x | x |
s | 공백을 뒤로 | UB | x | x | x | 최대 문자 갯수 |
p | 공백을 뒤로 | 공백을 0으로 | x | x | x (이미 적용) | 최소 문자 갯수 |
d | 공백을 뒤로 | 공백을 0으로 | 양수에 + | 양수에 공백 | x | 최소 문자 갯수 |
i | 공백을 뒤로 | 공백을 0으로 | 양수에 + | 양수에 공백 | x | 최소 문자 개수 |
u | 공백을 뒤로 | 공백을 0으로 | x | x | x | 최소 문자 개수 |
x | 공백을 뒤로 | 공백을 0으로 | x | x | 숫자에 0x붙임 | 최소 문자 개수 |
X | 공백을 뒤로 | 공백을 0으로 | x | x | 숫자에 0X붙임 | 최소 문자 개수 |
% | 공백을 뒤로 | 공백을 0으로 | x | x | x | x |
단 UB 의 경우, undefined behavior이지 결과값은 플래그가 적용된 상태
플래그가 두 개 동시에 나오는 경우, 0 플래그는 제외하고 -플래그만 사용한다.
또한, 플래그가 여러개가 나올 경우에도 작동한다.
#include <stdio.h>
int main()
{
printf("%---0---0---s\n", "12345");
}
물론 위 경우처럼 '-'와 '0'이 동시에 나올 경우, undefined behavior이다.
하나만 여러개 나올 경우 경고발생도 없다.
플래그는 어느 위치에서나 적용될 수 있지만, 경고 메시지가 뜬다.
(%---10.3--s의 경우, invalid conversion specifier '-')
따라서, ft_printf는 undefined behavior에 관하여 출력내용을 따라하기도,
에러로 판단하고 출력하지 않기도 한다.
%
에서부터 type
까지의 부분에 대하여 출력될 총 문자열의 길이를 뜻한다.데이터의 길이
(가변인자로 받았고, 가공되어 출력 전의 상태의 데이터) 보다 값이 크다면 width
만큼, 아니면 데이터의 길이
만큼 출력이 된다.*
이 나오면 가변인자에서, 나오지 않았다면 숫자가 있을 경우 파싱하여 값을 저장한다.*
과 숫자는 같이 나올 수 없다.-
플래그 + 숫자로 간주한다..
을 사용.*
을 이용하여 가변인자에서 불러올 수 있다..
뒤에 숫자 없이 바로 type
이 올 수 있다. (이때, 0으로 간주 ex)%.d)data
의 크기를 int
로 받아온다.width
의 길이가 1보다 길면, 출력할 길이를 width만큼 확보한다.0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다-플래그
유무에 따라 문자를 앞 혹은 뒤에 넣고 출력한다.char *
자료형으로 받아온다. 저장되는 데이터는 문자열의 주소값이다.data
가 null(0)
일 경우 문자열은 (null)
이 된다. 이때, 괄호가 포함된다.문자열의 길이보다 길
경우 정밀도는 문자열의 길이이며, 양수이면서 문자열의 길이보다 짧을
경우 문자열을 정밀도의 길이만큼만 출력한다.width
이 현재 가공된 데이터
보다 길경우 width만큼, 짧을 경우
가공된 데이터 길이만큼 확보한다.0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.-플래그
유무에 따라 문자열을 앞 혹은 뒤에 넣고 출력한다.int형
으로 받아온다.litoa함수
를 사용하여 문자열로 바꾼다. 이 때 음수는 고려하지 않는다.
그 차이만큼 앞에 0을 추가
한다.0플래그
가 적용될 사항일 경우 0
, 아닐경우 빈칸
을, 확보한 string에 넣는다-플래그
유무에 따라 문자열을 앞 혹은 뒤에 넣는다.주소값 출력이지만 가변인자로 일반변수를 넣으면, 값을 16진수로 변환하여 출력한다.
data
를 void *
형으로 받아오고 unsigned long
으로 캐스팅한다.그 차이만큼 앞에 0
을 추가한다.문자열의 길이 + 2
만큼 출력할 문자열의 길이를 확보한다. 문자열의 길이 + 2
보다 width길이가 길다면 그만큼 더 확보
한다.0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.-플래그
유무에 따라 문자열을 앞 혹은 뒤에 넣는다. %c
에서 가변인자를 받아오는 부분
과 문자를 출력할 문자열에 삽입하는 과정을 %로 바꾸면
된다.// 정수
printf("%d\n", -20); // 부호 있는 10진 정수
printf("%i\n", -20); // 부호 있는 10진 정수
printf("%u\n", 10); // 부호 없는 10진 정수
printf("%x\n", 0xF1); // 부호 없는 16진 정수(소문자)
printf("%X\n", 0xF1); // 부호 없는 16진 정수(대문자)
// 문자, 문자열
printf("%c\n", 'a'); // 문자
printf("%s\n", "Hello world!"); // 문자열
// 포인터
int num 1;
void *ptr = &num1;
printf("%p\n", ptr) // 앞에 0x가 붙고, A~F는 소문자로 출력, 높은 자릿수의 0은 생략
// %기호
printf("%%\n"); // % 기호 출력
// 정수 서식 지정자 %d에 폭 지정
printf("%6d\n", 20); // %d의 출력 폭을 6칸으로 지정
// 20 4칸 공백
printf("%6d\n", 2000); // %d의 출력 폭을 6칸으로 지정
// 2000 2칸 공백
// 정수 서식 지정자 %d에 폭과 0플래그 사용
printf("%06d\n", 20); // 출력 폭을 6칸으로 지정, 남는 공간은 0으로 채움
//000020 0이 4개 채워짐
printf("%06d\n", 2000); // 출력 폭을 6칸으로 지정, 남는 공간은 0으로 채움
//002000 0이 2개 채워짐
// 정수 서식 지정자 %d에 폭과 ' ' 플래그, '+'플래그 사용
printf("% d\n", 10); // 양수일 떄는 부호를 출력하지 않고 공백으로 표시
// 10
printf("% d\n", -10); // 음수일 때는 - 부호를 출력
//-10
printf("%+d\n", 200); // 양수일 떄는 + 부호를 출력
//+200
printf("%+d\n", -200); // 음수일 떄는 - 부호를 출력
//-200
// 16진수를 출력시 '#' 플래그 사용
printf("%#x\n", 0xf1); // 16진수 소문자 출력이면 앞에 0x를 붙임
//0xf1
printf("%#X\n", 0xf1); // 16진수 대문자 출력이면 앞에 0X를 붙임
//0XF1
#ifndef FT_PRINTF_H
# define FT_PRINTF_H
# include "./libft/libft.h"
# include <stdarg.h>
# include <unistd.h>
# define SPECIFIERS "cspdiuxX%"
# define HEXLOW "0123456789abcdef"
# define HEXUP "0123456789ABCDEF"
typedef struct s_format
{
int minus;
int plus;
int width;
int precision;
int neg_prec;
int zero;
int dot;
int space;
int sharp;
int neg;
char specifier;
}t_format;
int ft_nbrlen(long n, int base);
int ft_printnstr(char *str, int n);
int ft_printnchar(int c, int n);
int ft_printchar(int c);
int ft_printf(const char *str, ...);
t_format ft_newformat(void);
int ft_print_format(t_format f, va_list ap);
int ft_print_c_pct(t_format f, va_list ap);
int ft_print_s(t_format f, va_list ap);
int ft_print_u(t_format f, va_list ap);
int ft_print_d_i(t_format f, va_list ap);
int ft_print_x(t_format f, va_list ap);
int ft_print_p(t_format f, va_list ap);
int ft_parse(char *str, va_list ap);
int ft_recursive_hex(t_format f, size_t n, size_t iteration);
char *ft_allocate(char *str, int len, unsigned int n);
char *ft_uitoa(unsigned int n);
char *ft_litoa(long long n);
#endif
#include "ft_printf.h"
int ft_printf(const char *str, ...)
{
int print_len;
va_list ap;
print_len = 0;
va_start(ap, str); // str을 ap에 저장하면서 시작
while (*str)
{
if (*str == '%') // %발견 시
{
if (*(++str)) // 다음 문자가 존재 시
print_len += ft_parse((char *)str, ap); // parsing
while (*str && !ft_strchr(SPECIFIERS, *str)) // 구분자가 나올 때 까지 str++
str++;
}
else
print_len += ft_printchar(*str); // 아니라면 그냥 바로 출력
if (*str)
str++;
}
va_end(ap); // ap를 끝냄
return (print_len); // 최종 출력한 길이 반환
}
int ft_print_format(t_format f, va_list ap)
{
int print_len;
print_len = 0;
if (f.specifier == 'c' || f.specifier == '%')
print_len = ft_print_c_pct(f, ap);
if (f.specifier == 's')
print_len = ft_print_s(f, ap);
if (f.specifier == 'u')
print_len = ft_print_u(f, ap);
if (f.specifier == 'd' || f.specifier == 'i')
print_len = ft_print_d_i(f, ap);
if (f.specifier == 'X' || f.specifier == 'x')
print_len = ft_print_x(f, ap);
if (f.specifier == 'p')
print_len = ft_print_p(f, ap);
return (print_len);
}
#include "ft_printf.h"
t_format ft_newformat(void)
{ // flag 초기화
t_format newformat;
newformat.minus = 0;
newformat.plus = 0;
newformat.width = 0;
newformat.precision = 0;
newformat.specifier = 0;
newformat.zero = 0;
newformat.dot = 0;
newformat.space = 0;
newformat.sharp = 0;
newformat.neg_prec = 0;
newformat.neg = 0;
return (newformat);
}
#include "ft_printf.h"
t_format ft_parse_bonus(char *str, t_format f)
{
while (*str != '.' && !ft_strchr(SPECIFIERS, *str))
{
if (*str == '+') // '+' 발견시
f.plus = 1; // '+' 플래그 설정
if (*str == ' ') // 공백 발견시
f.space = 1; // 공백 플래그 설정
if (*str == '#') // '#' 발견시
f.sharp = 1; // sharp 플래그 설정
str++;
}
return (f);
}
t_format ft_parse_width(char *str, va_list ap, t_format f)
{ // width 설정
int specified;
specified = 0;
while (*str != '.' && !ft_strchr(SPECIFIERS, *str)) // .이 아니고, 구분자가 없을 때
{
if (*str == '-') // '-'이 발견되었다면
f.minus = 1; // '-'플래그 설정
if (*str == '0' && !ft_isdigit(*(str - 1))) // '0'이 발견되고 그 앞에 정수가 없다면
f.zero = 1; // '0' 플래그 설정
else if (((*str > '0' && *str <= '9') || *str == '*') && !specified)
{ // 정수에 포함되는 값이거나 *이 등장하고 아직 width이 식별되지 않았을 떄
if (*str == '*') // '*'이 있다면
f.width = va_arg(ap, int); // 가변인자에서 width를 받아옴
else
f.width = ft_atoi(str); // width를 atoi로 정수화
specified = 1; // width가 식별됨을 의미
}
str++;
}
return (f);
}
t_format ft_parse_precision(char *str, va_list ap, t_format f)
{ // precision을 설정
int specified;
specified = 0;
while (!ft_strchr(SPECIFIERS, *str)) // 구분자가 발견되기 전까지
{
if ((ft_isdigit(*str) || *str == '*') && !specified) // 정수거나 '*'이라면
{
if (*str == '*') // '*'이 발견되었다면
f.precision = va_arg(ap, int); // 가변인자에서 precision을 받아옴
else
f.precision = ft_atoi(str); // precision을 atoi로 정수화
specified = 1; // precision이 발견되었음을 의미
}
str++;
}
return (f);
}
int ft_parse(char *str, va_list ap)
{
t_format new_format;
new_format = ft_parse_width(str, ap, ft_newformat()); // width를 parse
new_format = ft_parse_bonus(str, new_format); // bonus에 대한 플래그 설정
while (!ft_strchr(SPECIFIERS, *str) && *str != '.') // width와 precision을 구분하는 .이 없고, 구분자가 없다면
str++; // 문자 이동
if (*str == '.' && !new_format.specifier) // .이 발견되었고, 구분자가 없다면
{
new_format.dot = 1; // '.'의 유무 표시
new_format = ft_parse_precision(++str, ap, new_format); // precision을 지정
while (!ft_strchr(SPECIFIERS, *str)) // 구분자가 나올때 까지 문자 이동
str++;
}
if (new_format.width < 0) // width이 음수라면
{
new_format.minus = 1; // '-'플래그 설정
new_format.width *= -1; // width 양수화
}
new_format.specifier = *str; // 구분자 지정
new_format.neg_prec = new_format.precision < 0; // precision이 음수라면 neg_prec에 표시
return (ft_print_format(new_format, ap)); // 구분자에 따라서 출력
}
#include "ft_printf.h"
int ft_printchar(int c)
{
write(1, &c, 1); // 문자 1개를 출력 후 1 반환
return (1);
}
int ft_printnchar(int c, int n)
{
int print_len;
print_len = 0;
while (n-- > 0) // 문자 n개를 출력 후 출력된 문자의 개수 반환
print_len += (int)write(1, &c, 1);
return (print_len);
}
int ft_printnstr(char *str, int n)
{
if (str != NULL)
return ((int)write(1, str, n)); // 문자열 출력 후 문자열의 길이 반환
return (0);
}
int ft_print_c_pct(t_format f, va_list ap)
{
char c;
int print_len;
print_len = 0;
if (f.specifier == 'c') // 식별된 구분자가 'c'라면
c = va_arg(ap, int); // 가변인자에서 int형으로 받아옴
else // 아니라면
c = '%'; // %를 출력한다는 의미
f.precision = 1; // precision은 1로 고정
if (!f.minus && f.zero && f.width > f.precision) // -플래그가 없고 0플래그가 있다면
print_len += ft_printnchar('0', f.width - f.precision); // width - precision만큼 0출력
else if (!f.minus && f.width > f.precision) // -플래그와 0플래그가 없다면
print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
print_len += ft_printchar(c); // 문자를 출력
if (f.minus && f.width > f.precision) // -플래그가 있다면
print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
return (print_len); // 출력한 길이 반환
}
int ft_print_s(t_format f, va_list ap)
{
char *string;
int print_len;
print_len = 0;
string = va_arg(ap, char *); // 문자열을 char *형태로 가변인자에서 받아옴
if (!string) // string == 0일 경우
string = "(null)"; // "(null)"을 저장함
if (!f.dot || f.precision > (int)ft_strlen(string) || f.neg_prec) // 정밀도가 없거나, 정밀도가 음수거나, 문자열의 길이보다 작다면
f.precision = ft_strlen(string); // precision = 문자열의 길이
if (!f.minus && f.zero && f.width > f.precision) // -플래그가 없고, 0플래그가 있다면
print_len += ft_printnchar('0', f.width - f.precision); // width - precision만큼 0출력
else if (!f.minus && f.width > f.precision) // -플래그와 0플래그가 없다면
print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
print_len += ft_printnstr(string, f.precision); // 문자열 출력
if (f.minus && f.width > f.precision) // -플래그가 있다면
print_len += ft_printnchar(' ', f.width - f.precision); // width -precision만큼 공백 출력
return (print_len); // 출력한 길이 반환
}
#include "ft_printf.h"
char *ft_sharp(t_format f) // sharp에 관한 함수
{
if (f.specifier == 'X')
return ("0X");
return ("0x");
}
int ft_recursive_hex(t_format f, size_t n, size_t iteration)
{
int print_len;
int remainder;
char character;
print_len = 0;
if (n > 0 || (!iteration && !(f.specifier == 'p' && f.dot))) // n이 0일때, %p이고 정밀도가 0이라면 16진수 출력 x
{
remainder = n % 16; // 마지막에 출력할 문자 저장
if (f.specifier == 'X') // X라면 대문자 16진수 출력
character = HEXUP[remainder];
else // x or p라면 소문자 16진수 출력
character = HEXLOW[remainder];
n /= 16; // n /= 16
iteration = 1; // 넘어온 iteration이 0일 때, %p가 아니거나 정밀도가 0이 아니라면 이후에도 출력해야 하므로 iteration = 1로 둔다.
print_len += ft_recursive_hex(f, n, iteration); // 재귀를 통해 앞에서 부터 16진수 출력
print_len += ft_printchar(character); // 16진수 출력
}
return (print_len);
}
// 0플래그가 존재하고 정밀도가 존재할 때(0 포함) 공백 출력 후 0x출력
// 0플래그가 존재하고 정밀도가 존재하지 않을 경우 0x출력 후 0출력
// 0플래그가 존재하지 않고, -플래그가 존재하지 않을 때 공백 출력 후 0x출력
// -플래그가 존재할 때 0x출력 후 16진수 출력 후 공백 출력
int ft_print_hex(t_format f, unsigned long n, int len)
{
int print_len;
print_len = 0;
if (f.zero) // 0플래그 존재하고 정밀도가 존재하지 않을 떄 #플래그 일 경우 0x 먼저 출력
print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && !f.dot && n));
if (!f.minus && f.zero && f.width > f.precision && (!f.dot || f.neg_prec))
print_len += ft_printnchar('0', f.width - f.precision);
else if (!f.minus && f.width > f.precision) // -플래그, 0플래그가 없을 떄
print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && f.dot && n)); // 0플래그가 존재하고 정밀도가 존재할 때 공백 출력 후 0x출력
if (!f.zero) // 0플래그가 존재하지 않고, 정밀도가 존재하지 않을 때 공백 출력 후 0x출력
print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && n && !f.dot));
print_len += ft_printnchar('0', f.precision - len); // precision - len만큼 0출력 precision > len 일 경우
if (len) // len이 존재한다면
print_len += ft_recursive_hex(f, n, n); // 재귀를 통한 16진수 출력
if (f.minus && f.width > f.precision) // -플래그가 존재할 경우
print_len += ft_printnchar(' ', f.width - f.precision); // 16진수 출력 후 공백 출력
return (print_len); // 출력한 길이 반환
}
int ft_print_x(t_format f, va_list ap)
{
int print_len;
unsigned int n;
int len;
print_len = 0;
n = va_arg(ap, unsigned int); // unsigned int 형으로 가변인자에서 받아옴
len = ft_nbrlen(n, 16); // len의 길이 지정
if (!n && (!f.precision && f.dot)) // n이 0이거나 정밀도가 0이라면
len = 0; // len = 0
if (f.neg_prec || f.precision < len || !f.dot) // 정밀도가 없거나, 정밀도가 음수거나, 정밀도가 len보다 작다면
f.precision = len; // precision = len
if (f.sharp && n) // n이 존재하고 sharp플래그가 있다면
f.width -= 2; // 0x or 0X의 공간만큼 width에서 뺌
print_len += ft_print_hex(f, n, len); // 16진수 출력
return (print_len); // 출력한 길이 반환
}
#include "ft_printf.h"
char oper(t_format f) // 음수라면 -를 반환, +플래그 존재시 +반환
{
if (f.plus)
return ('+');
return ('-');
}
int ft_print_nbr(t_format f, char *nbr, int len)
{
int print_len;
print_len = 0;
f.width -= (f.space && !f.neg && !f.plus && f.width);
// space플래그가 있을때, 음수가 아니고, +플래그가 없다면 width보다 1만큼 작게 나옴
if (f.neg || f.plus) // 음수거나 +플래그가 있는데 0플래그가 있고, 정밀도가 없거나 음수라면 바로 부호 출력
print_len += ft_printnchar(oper(f), f.zero && (!f.dot || f.neg_prec));
else if (f.space) // 공백 플래그가 있고, 정밀도가 없으며 0플래그가 존재한다면 공백 출력 -> 양수에 대해 + 플래그가 없을 떄
print_len += ft_printnchar(' ', f.zero && !f.dot);
if (!f.minus && f.zero && f.width > f.precision && (!f.dot || f.neg_prec)) // 0플래그가 존재하고 정밀도가 없거나 음수라면 0출력
print_len += ft_printnchar('0', f.width - f.precision - f.neg - f.plus);
else if (!f.minus && f.width > f.precision) // 0플래그, -플래그가 없다면 공백 출력
print_len += ft_printnchar(' ', f.width - f.precision - f.neg - f.plus);
if (f.neg || f.plus) // 음수거나 +플래그가 있는데 0플래그가 없거나 양수인 정밀도가 존재한다면 0이나 공백 후 부호 출력
print_len += ft_printnchar(oper(f), !f.zero || (f.dot && !f.neg_prec));
else if (f.space) // 공백 플래그가 존재하고 0플래그가 없거나 정밀도가 존재한다면 공백 출력
print_len += ft_printnchar(' ', !f.zero || f.dot);
print_len += ft_printnchar('0', f.precision - len); // precision - len만큼 0출력
print_len += write(1, nbr, len); // nbr출력
if (f.minus && f.width > f.precision) // -플래그가 존재한다면 숫자 출력 후 공백 출력
print_len += ft_printnchar(' ', f.width - f.precision - f.neg - f.plus);
return (print_len); // 출력한 길이 반환
}
int ft_print_u(t_format f, va_list ap)
{
char *nbr;
unsigned int n;
int print_len;
int len;
print_len = 0;
n = va_arg(ap, unsigned int); // unsigned int형을 가변인자로 받아옴
f.plus = 0; // %u는 plus플래그가 없다
nbr = ft_uitoa(n); // uitoa함수를 통해서 nbr에 문자화
len = ft_strlen(nbr); // nbr의 길이를 구함
if (*nbr == '0' && (!f.precision && f.dot)) // 정밀도가 0이고, nbr이 '0'일 경우
len = 0; // len = 0
if (f.precision < len || !f.dot) // 정밀도가 존재하지 않거나 정밀도 < len일 경우
f.precision = len; // 정밀도 = len
print_len += ft_print_nbr(f, nbr, len); // 플래그에 따라 출력
free(nbr); // itoa를 통한 동적할당이 있었으므로 해제
return (print_len); // 출력한 길이 반환
}
int ft_print_d_i(t_format f, va_list ap)
{
char *nbr;
long long n;
int print_len;
int len;
print_len = 0;
n = va_arg(ap, int); // int형을 가변인자로 받아옴
if (n < 0) // n이 음수일 경우
{
f.neg = 1; // 음수 플래그에 표시
f.plus = 0; // 음수 이므로 plus플래그는 존재하지 않음
n *= -1; // 음수 부호는 따로 찍을 예정이므로 양수화
}
nbr = ft_litoa(n); // litoa함수를 통해서 longlong 형태로 itoa사용할 수 있도록 함
len = ft_strlen(nbr); // nbr의 길이 반환
if (*nbr == '0' && (!f.precision && f.dot)) // 정밀도가 0이고, nbr이 '0'일 경우
len = 0; // len = 0
if (f.precision < len || !f.dot) // 정밀도가 없거나 len보다 작을 경우
f.precision = len; // 정밀도 = len
print_len += ft_print_nbr(f, nbr, len); // 플래그에 따라서 출력
free(nbr); // itoa를 통한 동적할당이 있었으므로 해제
return (print_len); // 출력한 길이 반환
}
#include "ft_printf.h"
int count_pnt(t_format f, size_t n, size_t iteration)
{
int pnt_len;
pnt_len = 1;
if (n > 0 || !(iteration || f.dot)) // &p일 때 n이 0이고 정밀도가 있다면 출력x임
{
if (n < 16) //n이 16보다 작다면
return (1); // 길이 1
n /= 16;
iteration = 1;
pnt_len += count_pnt(f, n, iteration); // 재귀를 통한 길이 구하기
}
return (pnt_len);
}
int ft_print_p(t_format f, va_list ap)
{
int print_len;
size_t n;
int len;
print_len = 0;
n = (size_t)va_arg(ap, void *); // void * 형으로 가변인자에서 가져온 뒤 size_t(unsigned long)으로 캐스팅
len = count_pnt(f, n, n); // 포인터의 길이 구하기
if (!n && (!f.precision && f.dot)) // n이 0이고, 정밀도가 0이라면
len = 0; // len - 0
if (f.precision < len || !f.dot) // 정밀도가 없거나 len보다 작다면
f.precision = len; // 정밀도 = len
f.width -= 2; // 0x출력할 만큼 width 감소
print_len += write(1, "0x", 2 * f.zero); // 0플래그가 있는 경우 0x먼저 출력
if (!f.minus && f.width > f.precision && !f.dot && f.zero)
print_len += ft_printnchar('0', (f.width - f.precision));
else if (!f.minus && f.width > f.precision)
print_len += ft_printnchar(' ', (f.width - f.precision));
print_len += write(1, "0x", 2 * !f.zero); // 0플래그가 없을경우 공백 이후 출력
print_len += ft_printnchar('0', (f.precision - len) * (n != 0)); // n이 0이 아닐 때 precision - len 만큼 0출력
print_len += ft_printnchar('0', f.precision * (f.dot && !n));
if (len) // len이 존재한다면
print_len += ft_recursive_hex(f, n, n); // 16진수 출력
if (f.minus && f.width > f.precision)
print_len += ft_printnchar(' ', f.width - f.precision); // '-'플래그 존재시 16진수 출력 이후 공백 출력
return (print_len);
}
#include "ft_printf.h"
int ft_nbrlen(long n, int base)
{
int len;
len = 0;
if (!base)
base = 10;
if (n == 0)
return (1);
while (n != 0)
{
len++;
n /= base;
}
return (len);
}
char *ft_uitoa(unsigned int n)
{
char *str;
int len;
len = ft_nbrlen(n, 10);
str = (char *)malloc(sizeof(char) * (len + 1));
if (!str)
return (0);
str[len] = '\0';
while (len-- > 0)
{
str[len] = (n % 10) + '0';
n /= 10;
}
return (str);
}
int count_ll(long long n)
{
int cnt_nbr;
if (n == 0)
return (1);
cnt_nbr = 0;
if (n < 0)
cnt_nbr = 1;
while (n)
{
n /= 10;
cnt_nbr++;
}
return (cnt_nbr);
}
char *ft_litoa(long long n)
{
long long nbr;
char *res;
int len;
len = count_ll((long long)n);
nbr = n;
if (n < 0)
nbr *= -1;
res = (char *)malloc(sizeof(char) * (len + 1));
if (!res)
return (NULL);
res[len--] = '\0';
while (len >= 0)
{
res[len] = nbr % 10 + '0';
nbr /= 10;
len--;
}
if (n < 0)
res[0] = '-';
return (res);
}
NAME = libftprintf.a
LIBFT = libft
LIBFT_LIB = libft.a
SRCS = ./ft_printf.c ./ft_format.c ./ft_parse.c ./ft_print_chars.c ./ft_print_hex.c ./ft_print_nbrs.c
OBJS = $(SRCS:.c=.o)
INCS = .
AR = ar rcs
CC = cc
CFLAGS = -Wall -Wextra -Werror
RM = rm -rf
.c.o:
$(CC) $(CFLAGS) -c $< -o $(<:.c=.o) -I$(INCS)
$(NAME): $(OBJS)
make all -C $(LIBFT)/
cp $(LIBFT)/$(LIBFT_LIB) $(NAME)
$(AR) $(NAME) $(OBJS)
all: $(NAME)
fclean: clean
$(RM) $(NAME)
make fclean -C $(LIBFT)
clean:
$(RM) $(OBJS)
make clean -C $(LIBFT)
re: fclean all
.PHONY: all clean fclean re