如何在C语言中编写自己的printf()函数?

17

实际上,我正在尝试使用可变参数在C语言中编写自己的printf()函数。但是我无法获得正确的解决方案。有人能帮我吗?


3
您的问题是关于编写 printf 函数还是使用 varargs 吗? - pmg
1
你可能需要更详细地说明你卡在了哪里。你了解 stdarg.h 吗?你是不知道如何使用它,还是在解析格式字符串时遇到了麻烦? - dmckee --- ex-moderator kitten
请参见C FAQ列表中的问题15.4 - Steve Summit
8个回答

21

在实现printf()函数之前,我们必须处理一个不寻常的问题,即可变参数。由于我们知道printf除了字符串以外还可以接受许多参数,因此我们需要使用一个标准库stdargs.h来处理这个可变参数问题。在这个实现上下文中,我们不需要学习整个stdarg.h库,因为我们只需要直接理解C程序中这些库的一些宏函数。

这里是一个漂亮快速的代码来源解释

#include<stdio.h> 
#include<stdarg.h>                      

void Myprintf(char *,...);              //Our printf function
char* convert(unsigned int, int);       //Convert integer number into octal, hex, etc.


int main() 
{ 
    Myprintf(" WWW.FIRMCODES.COM \n %d", 9); 

    return 0;
} 


void Myprintf(char* format,...) 
{ 
    char *traverse; 
    unsigned int i; 
    char *s; 

    //Module 1: Initializing Myprintf's arguments 
    va_list arg; 
    va_start(arg, format); 

    for(traverse = format; *traverse != '\0'; traverse++) 
    { 
        while( *traverse != '%' ) 
        { 
            putchar(*traverse);
            traverse++; 
        } 

        traverse++; 

        //Module 2: Fetching and executing arguments
        switch(*traverse) 
        { 
            case 'c' : i = va_arg(arg,int);     //Fetch char argument
                        putchar(i);
                        break; 

            case 'd' : i = va_arg(arg,int);         //Fetch Decimal/Integer argument
                        if(i<0) 
                        { 
                            i = -i;
                            putchar('-'); 
                        } 
                        puts(convert(i,10));
                        break; 

            case 'o': i = va_arg(arg,unsigned int); //Fetch Octal representation
                        puts(convert(i,8));
                        break; 

            case 's': s = va_arg(arg,char *);       //Fetch string
                        puts(s); 
                        break; 

            case 'x': i = va_arg(arg,unsigned int); //Fetch Hexadecimal representation
                        puts(convert(i,16));
                        break; 
        }   
    } 

    //Module 3: Closing argument list to necessary clean-up
    va_end(arg); 
} 

char *convert(unsigned int num, int base) 
{ 
    static char Representation[]= "0123456789ABCDEF";
    static char buffer[50]; 
    char *ptr; 

    ptr = &buffer[49]; 
    *ptr = '\0'; 

    do 
    { 
        *--ptr = Representation[num%base]; 
        num /= base; 
    }while(num != 0); 

    return(ptr); 
}

如果你想让它正常工作,在 while( *traverse != '%' ){ 后面添加 if( *traverse == '\0') return; - GabrielT

8

如果您有时间并且真的很好奇,可以学习GNU libc的版本: 请参见printf,它使用vprintf,而vprintf又使用vfprintf


23
让一个新手C程序员阅读GNU代码?不要太残忍哦 :-) - finnw
1
@ValentinGolev:编写基本代码可能很容易,但是使用数百个指针、大量数据结构和许多奇怪的运算符将长代码放在一行中,这将非常困难。特别是对于初学者来说。如果你想感受一下,可以尝试阅读大量GNU代码。 - Box Box Box Box

3

Linux va_start(3) man page提供了非常好的编写此类函数的示例(更简单,但通常所有主要部分都在其中)。此外,您可以检查几乎任何libstdc实现。


2

