C/C++编程:可变参数

1059 篇文章 280 订阅

常见实现方法

变常参数的宏定义以及__VA_ARGS

变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS则可以在宏定义的实现部分替换省略号所代表的字符串,比如:

#define PR(...) printf(__VA_ARGS__)

就可以定义一个printf的别名PR。实际上,变长参数宏与printf经常搭配使用:

#include <stdio.h>

#define LOG(...){ \
    fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__);\
    fprintf(stderr, __VA_ARGS__);\
    fprintf(stderr, "\n");\
}

int main()
{
    LOG("test : %d", 3);
    return 0;
}

在这里插入图片描述

va_list

头文件:

<stdarg.h>

作用:

保存va_start,va_arg,va_end和va_copy(typedef)所需的信息

va_start允许访问可变参数函数参数
va_arg访问下一个可变参数函数参数
va_copy制作可变参数函数参数(函数宏)的副本
va_end结束可变参数函数参数(函数宏)的遍历

当你的函数的参数个数不确定时,就可以使用上述宏进行动态处理,这无疑为你的程序增加了灵活性。

va_list的使用方法:

  • 首先在函数中定义一个具有va_list型的变量,这个变量是指向参数的指针。
  • 然后用va_start宏初始化变量刚定义的va_list变量,使其指向第一个可变参数的地址。
  • 然后va_arg返回可变参数,va_arg的第二个参数是你要返回的参数的类型(如果多个可变参数,依次调用va_arg获取各个参数)。
  • 最后使用va_end宏结束可变参数的获取。

在使用va_list是应该注意一下问题:

  • 可变参数的类型和个数完全由代码控制,它并不能智能地识别不同参数的个数和类型。
  • 如果我们不需要一一详解每个参数,只需要将可变列表拷贝到某个缓冲区,可以用vsprintf函数。
  • 因为编译器对可变参数的函数原型检查不够严格,对编程查错不利,不利于我们写出高质量的代码。

C++11中的可变参数initializer_list

C++11在标准库中提供了initializer_list类,用于处理参数数量可变但是类型相同的情况。使用initializer_list最常用的方式是通过{}包围的值列表对其进行初始化:

initializer_list<int> vlist{9, 8, 7, 6};

继续看下面的函数:

template<typename T>
void output(initializer_list<T> lst)
{
    for(auto &a : lst){
        cout << a << endl;
    }
}

这个函数很简单,就是输出list中的内容,它有几个特点:

  • 通过模版,auto的使用,是它可以自动适应参数的类型
  • 通过initializer_list的使用,自动适应参数的个数

函数弄好以后,怎么使用就可以看心情了。

initializer_list<int> vlist{9, 8, 7, 6};
output(vlist);

output({1, 3, 4, 5});

output({"How", "are", "you", "!"});

模板

类型一致可变参数模板

#include <iostream>
#include <cstdarg>
using namespace std;

template<class T>
T add(int n, T t...)   //第一个表示多少个参数,最后
{
	cout << typeid(T).name() << endl;
	va_list arg_ptr; //开头指针            //va_list:char *类型
	va_start(arg_ptr, n);  //从arg_ptr开始读取N个
	T res(0);              //初始化为0
	for (int i = 0; i < n; i++)
	{
		res += va_arg(arg_ptr, T);    //根据数据类型取出数据
	}
	va_end(arg_ptr);
	//cout << res << endl;
	return res;	
}

void main()
{
	cout << add(1, 2, 3) << endl;
	cout << add(1.1, 2.2, 3.3, 4.4) << endl;
	cin.get();
}

类型不一致可变参数模板

//参数类型不一致,个数不确定
#include <iostream>
#include <cstdarg>
using namespace std;

void show()   //递归必须有一个结束符:空函数
{

}
template <typename T, typename...Args>  //typename比class作用域更强
										//typename...处理可变参数
void show(T t, Args...args)
{
	cout << t << endl;  //打印
	show(args...);
}

void main()
{
	show(1, 1.2, "123", "A");
	cin.get();
}

总结:

1、CPP的可变参数库:#include < cstdarg>

2、尽量使用typename而不是class来声明一个模板

3、函数的可变参数模板必须用递归来实现

类型不一致可变参数模板

#include <iostream>
#include <cstdarg>
using namespace std;
void show(const char *str)
{
	cout << str;
}
template <typename T, typename...Args>  //typename比class作用域更强
void show(const char *str, T t, Args...args)
{
	while (str && *str)  //指针不为空为字符串没有指向末尾
	{
		if (*str == '%' && *(str + 1) != '%')
		{
			++str;  //指针向前移动
			cout << t;
			show(++str, args...);  //继续调用
			return;
		}
		else
		{
			cout << *str++; //跳过一个字符
		}
	}
}
void main()
{
	printf("%dADCE1345%s%c%%XXXX", 10, "1234", '0');
	putchar('\n');
	show("%d1345%s%c%%XXXX", 10, "1234", '0');
	cin.get();
}
|  |  |
|--|--|
|  |  |

