• 【C】【C++】可变参数、不定参函数的使用



    1. C 语言

    C 语言中的可变参数写法:...


    1.1 可变宏函数

    • 以日志举例,我们写入日志时只需要输入关键信息,行号文件等由宏函数补全
    • 这其中,我们需要输入的信息是格式不定的,需要用到可变参数
    #include 
    
    #define LOG(fmt, ...) printf("[%s:%d]# "fmt, __FILE__, __LINE__, ##__VA_ARGS__);
    
    int main()
    {
        // printf("[%s:%d]# %s, %d\n",__FILE__, __LINE__, "something error...", 666);
        
        LOG("%s %d\n","something error...", 666);
        LOG("%s\n","something error...");
       
        LOG("something error...\n"); 
        // 如果只传一个fmt,没有可变参数,需要加 ##,意味着当可变参数部分没有的时候,逗号取消
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出结果:
    在这里插入图片描述

    C语言库中的宏

    1. __FILE__:字符串,记录当前文件名
    2. __LINE__:整型,记录当前行数
    3. __VA_ARGS__:可变参数

    语句分析:

    #define LOG(fmt, ...) printf("[%s:%d]# "fmt, __FILE__, __LINE__, ##__VA_ARGS__);
    
    用户使用LOG宏函数的时候,只需要想 printf 函数一样传入格式和内容就可以了,比如:LOG("%s\n", "message")
    printf 函数本应该输出:message
    但在我们使用宏函数扩展后可以输出:[文件名称:所在行数]# message
    具体是怎么匹配的呢?看看下面的解释:
    
    1. "[%s:%d]# "fmt --> 中间并没有逗号!相当于在 "[文件名称:所在行数]# " 后拼接上用户给出的 fmt 即 "message"
    
    2. __FILE__, __LINE__, 作为我们在宏函数里“绑定”的两个参数,传入给了我们宏函数中设定好的"[%s:%d]# "这两处
    
    3. ##__VA_ARGS__ 根据用于fmt定义来的可变参数,这里需要加 ## 是因为,如果用户不传入可变参数而是直接输出字符/别的格式
    	LOG("message")这样的情况,函数检测到没有多余可变参数,就会把宏函数 ## 前面逗号以后的部分省去
    	## 意为,用户传入这个参数就加上,没有就省去
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2 可变函数

    #include 
    
    • 1
    • va_list 类型,可以定义指向函数参数的指针

    • va_start() 函数:第一个参数是指针,该函数可以让指针指向第二个参数后的第一个可变参数

    • va_arg() 函数:第一个参数是指针,在可变参数范围中找到第二个参数所表示类型的值

    • va_end() 函数:释放 va_list 指针


    有点懵,别着急,下面两个例子一看就清楚了:

    🌰1. 实现打印一定个数的一串数字:

    void numPrint(int cnt, ...)
    {
        va_list p;        // 是一个指针,我们需要让他指向函数参数,用于后续打印
        va_start(p, cnt); // 意思是,让p指向 cnt 参数后的,第一个可变参数的位置
    
        // 将可变参数里的内容依次取出来打印
        for (int i = 0; i < cnt; i++)
        {
            int num = va_arg(p, int); // 调用一次往后走一次,不需要手动处理
            printf("param[%d]: %d\n", i, num);
        }
    
        va_end(p); // 销毁指针
    }
    
    int main()
    {
        // 打印数字
    	numPrint(5, 1, 2, 3, 4, 5);
        numPrint(1, 100);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出结果:
    在这里插入图片描述


    下面这个函数是专门做格式解析工作的,在第二个举例中会用到,本篇文章主要讲解可变函数系列,所以格式解析我们用这个接口就好:

    • vasprintf() 函数:解析格式字符串到指定位置,注意,其中的第一个参数的空间是被这个函数接口 动态开辟 出来的,使用完毕需要用户手动释放
    #define _GNU_SOURCE         /* See feature_test_macros(7) */
    #include 
    
    int asprintf(char **strp, const char *fmt, ...); 
    int vasprintf(char **strp, const char *fmt, va_list ap); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参数 strp:

    • 把解析后的字符串存入一个新开辟的地址空间中,并把地址赋给 strp。

    参数 fmt:

    • 格式化字符串

    参数 ap:

    • 可变函数参数的指针

    🌰2. 实现任何格式的识别和打印:

    void myprintf(const char *fmt, ...)
    {
        va_list p;
        va_start(p, fmt);
    
        // 接口 vasprintf,是专门做格式解析工作的,我们这里目的是可变参数,格式解析用接口带过
        char *out;
        int ret = vasprintf(&out, fmt, p); // 把可变参数内容都以字符串形式,写入out
        if (ret != -1)
        {
            printf(out);
            free(out);	// 手动释放动态开辟空间
        }
        va_end(p);
    }
    
    int main()
    {
        // 打印任意格式
        myprintf("%s %d %s\n", "这就是", 1, "个测试");
        myprintf("单参数test\n");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    输出结果:
    在这里插入图片描述


    2. C++

    C++ 可变参数

    • 模板类型的写法:...Args,… 代表可变参数,Args可以自定义
    • 形参的写法:Args ...args,…代表可变参数,Args是我们定义的类型,args是不定参形参名可以自定义
    • 使用时,…放在后面,代表可变参数展开

    在 使用模板+不定参数 的时候,一定需要注意:

    • 没有可变参数的时候,不能直接用,需要模板特化一下
    • 即使我们写的逻辑已经闭环了!但是模板参数他自己就是会推导到最后去!!
    • 也就是说,我们在使用可变参数的时候,都要考虑一下没有可变参数的情况
    #include 
    using namespace std;
    
    void cppprintf()
    {
        cout << endl;
    }
    
    template<typename T, typename ...Args>  // 不定参的参数包类型
    // void cppprintf(T &&v, Args &&...args)    // 这里 Args 是参数包的类型,...arg 是不定参数的写法,使用右值引用
    void cppprintf(const T &v, Args &&...args)
    {
        cout << v;
        if((sizeof ...(args)) > 0)
        {
            cppprintf(forward<Args>(args)...);   // forward 完美转发,不会改变左值或者右值的类型
        }
        else
        {
            cout << endl;
        }
    }
    
    int main()
    {
        int a = 111;
        cppprintf("1个参数");
        cppprintf("1个参数","2个参数");
        cppprintf("1个参数","2个参数", a);
        cppprintf("1个参数","2个参数",333);  // T &v,加 const 就能过了,或者改成右值也可以
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述


    🥰如果本文对你有些帮助,欢迎👉 点赞 收藏 关注,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 若有差错恳请留言指正~~


  • 相关阅读:
    养殖自动化管理系统:开启智慧养殖新篇章
    【介绍下LeetCode的使用方法】
    Hawkeye
    Ubuntu下 FTP的搭建配置
    jmeter-http post请求
    x264编译
    [ C++ ] 一篇带你了解C++中动态内存管理
    [ 红队知识库 ] 常见防火墙(WAF)拦截页面
    Matlab图像处理之Lee滤波器
    Python期末复习题:组合数据类型
  • 原文地址:https://blog.csdn.net/m0_67470729/article/details/132792981