C语言printft函数设计与实现
我们编写C语言代码时经常会使用printf
函数,但是这个函数不智能,不能自动推导数据类型,我们必须使用%s
, %d
等符号手动指定数据类型:
const char* name = "Bob";
int age = 31;
printf("I'm %s, I'm %d years old.\n", name, age);
以上方法不仅麻烦而且不直观,我们能不能实现一个更加智能的printf
函数呢?如果可以将类型推导的工作交给编译器就好了!
根据使用C++的经验,我立马想到C++的std::format
(C++20)可以做到下面这种写法:
std::cout << std::format("I'm {}, I'm {} years old.\n", name, age);
这时我想到了C11推出的泛型关键字_Generic
,我们或许可以利用泛型机制实现一个可以自动推导数据类型的智能printf
函数,我将其命名为printft
(printf template):
int printft(const char* format, ...);
看上去跟printf
函数的签名完全一样,但是用法不一样,看上去已经很像C++的std::format了:
#define P(arg) TYPECODE(arg), (arg)
const char* name = "Bob";
int age = 31;
//output: Hi, Bob. I'm 31 years old.
printft("I'm {}, I'm {} years old.\n", P(name), P(age));
你肯定一下发现了这个P宏,还有TYPECODE宏是什么?
TYPECODE利用了_Generic
可以推导每一个参数的类型
#define TYPE_INT 1
#define TYPE_CHAR 2
#define TYPE_CHAR_PTR 3
#define TYPE_CONST_CHAR_PTR 4
#define TYPE_DEFAULT 5
#define TYPECODE(T) _Generic((T),\
int:TYPE_INT,\
char:TYPE_CHAR,\
char*:TYPE_CHAR_PTR,\
const char*:TYPE_CONST_CHAR_PTR,\
default:TYPE_DEFAULT\
)
根据T参数的类型,会返回一个整数代表type code,我们一旦拥有type code,就可以自动推导每一个参数的类型。
printft
的实现细节:
#define PRINT_BUF_SIZE 1024
int snfmtv(char* s, int n, const char* fmt, va_list v)
{
memset(s, 0, n);
int fmtlen = strlen(fmt);
int bufIndex = 0;
for (int i = 0; i < fmtlen; i++)
{
if (fmt[i] != '{')
{
s[bufIndex++] = fmt[i];
}
else
{
for (int j = i + 1; j < fmtlen; j++)
{
if (fmt[j] == '}')
{
int typecode = va_arg(v, int);
if (typecode == TYPE_INT)
{
int intval = va_arg(v, int);
char intstr[NUMBER_BUF_SIZE];
int intstr_len = snprintf(intstr, NUMBER_BUF_SIZE, "%d", intval);
memcpy(s + bufIndex, intstr, intstr_len);
bufIndex += intstr_len;
}
if (typecode == TYPE_CHAR)
{
char charval = va_arg(v, char);
char charstr[NUMBER_BUF_SIZE];
int charstr_len = snprintf(charstr, NUMBER_BUF_SIZE, "%c", charval);
memcpy(s + bufIndex, charstr, charstr_len);
bufIndex += charstr_len;
}
if (typecode == TYPE_CHAR_PTR || typecode == TYPE_CONST_CHAR_PTR)
{
char* str = va_arg(v, char*);
int str_len = strlen(str);
memcpy(s + bufIndex, str, str_len);
bufIndex += str_len;
}
i = j;
break;
}
}
}
}
return 0;
}
int printftv(const char* format, va_list args)
{
char buf[PRINT_BUF_SIZE];
int c = snfmtv(buf, PRINT_BUF_SIZE, format, args);
printf(buf);
return 0;
}
int printft(const char* format, ...)
{
va_list args;
va_start(args, format);
int c = printftv(format, args);
va_end(args);
return c;
}
有任何问题欢迎留言!我会在一天内回复你。