
У попередній частині ми створили основу для власної імплементації функції printf, використовуючи функцію write з бібліотеки unistd.h. Зараз ми спробуємо реалізувати обробку прапорців %i та %d, які використовуються для виводу значень типу int. Ці прапорці мають однакову поведінку в функції printf, а відмінність між ними проявляється лише при використанні їх у функції scanf (детальніше).
Почнемо з простого і використаємо функцію write для виводу значення змінної типу int:
#include <unistd.h>
int main(void){
	int a  = 123;
	write(1, &a, sizeof(a));
	return 0;
}Скомпілюємо і отримаємо:
[cr1m3s@pc:~]$ gcc foo.c
[cr1m3s@pc:~]$ ./a.out
{Ми отримали трохи дивний результат "{". Це не є помилкою функції і не просто випадковим значенням з пам'яті. Давайте відкриємо таблицю ASCII і перевіримо:
│ Oct   Dec   Hex   Char
...
│ 173   123   7B    {Отже, просто так вивести значення типу int у нас не вийде. Для обробки цілочисельних значень нам потрібно написати власний конвертор типу int до типу char або char*.
Давайте почнемо з простого перетворення числа на літеру. Знову звернемось до таблиці ASCII:
       Oct   Dec   Hex   Char
       060   48    30    0                   
       061   49    31    1                       
       062   50    32    2                       
       063   51    33    3                      
       064   52    34    4                     
       065   53    35    5                       
       066   54    36    6                     
       067   55    37    7                      
       070   56    38    8                       
       071   57    39    9   Тобто щоб вивести числа нам потрібні значення int починаючи від 48 до 57.
#include <unistd.h>
int main(void){
	int a  = 48;
	while (a < 58) {
		write(1, &a, sizeof(a));
		a++;
	}
	return 0;
}Після компіляції і виконання отримаємо:
[cr1m3s@pc:~]$ gcc foo.c
[cr1m3s@pc:~]$ ./a.out
0123456789Отже, щоб отримати представлення числа типу int у форматі char, достатньо до значення int додати 48. Напишемо просту функцію для цього:
int write_int(int a){
	if (a < 0 || a > 9)
		return -1;
	// тип char в С зберігає значення в діапазоні від -128 до 127
        // тому ми можемо проводити математичні операції між int та char
        a += '0';        
        write(1, &a, sizeof(a));
	return 0;
}Це непоганий початок, але таким чином ми можемо виводити лише числа в діапазоні від 0 до 9. Для перетворення більших (або менших:)) чисел нам потрібна значно складніша функція.
Давайте почнемо з визначення довжини кінцевої стрічки, що буде представляти число. Для цього напишемо функцію, яка визначатиме кількість символів у змінній типу int.
int int_len(int a){
	int result = 0;
	int n = a;
	if (n == 0)
		return 1;
	if (n < 0)
		result++;
	while (n != 0){
		result++;
		n /= 10;
	}
	return result;
}Знаючи кількість символів в цілочисленній змінній, ми можемо створити стрічку використовуючи malloc (не забуважємо підключити бібліотеку stdlib.h).
char* int_toa(int a){
	int num;
	int sign;
	int i_len;
	char* result;
	i_len = int_len(a);
        // не забуваємо про '\0' на кінці стрічки
	result = malloc((i_len + 1) * sizeof(char));
        // sign виступає як костиль
        // можна було б просто помножити негативне значення на -1
        // але ми можемо прийти до переповнення
        // INT_MAX --> 2147483647
        // INT_MIN --> -2147483648
        // INT_MIN * -1 > INT_MAX
	sign = 1;
	num = a;
	if (!result)
		return NULL;
	if (num < 0)
		sign = -1;
        // починаємо з кінця
	result[i_len] = '\0';
	while (--i_len >= 0){
                // використовуємо sign щоб отримувати коректне значення
                // тим самим уникаємо використання abs 
		result[i_len] = (num % 10) * sign + '0';
		num /= 10;
	}
	if (sign == -1)
		result[0] = '-';
	return result;
}Тепер потрібно написати функцію котра буде власно друкувати конвертоване значення у вигляді стрічки і повертати кількість надрукованих символів.
int printdecimal(int n)
{
	char *number;
	int result;
	number = int_toa(n);
        // використаємо функцію printstring написану в минулій частині
	result = printstring(number);
        // не забуваємо звільнити пам'ять після використання
	free(number);
	return (result);
}
Додамо нові прапорці до функції handle_flag:
int handle_flag(va_list args, char flag)
{
        if (flag == 'd' || flag == 'i')
                return (printdecimal(va_arg(args, int)));
  	if (flag == 'c')
		return (printchar((char)va_arg(args, int)));
	if (flag == 's')
		return (printstring(va_arg(args, char *)));
	if (flag == '%')
		return (printchar('%'));
	return (0);
}
Напишемо невеличкий тест:
int main() {
    char * str = "%cello %s!%%\n";
    myprintf(str, 'H', "world");
 
    int a = myprintf("%d", -2147483648);
    myprintf("\n");
    int b = myprintf("%d", 0);
    myprintf("\n");
    int c = myprintf("%d", 2147483647);
    myprintf("\n");
 
    myprintf("%i\n", a);
    myprintf("%i\n", b);
    myprintf("%i\n", c);
    
    return 0;
}Отримаємо:
Hello world!%
-2147483648
0
2147483647
11
1
10
[Execution complete with exit code 0]Код успішно компілюється, виводить потрібні значення і навіть повертає 0, незважаючи на спроби маніпулювати пам'яттю вручну з використанням malloc і free. Тепер наша функція може обробляти 5 з 9 прапорців. В останній частині розглянемо обробку 16-річних чисел (%x, %X), позитивних цілих чисел (%u) і найцікавіше - вказівників (%p).
Повний код можна знайти за посиланням.
Дякую за те, що дочитали до кінця.