实现简易版的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);
}

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

输出字符串
字符串是以'\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");
}
