printf函数,一个精简版的实现(putchar)

今天学到了两个技能,其一为使用递归将整数以字符串的形式打印,其二为va_list实现变长参函数。马上综合这两个小技术,实现了一个精简版的printf函数。之所以为精简版,是因为支持功能有限,同时依赖于putchar函数。

  1. 将整数以字符串形式打印
    由于整数的长度一般只有11位左右,使用递归来打印很适合。于是设计了一个__number_v2函数,专门负责将整数以字符串形式打印。
    这里注意,对于浮点型来说,可以先打印整数部分,然后打印小数点,接着将小数部分乘以1e6(这里限定了保留多少位有效数字)这样就又转化为整数了,继续以__number_v2函数打印。

  2. var_arg实现变参函数
    以下是在Windows 10中的Visual Studio 2015上编译通过。基本框架就是首先声明一个va_list,接着使用一对__crt_va_start__crt_va_end来设定范围,使用__crt_va_arg来解析变参。这时候要注意,

    • 其一在__crt_va_start时,需要一个初始化工作。
    • 其二就是使用__crt_va_arg提取参数时需要指定参数类型。

这里使用__char_v2putchar进行封装,是为了统计printfv2所打印的总长度,也就是为获取ans的值。

static int __char_v2(char c, int* count) {
putchar(c);
return ++(*count);
}

static void __number_v2(unsigned int number, int* count) {
unsigned int quotient = number / 10;
if (quotient != 0) {
__number_v2(quotient, count);
}
__char_v2(number % 10 + '0', count);
}
int printfv2(const char* format, ...) {
va_list var_arg;
int ans = 0, tmp_int = 0;
double tmp_double = 0;
char* tmp_charptr = '\0';
char c = '\0';

if (!format) return ans;
__crt_va_start(var_arg, format);
while ((c = *format) != '\0') {
if (c == '%') {
switch (*(++format))
{
case 'f':
tmp_double = __crt_va_arg(var_arg, double);
if (tmp_double < 0) {
__char_v2('-', &ans), tmp_double = -tmp_double;
}
__number_v2((int)tmp_double, &ans);
__char_v2('.', &ans);
//6 digits after the decimal point is reserved
__number_v2((int)((tmp_double - (int)tmp_double)*1e6), &ans);
break;
case 'd':
tmp_int = __crt_va_arg(var_arg, int);
if (tmp_int < 0) {
__char_v2('-', &ans), tmp_int = -tmp_int;
}
__number_v2(tmp_int, &ans);
break;
case 's':
tmp_charptr = __crt_va_arg(var_arg, char*);
while (*tmp_charptr != '\0') {
__char_v2(*(tmp_charptr++), &ans);
}
break;
case 'c':
__char_v2(__crt_va_arg(var_arg, char), &ans);
break;
default:
__char_v2(c, &ans), __char_v2(*format, &ans);
break;
}
++format;
}
else {
__char_v2(*(format++), &ans);
}
}
__crt_va_end(var_arg);
return ans;
}
int main() {
int ans = 0;

ans = printfv2("My name is %s, I am %d years old, %fkg in the title, I like the letter %c.\n", "DUJUN QING", 18, 56.789, 'X');
printfv2("Length is %d.\n", ans);
printfv2("There is a negative number (%f) here, and a percent sign %.\n", -12.3);

return 0;
}

输出内容,

My name is DUJUN QING, I am 18 years old, 56.789000kg in the title, I like the letter X.
Length is 89.
There is a negative number (-12.300000) here, and a percent sign %.

Reference:
[1] 《C和指针》 徐波译