原理

typedef char *va_list;

va_start宏,获取可变参数列表的第一个参数的地址(list是类型为va_list的指针,param1是可变参数最左边的参数):

#define va_start(list,param1)   ( list = (va_list)&param1+ sizeof(param1) )

va_arg宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(mode参数描述了当前参数的类型):

#define va_arg(list,mode)   ( (mode *) ( list += sizeof(mode) ) )[-1]

va_end宏,清空va_list可变参数列表:

#define va_end(list) ( list = (va_list)0 )

假设有这样一个函数

void test(char *para1,char *param2,char *param3, char *param4)
{
      va_list list;
      ......
      return;
}

在c语言中,函数参数是存储在栈中的,函数参数从右到左依次入栈。又Linux中,栈地址从高到低生长。因此,调用test函数时,其参数入栈情况如下:
在这里插入图片描述
调用va_start(list, param1)时,list指针指向情况如下:
在这里插入图片描述
最复杂的宏是va_arg。它必须返回一个由va_list所指向的恰当的类型的数值,同时递增va_list,使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再被转换为要求的类型。因为该指针现在指向的位置"过"了一个类型单位的大小,所以我们使用了下标-1来存取正确的返回参数。

实践

基本用法

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


void simple_printf(const char *fmt, ...){
    va_list  args;   //  //定义一个具有va_list型的变量,这个变量是指向参数的指针。
    va_start(args, fmt);  // 第一个参数指向可变列表的地址,地址自动增加

    while (*fmt != '\0'){
        if (*fmt == 'd'){
            int i = va_arg(args, int );  //  访问下一个可变参数函数参数
            printf("%d\n", i);
        } else if (*fmt == 'c'){
            int c = va_arg(args, int);
            printf("%c\n", c);
        }else if (*fmt == 'f') {
            double d = va_arg(args, double);
            printf("%f\n", d);
        }
        ++fmt;
    }
}

int main(){
    simple_printf("dcff", 3, 'a', 1.99, 425.5);
}

在这里插入图片描述

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


double stddev(int count, ...){
    double sum = 0;
    double sum_sq = 0;
    va_list  args;
    va_start(args, count);
    for (int i = 0; i < count; ++i) {
        double num = va_arg(args, double );
        sum += num;
        sum_sq += num * num;
    }
    va_end(args);
    return  sqrt(sum_sq/count - (sum/count)*(sum/count));
}

int main(){
    printf("%f\n", stddev(4, 25.0, 27.3, 26.9, 25.7));
}

在这里插入图片描述

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


double stddev(int count, ...){
    double sum = 0;
    va_list  args1, args2;
    va_start(args1, count);
    va_copy(args2, args1);
    for (int i = 0; i < count; ++i) {
        double num = va_arg(args1, double);
        sum += num;
    }
    for (int i = 0; i < count; ++i) {
        double num = va_arg(args2, double);
        sum += num;
    }


    va_end(args1);
    return sum;
}

int main(){
    printf("%f\n", stddev(4, 25.0, 27.3, 26.9, 25.7));
}

在这里插入图片描述

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

void error(char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    fprintf(stderr, "Error: ");
    vfprintf(stderr, format, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    return;    
}

·

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void acl_msg_printf(const char *fmt,...){
    char buf[2048];
    va_list ap;

    va_start (ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    printf("%s\r\n", buf);


    va_end(ap);
}

int main(){
    acl_msg_printf("%s %d %s", "Failed", 100, "times");
}

在这里插入图片描述

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void acl_msg_error(char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    fprintf(stderr, "Error: ");
    vfprintf(stderr, format, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    return;
}

int main(){
    acl_msg_error("%s %d %s", "Failed", 100, "times");
}

在这里插入图片描述

include <cstdio>
#include <cstdarg>
#include <cstring>
#include <memory>
#include <string>
 
std::string str_format(const char *fmt, ...)
{
    int old_size = strlen(fmt);
    std::unique_ptr<char[]> buf(new char[old_size]);
    va_list ap;
     
    va_start(ap, fmt);
    int new_size = vsnprintf(buf.get(), old_size, fmt, ap);
    va_end(ap);
    if (new_size < 0)
        return "";
 
    buf.reset(new char[new_size + 1]);
    va_start(ap, fmt);
    new_size = vsnprintf(buf.get(), new_size + 1, fmt, ap);
    va_end(ap);
    if (new_size < 0)
        return "";
 
    return std::string(buf.get());
}
 
int main()
{
    auto ret = str_format("%d %lf %s", 1, 3.14, "hello world");
    printf("%s\n", ret.c_str());  // 1 3.140000 hello world
    return 0;
}
  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值