У попередній частині ми створили основу для власної імплементації функції 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).
Повний код можна знайти за посиланням.
Дякую за те, що дочитали до кінця.