WuMingZhao/Printft

Created Sun, 07 Jan 2024 16:45:19 +0800 Modified Sun, 28 Jan 2024 14:35:44 +0000
781 Words

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

完整代码 Source Code

有任何问题欢迎留言!我会在一天内回复你。