这个答案 可以帮助你理解如何编写可变参数函数。请注意,没有进行错误/边界检查,也没有设置属性告诉编译器哪些参数可能是合适的,与仅使用printf()相比没有任何优势。

它可能是你正在寻找的示例,也可能不是。

相关代码片段(稍微扩展一下):

#include <stdarg.h>
void _printf(FILE *out, va_list ap)
{
    vfprintf(out, fmt, ap);
}

void printf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    _printf(stdout, ap);
    va_end(ap);
}

注意:将这些返回正确类型(有符号整数)的工作留给读者自己完成。对我来说,这看起来很像作业,我只是试图帮助您克服使用va_start和va_end的任何障碍,并展示va_list可以传递给辅助函数,以避免在许多实现中重复代码的问题。
我强烈建议查看BSD(甚至glibc)printf子系统实现。您还可以查看uclibc、dietlibc等等。

2

这两本书都没有解释如何编写它,除了第一本可能有一点。 - Miguel M
很奇怪,@MiguelM。你确定你看的是和我一样的书吗?Hanson在第14章讲解格式化,Plauger在第12章讲解实现支持<stdio.h>所需的所有函数。 - Jonathan Leffler
我会再检查一遍。我很确定我没有在那里看到它们,但我承认我检查得很快。 - Miguel M

0

它只适用于“%s”格式说明符。但我认为这仍然很有用。

void printf(char* str, ...)
{
    char* s;

    va_list vl;
    va_start(vl, str);
    for (char* ptr = str; *ptr != '\0'; ptr++)
    {
        if (*ptr == '%')
        {
            ptr++;
            s = va_arg(vl, char*);
            while (*s != '\0')
                putchar(*s++);
        }
        putchar(*ptr);
    }
    va_end(vl);
}

int main()
{
    char str[60] = "String Array is initialized";
    printf("abcd %s abcd \n", str);
    printf("It work!\n");
}

0
    #include <stdarg.h>

int my_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    int i = 0;
    while (format[i])
    {
        if (format[i] == '%')
        {
            i++;
            switch (format[i])
            {
                case 'd':
                {
                    int x = va_arg(args, int);
                    printf("%d", x);
                    break;
                }
                case 'f':
                {
                    double x = va_arg(args, double);
                    printf("%f", x);
                    break;
                }
                case 'c':
                {
                    int x = va_arg(args, int);
                    printf("%c", x);
                    break;
                }
                case 's':
                {
                    char *x = va_arg(args, char*);
                    printf("%s", x);
                    break;
                }
                case 'x':
                case 'X':
                {
                    int x = va_arg(args, int);
                    printf("%x", x);
                    break;
                }
                case 'p':
                {
                    void *x = va_arg(args, void*);
                    printf("%p", x);
                    break;
                }
                case '%':
                    putchar('%');
                    break;
                default:
                    putchar(format[i]);
                    break;
            }
        }
        else
        {
            putchar(format[i]);
        }
        i++;
    }

    va_end(args);
    return 0;
}
int main()
{
    int x = 5;
    double y = 3.14;
    char c = 'a';
    char *str = "Hello World!";
    my_printf("The value of x is %d, y is %f, c is %c, str is %s", x, y, c, str);
    return 0;
}

我认为创建一个新的函数,而不使用printf()函数来完成printf()函数的所有功能是相当困难的。因此,我尝试了这种方式。 在这里,我为代码添加了文档- Printf()函数


如果格式字符串以单独的“%”结尾,则代码具有未定义的行为。 - chqrlie
使用 printf 实现 my_printf 是一种有趣的方法... 去除 printf 的任务留给用户作为练习。 - chqrlie
此外,该函数应返回输出的字节数。 - chqrlie
从您的代码中删除对printf的引用通常很容易,但完全支持标志、宽度和精度字段是繁琐而棘手的,而完全支持浮点转换确实非常困难。为所有值和参数生成预期输出是C库中最困难的任务之一。 - chqrlie
当然可以,@chqrlie。如果这样可以让代码无误,那就一定要这么做。我也会从中学到东西。 - Snehasish Konger
显示剩余2条评论

