0

printf()メソッドが符号付きまたは符号なしの整数をどのように出力するかについて質問があります。ある日、コンピューターには 10 進数の概念がないことを考えると、2 進数のシーケンスを人間が理解できる 10 進数のシーケンスに変換するのはどれほど難しいに違いないか考えていました。

以下に、関連付けられたメソッドを含むprintf()メソッド (ここから) があります。printi()コメントでわかるように、どのように機能するかについてできる限り理解しようとしました。

#define PAD_RIGHT 1
#define PAD_ZERO 2

#include <stdarg.h>

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, i.e. base 10 for decimal
        sg is if the number is signed, i.e. 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 < 0)
    {
        neg = 1;
        u = -i;
    }

    s = print_buf + PRINT_BUF_LEN - 1;
    *s = '\0';

    while (u)
    {
        t = u % b;

        if (t >= 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 );
}

ライブラリのソース コードを読むときに私が嫌いなことが 1 つあるとすれば、それはほとんど読めないことです。変数名が 1 文字で、説明するコメントがないのは面倒です。

簡単な方法で、整数を 10 進数の文字列に変換するメソッドが正確に何をしているのか説明していただけますか?

4

3 に答える 3

2

貼り付けたコードは読みにくいものではありません。早々に諦めてしまったのではないでしょうか。

負の数の可能性を一瞬無視すると、このprinti()ルーチンは次のようになります。

  • 12 文字幅の数値を出力するためのバッファを作成します
  • そのバッファの末尾を指すように文字ポインタを設定しsます** NULL で終了し、ポインタを「左」に 1 文字移動します

次に、数値が > 0 である限り、ルーチンはループに入ります。

  • MOD を 10 で割る (つまり、10 で割って余りを取る)
    • sこれが指している数字になるため、ASCII表現がそこに配置されます
    • s再び左に移動
  • 数値をそれ自体 / 10 に設定します。これにより、印刷されたばかりの数字が削除されます
  • 印刷する桁がさらにある限り、ループを繰り返します

ここで唯一難しいのは負の数の処理ですが、負の数がどのように格納されるかを理解していれば、それほど難しいことではありません。

于 2012-12-14T12:07:02.093 に答える
1

テンプレート ライブラリのヘッダーを長い間見つめてきたのかもしれませんが、そのライブラリ コードはかなり読みやすいように見えます。

残りの部分 (標識をジャグリングするなど) は簡単に理解できるはずなので、メイン ループについて説明します。

while (u)
{
    t = u % b;

    if (t >= 10)
        t += letbase - '0' - 10;

    *--s = t + '0';
    u /= b;
}

基本的には、右から左に 1 つずつ数字を抽出しています。仮定します(つまり、 orb == 10の通常の場合)。モジュロ演算子と呼ばれる演算子は、整数除算の余りを計算します。行が最初に実行されると、出力文字列の右端の桁が計算されます。これは、数値を 10で割った後の剰余として残されるものです。一番右の桁。)%d%u%t = u % b;uu

この右端の数字を に抽出した後t、このifステートメントは、この数字が 10 以上の場合に何を「呼び出す」かを決定します。この修正tは、'0' (数字の '0' のASCII 値で、48) が次の行に追加されると、結果が 'a' または 'A' で始まる文字になるように調整することになります。 10 より大きい基数の 16 進数とその他の数字を生成します)。

その後の行は、数字をバッファに書き込みます。バッファの右端の文字に入ります (通常のようにバッファの先頭ではなく、このバッファの末尾を指すように が初期化されていることにprint_buf注意してください)。続いて、次の文字に備えてポインタを 1 文字左に移動します。ss

次の行 はu /= b、単純uに 10 で割り、右端の数字を効果的に破棄します。(これは、整数の除算では分数が生成されず、常に切り捨てられるため機能します。) これにより、次のループ反復処理のために右から 2 番目の桁が開かれます。すすぎ、繰り返します。何も残っていない場合、ループは最終的に停止します (条件while (u)は条件と同等ですwhile (u != 0))。

于 2012-12-14T12:05:45.340 に答える
0

正の整数Iを基数10に変換する方法は、基本的に次のとおりです。

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;
}

これが番号を逆方向に出力することを除いて。

于 2012-12-14T12:04:28.697 に答える