Hellow! 你们好啊!,今天博主来介绍一个C语言练手的小项目--模拟实现printf函数,顺便也更进一步认识不定参数的实现原理,不了解的铁子们可以先去看看博主的C语言专栏关于不定参数的介绍。


注:代码运行环境windows11,IDE大家各有所好,可以编译源代码就行

1 前置知识点铺垫--函数栈帧

简单点说就是调用函数时会创建一个栈空间来存储函数的参数变量,参数依次从右往左入栈,还有存储函数的返回地址,这个函数是从哪个地址调用过来的,还有函数里面创建的临时变量等等,想了解更多的可以去看博主的C语言专栏。

2 实现代码及接口认识

介绍都在注释里,博主这次就偷个懒

#define _GNU_SOURCE
#include<stdio.h>
#include<stdarg.h>
#include<stdlib.h>

//...可变参数可以做函数的形参
//补充知识点:函数栈帧和参数压栈
//当调用函数时,OS会申请一块栈空间用来存储传入给函数的参数,依次从右向左入栈和返回地址
//还有函数内创建的临时变量等等。
//也就是说函数所有的参数存储的空间是连续的通过起始指针和每个参数的类型就可以拿到每个参数了
//从而实现可变参数的功能,最后入栈的参数在存储参数的空间的最开始。
//所以可变参数的函数的第一个参数必须要给具体的类型

//注意:使用时要确保参数类型和数量正确,否则可能导致未定义行为。
void PrintNum(int count,...)
{
    va_list ap;//作用是声明一个可变参数列表对象--就是一个指针
    //初始化列表对象,从存储参数c的栈空间中count参数的地址后面的地址开始取出存储的参数
    //按字节取出存储参数,把所有字节拷贝出来到参数对象ap中
    va_start(ap,count);
    for(int i=0;i<count;i++)
    {
        //从参数对象ap中按int类型拿存储在这些字节中的值
        //比如从头拿出4个字节然后转成int类型返回,下次取就从上次取的结束的位置开始取
        int num=va_arg(ap,int);
        printf("%d ",num);
    }
    va_end(ap);//把指针置空
    printf("\n");
}
//实现printf
void myprintf(const char* fmt,...)
{
    va_list ap;
    va_start(ap,fmt);
    char* res;
    //在堆区申请一块足够大的空间,然后把ap参数列表对象中的参数按照fmt中的格式拿出来
    //比如 fmt="hh%d-%s ffd %c"--从ap中按字照%d即整形取出4个字节然后转成int ,接着从
    //后面继续取出一个字符串的长度的字节转成char*,接着取出一个字节转成char类型
    //然后替换掉fmt中的格式说明符,然后把该字符串放入堆区,把堆区的起始地址通过传入的char**类型的
    //输出型参数返回
    int n=vasprintf(&res,fmt,ap);
    if(n!=-1)
    {
        printf((const char*)res);
        free(res);
    }
    va_end(ap);
}

int main()
{
    PrintNum(4,1,2,3,4);
    PrintNum(7,1,2,3,4,5,6,7);
    myprintf("asdsad\n");
    myprintf("as %ddsa %sd\n",10,"哈哈哈");
    printf("asdsad");
    return 0;
}

本期收录于博主的专栏——C/C++练手小项目专栏,代码里面都有注释有哪里不理解的小伙伴可以评论区留言。

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:小董 不懂  *( ̄▽ ̄)°★*

       

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