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

常见实现方法

变常参数的宏定义以及__VA_ARGS

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

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

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

#include 

#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 
#include 
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 
#include 
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 
#include 
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函数时,其参数入栈情况如下:
C/C++编程:可变参数_第1张图片
调用va_start(list, param1)时,list指针指向情况如下:
C/C++编程:可变参数_第2张图片
最复杂的宏是va_arg。它必须返回一个由va_list所指向的恰当的类型的数值,同时递增va_list,使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再被转换为要求的类型。因为该指针现在指向的位置"过"了一个类型单位的大小,所以我们使用了下标-1来存取正确的返回参数。

实践

基本用法

#include 
#include 


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

C/C++编程:可变参数_第3张图片

#include 
#include 
#include 


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 
#include 


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  
#include  

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

你可能感兴趣的:(C++,c++,c语言,开发语言)