0
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

// You can write your own low level function to write to display or hardware
void local_put(char c){
    putchar(c);
}

void decimal_to_baseN(char *converted, unsigned int num, int base) 
{
    if (num == 0) {
        converted[0] = '0';
        converted[1] = '\0';
        return;
    }
    int MAX_REP_LEN = 250;
    char NUM_BASE[] = "0123456789ABCDEF";
    char buffer[MAX_REP_LEN];

    int i = MAX_REP_LEN - 1;
    while (num != 0) {
        buffer[i--] = NUM_BASE[num % base];
        num /= base;
    }
    int counter = 0;
    for (int j = i + 1; j <= MAX_REP_LEN - 1; j++) {
        converted[counter++] = buffer[j];
    }
    converted[counter] = '\0';
}


void my_print(char *c, ...)
{
    int num;
    va_list arg;
    va_start(arg, c);
    char outbuf[2048];
    int i;
    char *ch;
    double db_val;
    unsigned int uint_val;
    while(*c!='\0'){
        if(*c=='%'){
            c++; // To incement to the formating character
            // processing the formatting character
            switch(*c){
                case 'b':
                case 'B':
                case 'h':
                case 'H':
                case 'O':
                case 'o':
                case 'd':
                case 'D':
                case 'i':
                case 'I':
                    num = va_arg(arg, int);
                    if(num<0){
                        // Simple - sign is used instead of 2s complement
                        local_put('-');
                        num = num * -1;
                    }
                    if(*c=='b' || *c=='B'){
                        decimal_to_baseN(outbuf, num, 2);
                    }else if(*c=='o' || *c=='O'){
                        decimal_to_baseN(outbuf, num, 8);
                    }else if(*c=='d' || *c=='D'){
                        decimal_to_baseN(outbuf, num, 10);
                    }else if(*c=='h' || *c=='H'){
                        decimal_to_baseN(outbuf, num, 16);
                    }
                    
                    for(int i=0;outbuf[i]!='\0';i++){
                        local_put(outbuf[i]);
                    }
                break;
                
                case 'c':
                case 'C':
                    num = va_arg(arg, int);
                    local_put(num);
                break;
                    
                case 's':
                case 'S':
                    ch = va_arg(arg, char*);
                    while(*ch!='\0'){
                        local_put(*ch++);
                    }
                break;
                
                case 'f':
                case 'F':
                    db_val = va_arg(arg, double);
                    sprintf(outbuf, "%f", db_val);
                    for(int i=0;outbuf[i]!='\0';i++){
                        local_put(outbuf[i]);
                    }
                break;
                
                case 'u':
                case 'U':
                    uint_val = va_arg(arg, unsigned int);
                    sprintf(outbuf, "%u", uint_val);
                    for(int i=0;outbuf[i]!='\0';i++){
                        local_put(outbuf[i]);
                    }
                break;
            }
        }else{
            local_put(*c);   
        }
        c++;
    }
    va_end(arg);
}

int main()
{
    int num = 100;
    my_print("The Decimal: %d\r\n", num);
    my_print("The Unsigned int: %u\r\n", 4294967295);
    my_print("The Binary: %b\r\n", num);
    my_print("The Octel: %o\r\n", num);
    my_print("The Hex: %h\r\n", 999);
    my_print("The Character: %c\r\n", 'C');
    my_print("The String: %s\r\n", "Annie");
    my_print("The Float: %f\r\n", 4.35);
    return 0;
}

1
请记住,Stack Overflow 不仅旨在解决当前的问题,还要帮助未来的读者找到类似问题的解决方案,这需要理解底层代码。对于我们社区中的初学者和不熟悉语法的成员来说,这尤其重要。鉴于此,您能否编辑您的答案,包括您正在做什么的解释以及为什么您认为这是最好的方法?当社区已经有验证过的现有答案时,这更加关键。 - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接