张银峰的编程课堂

实现简易版的printf函数

现在,你可以在stdio.h中查看一下printf函数的声明,你会发现,它也是一个使用可变参数的函数。做为可变参数函数的最后一讲,我们将会编写一个类似的my_printf函数。

既然是自己编写,那就表明不能在my_printf函数中使用printf函数了。这样,对于每种类型的数据输出,都需要我们自己实现,这主要是通过putchar函数实现,它的作用是向终端输出一个字符,先看看int型的输出。

输出整数

#include <stdio.h>
#include <math.h>
#include <limits.h>

void print_int_recursion(int n)
{
    if (n / 10)
    {
        print_int_recursion(n / 10);
    }

    putchar(abs(n % 10) + '0');
}

void print_int(int n)
{
    if (n < 0)
    {
        putchar('-');
    }

    print_int_recursion(n);
}

int main()
{
    printf("\t\t INT_MIN \t INT_MAX\n");
    printf("printf \t\t %d \t %d\n", INT_MIN, INT_MAX);

    printf("print_int \t ");
    print_int(INT_MIN);
    printf(" \t ");
    print_int(INT_MAX);
}

glimix.com

print_int()先判断当前值是否为负数,并输出负号;然后通过递归调用print_int_recursion()实现数值从高位到低位的打印。

以打印数值123为例,整个递归的调用及返回堆栈如下。

call #1 print_int(123)
ret  #4
call #2     print_int_recursion(123)
ret  #3     putchar('3')
call #3         print_int_recursion(12)
ret  #2         putchar('2')
call #4             print_int_recursion(1)
ret  #1             putchar('1')

当然,这也可以通过非递归方式实现,原理是通过求余运算,将数值倒序存储在数组中,再逆序打印出来即可。

void print_int(int n)
{
    if (n == 0)
    {
        putchar('0');
        return;
    }

    if (n < 0)
        putchar('-');

    char buf[64];
    int len = 0;

    while (n != 0)
    {
        buf[len++] = abs(n % 10) + '0';
        n /= 10;
    }

    while (--len >= 0)
        putchar(buf[len]);
}

作为练习,下面这个版本的递归打印实现如何?

void print_int(int n)
{
    if (n < 0)
    {
        putchar('-');
        n = -n;
    }

    if (n / 10)
        print_int(n / 10);

    putchar(n % 10 + '0');
}

输出浮点数

这里我们仅处理单精度浮点数,采用的方式是把小数点的两部分按整数分别输出。

void print_float(float f)
{
    int   n1, n2;
    float f2;

    // 保存整数部分(带符号)
    n1 = (int)f;

    // 保留6位有效小数部分
    f2 = (f - n1) * 1000000.0f;

    // 去掉负号
    if (f2 < 0)
        f2 = -f2;

    // 四舍五入
    f2 += 0.5f;

    // 用整形保存小数部分
    n2 = (int)f2;

    print_int(n1);
    putchar('.');
    print_int(n2);
}

int main()
{
    printf("printf \t\t %f \t %f\n", 3.1415926f, -2.598746f);

    printf("print_float\t ");
    print_float(3.1415926f);
    printf("\t ");
    print_float(-2.598746f);
}

glimix.com

输出字符串

字符串是以'\0'结尾的,可以使用按长度迭代或指针算术迭代实现。

void print_string(const char *str)
{
    for (int i = 0, len = strlen(str); i < len; i++)
        putchar(str[i]);
}

void print_string(const char *str)
{
    while (*str)
        putchar(*str++);
}

实现my_printf

在实现my_printf函数时,我们保持与C语言输出格式控制符一致,剩下的就是与va相关的宏的使用了。

void my_printf(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);

    bool escape = false;

    while (*format)
    {
        if (escape)
        {
            switch (*format)
            {
            case 'd':
                print_int(va_arg(ap, int));
                break;

            case 'f':
                print_float((float)va_arg(ap, double)); // 注意这里是double
                break;

            case 'c':
                putchar(va_arg(ap, char));
                break;

            case 's':
                print_string(va_arg(ap, const char *));
                break;

            default:
                putchar(*format);
                break;
            }

            escape = false;
        }
        else
        {
            if (*format == '%')
            {
                escape = true;
            }
            else
            {
                putchar(*format);
            }
        }

        ++format;
    }

    va_end(ap);
}

int main()
{
    printf("string 1: %%%%%d", 235);
    putchar('\n');
    my_printf("string 1: %%%%%d", 235);
    putchar('\n');

    printf("\n");
    printf("   printf: %d, %f, %c, %s", -219582, 3.1415926777f, 'f', "glimix.com");
    putchar('\n');
    my_printf("my_printf: %d, %f, %c, %s", -219582, 3.1415926777f, 'f', "glimix.com");
}

glimix.com