Comprensione di printf () con numeri interi

Ho una domanda su come il metodo printf() stampa interi, firmati o non firmati. Un giorno, mi sono trovato a pensare a quanto sia difficile convertire una sequenza binaria in una sequenza di cifre decimali che un essere umano possa capire, dato che un computer non ha alcun concetto di decimale.

Sotto, ho un metodo printf() (da qui ) con i suoi metodi associati. Ho cercato di capire il più ansible su come funziona printi() , come puoi vedere nei commenti:

 #define PAD_RIGHT 1 #define PAD_ZERO 2 #include  static void printchar(char **str, int c) { extern int putchar(int c); if (str) { **str = c; ++(*str); } else (void)putchar(c); } static int prints(char **out, const char *string, int width, int pad) { register int pc = 0, padchar = ' '; if (width > 0) { register int len = 0; register const char *ptr; for (ptr = string; *ptr; ++ptr) ++len; if (len >= width) width = 0; else width -= len; if (pad & PAD_ZERO) padchar = '0'; } if (!(pad & PAD_RIGHT)) { for ( ; width > 0; --width) { printchar (out, padchar); ++pc; } } for ( ; *string ; ++string) { printchar (out, *string); ++pc; } for ( ; width > 0; --width) { printchar (out, padchar); ++pc; } return pc; } /* the following should be enough for 32 bit int */ #define PRINT_BUF_LEN 12 static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase) { /* i is the number we are turning into a string b is the base, ie base 10 for decimal sg is if the number is signed, ie 1 for signed (%d), 0 for unsigned (%u) By default, width and pad are 0, letbase is 97 */ char print_buf[PRINT_BUF_LEN]; register char *s; register int t, neg = 0, pc = 0; register unsigned int u = i; if (i == 0) { print_buf[0] = '0'; print_buf[1] = '\0'; return prints(out, print_buf, width, pad); } if (sg && b == 10 && i = 10) t += letbase - '0' - 10; *--s = t + '0'; u /= b; } if (neg) { if (width && (pad & PAD_ZERO)) { printchar(out, '-'); ++pc; --width; } else *--s = '-'; } return pc + prints(out, s, width, pad); } static int print(char** out, const char* format, va_list args) { register int width, pad; register int pc = 0; char scr[2]; for (; *format != 0; ++format) { if (*format == '%') { ++format; width = pad = 0; if (*format == '\0') break; if (*format == '%') goto out; if (*format == '-') { ++format; pad = PAD_RIGHT; } while (*format == '0') { ++format; pad |= PAD_ZERO; } for (; *format >= '0' && *format <= '9'; ++format) { width *= 10; width += *format - '0'; } if (*format == 's') { register char* s = (char*) va_arg(args, int); pc += prints(out, s ? s : "(null)", width, pad); continue; } if (*format == 'd') { pc += printi(out, va_arg(args, int), 10, 1, width, pad, 'a'); continue; } if (*format == 'x') { pc += printi(out, va_arg(args, int), 16, 0, width, pad, 'a'); continue; } if (*format == 'X') { pc += printi(out, va_arg(args, int), 16, 0, width, pad, 'A'); continue; } if (*format == 'u') { pc += printi(out, va_arg(args, int), 10, 0, width, pad, 'a'); continue; } if (*format == 'c') { /* char are converted to int then pushed on the stack */ scr[0] = (char) va_arg(args, int); scr[1] = '\0'; pc += prints(out, scr, width, pad); continue; } } else { out: printchar (out, *format); ++pc; } } if (out) **out = '\0'; va_end(args); return pc; } int printf(const char *format, ...) { va_list args; va_start( args, format ); return print( 0, format, args ); } 

Se c’è una cosa che odio nel leggere il codice sorgente della libreria, è che non è quasi mai leggibile. I nomi variabili con un carattere e nessun commento per spiegarli sono un dolore.

Puoi spiegare, in modo semplice, che cosa sta facendo esattamente il metodo per convertire un intero in una stringa di cifre decimali?

Il codice che hai incollato non è difficile da leggere. Sospetto che tu abbia rinunciato presto.

Ignorando il potenziale di un numero negativo per un momento, questa routine printi() :

  • crea un buffer per stampare il numero in, 12 caratteri di larghezza
  • imposta un puntatore di caratteri per puntare alla fine di quel buffer ** NULL – lo interrompe, quindi sposta il puntatore di un carattere sulla “sinistra”

Quindi la routine entra in un ciclo, fino a quando il numero rimane> 0

  • MOD per 10 (cioè, dividi per 10 e prendi il resto)
    • questa diventa la cifra a cui sta puntando, quindi la rappresentazione ASCII viene messa lì
    • s viene spostato di nuovo a sinistra
  • imposta il numero su se stesso / 10; questo rimuove la cifra appena stampata
  • ripetere il ciclo finché ci sono più cifre da stampare

L’unica cosa complicata qui è trattare i numeri negativi, ma se capisci come vengono memorizzati i numeri negativi, non è affatto complicato.

Forse ho guardato le intestazioni delle librerie dei template troppo a lungo, ma quel codice di libreria mi sembra abbastanza leggibile!

Spiegherò il ciclo principale, dal momento che il resto (la giocoleria del cartello ecc.) Dovrebbe essere abbastanza facile da capire.

 while (u) { t = u % b; if (t >= 10) t += letbase - '0' - 10; *--s = t + '0'; u /= b; } 

Fondamentalmente quello che stiamo facendo è estrarre cifre una alla volta, da destra a sinistra . Supponiamo che b == 10 (cioè il solito caso di %d o %u ). L’operatore % , chiamato operatore modulo, calcola il resto rimasto dopo la divisione intero. La prima volta che la riga t = u % b; gira, calcola la cifra più a destra della stringa di output – ciò che rimane come resto dopo aver diviso il numero u per 10. (Supponiamo che il numero u fosse 493: il resto dopo aver diviso per 10 è 3, la cifra più a destra. )

Dopo aver estratto questa cifra più a destra in t , l’istruzione if decide cosa “chiamare” questa cifra se è 10 o più grande. Questa correzione equivale a regolare t modo che, quando ‘0’ (il valore ASCII della cifra ‘0’, che è 48) sia aggiunto nella riga successiva, il risultato sarà una lettera che inizia con ‘a’ o ‘A’ ( per produrre cifre esadecimali e altre cifre per basi maggiori di 10).

La riga dopo scrive la cifra nel buffer. Va nel carattere più a destra del buffer print_buf (nota come s è inizializzato in precedenza per puntare alla fine di questo buffer, non l’avvio come di solito è il caso). Successivamente il puntatore s viene spostato di un carattere a sinistra in preparazione del carattere successivo.

La seguente riga, u /= b , semplicemente divide u per 10, scartando efficacemente la cifra più a destra. (Funziona perché la divisione integer non produce mai frazioni e arrotonda sempre verso il basso.) Questo apre quindi la seconda cifra più a destra per la successiva iterazione del ciclo da elaborare. Risciacquare, ripetere. Il ciclo si interrompe infine quando non è rimasto nulla (la condizione while (u) è equivalente alla condizione while (u != 0) ).

Il metodo per convertire un intero positivo I in base 10 è fondamentalmente:

 if (i == 0) printf("0"); else while (i != 0) { unsigned int j = i / 10; unsigned int digit = i - 10 * j; printf("%c", digit + '0'); i = j; } 

Tranne che questo stampa il numero indietro.