【C/C++】可变参数

在 c/c++ 中,函数是支持可变参数的,最典型的就是 printf() 函数,为了支持可变参数,函数参数的入栈顺序默认是从右往左的,即最后一个参数位于高地址,第一个参数位于低地址。

一、相关宏

1、va_list 类型变量

该变量类型是一个宏定义,本质上是一个char*指针类型的变量,这个指针指向下一个参数的地址

typedef char* va_list;

2、va_start() 宏

 void va_start(va_list ap, last_arg)

作用

初始化参数列表 ap,确定第一个不可变参数的位置(也就是第二个可变参数地址)

参数

  • ap:参数列表。
  • last_arg:传入最后一个入栈的参数(也就是第一个参数)。

3、va_arg() 宏

 type va_arg(va_list ap, type)

作用

检索函数参数列表中类型为 type 的下一个参数。它无法判断检索到的参数是否是传给函数的最后一个参数。

参数

  • ap:参数列表。
  • type:下一个参数的类型

4、va_end() 宏

 void va_end(va_list ap) 

作用

取完所有参数并从函数返回之前,必须调用 va_end(),由此确保堆栈的正确恢复。如果未正确使用 va_end(),程序可能瘫痪。

参数

  • ap:参数列表。

5、如何获取可变参数的个数/类型

因为 va_arg() 宏不能获知获取到的下一个参数是否为可变参数中的最后一个,所以如果想要获取到,可以通过传入的参数中获得。

  • 事先约定(过于死板,差不多变成普通函数了,不推荐)
  • 对于可变参数不为同一类型的:可以将第一个参数设置成 char*,在里面约定好后续变量的个数以及类型。比如 my_printf(“cdfs”, 'a', 10, 9.5, "hello world");,其中 c 代表第二个参数为 char 类型,d 表示第三个参数为 int 类型,f 表示第四个参数为 float 类型,s 表示第五个参数为 char* 类型。
  • 对于可变参数为同一类型的一种是在第一个参数中传入后续参数的个数。比如 add(3, 5, 8, -2);,第一个参数 3 代表后面的可变参数有 3 个。另一种是在最后一个参数中传入一个截止元素。比如 combine("hello", "world", NULL);,当 va_arg() 宏读取到的元素为 NULL 时,就知道该元素是最后一个了,无需继续进行读取。

6、例子

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

int adds(char *a, ...)
{
    va_list args;

    va_start(args, a);

    printf("第 1 个参数 a 的值:%s\n", a);
    printf("第 1 个参数 a 的地址:%p\n", &a);
    printf("此时 args 的地址:%p\n", args);
    printf("args 所指向的内容:%d\n", *((int*)args));
    printf("=======================================\n");
    for (int i = 0; i < 2; i++)
    {
        int j = va_arg(args, int);
        printf("第 %d 个参数:%d\n", i + 2, j);
        printf("此时 args 的地址:%p\n", args);
        printf("args 所指向的内容:%d\n", *((int*)args));
        printf("=======================================\n");
    }

    va_end(args);
}

int main()
{
	adds("hello world", 25, 32);
}

输出结果:

第 1 个参数 a 的值:hello world
第 1 个参数 a 的地址:0000005911D1F9E0
此时 args 的地址:0000005911D1F9E8
args 所指向的内容:25
=======================================
第 2 个参数:25
此时 args 的地址:0000005911D1F9F0
args 所指向的内容:32
=======================================
第 3 个参数:32
此时 args 的地址:0000005911D1F9F8
args 所指向的内容:298973088
=======================================

二、已有的可变参数底层函数

1、vprintf() 函数

是 printf() 底层函数。

/*
*描述:将可变参数列表的格式化数据打印到 stdout
*
*参数:
*  format  	包含格式字符串的 C 字符串,其格式字符串与 printf 中的格式相同。
*
*  arg  	标识使用 va_start 初始化的变量参数列表的值。
*
*返回值:
*   成功后,返回写入的字符总数。
*   如果发生写入错误,则会设置错误指示符(ferror)并返回负数。
*   如果在编写宽字符时发生多字节字符编码错误,则将 errno 设置为 EILSEQ,并返回负数;
*/
int vprintf(const char * format, va_list arg);

vprintf() 函数可以实现对 printf() 函数的封装。

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

void my_printf(char *format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

2、vsprintf() 函数

int sprintf(char *str, const char *format, ...) 底层函数。

/*
*描述:向一个字符串缓冲区打印格式化字符串
*
*参数:
*	str		字符串数组指针,该数组用于存储格式化后的 C 字符串。
*	format 	格式化模式参数。
*	arg		可变参数列表对象。
*	
*返回值:
*	如果成功,则返回写入的字符总数,不包括末尾追加的\0,否则返回一个负值。 
*/
int vsprintf(char *str, const char *format, va_list arg);
#include <stdio.h>
#include <stdarg.h>

char buffer[80];
int vspfunc(char *format, ...)
{
   va_list aptr;
   int ret;

   va_start(aptr, format);
   ret = vsprintf(buffer, format, aptr);
   va_end(aptr);

   return(ret);
}

3、vsnprintf() 函数

int snprintf(char *str, size_t size, const char *format, ...) 底层函数。

/*
*描述:向一个字符串缓冲区打印格式化字符串
*
*参数:
*	str		用于缓存格式化字符串结果的字符数组。
*	n 		限定最多打印到 str 缓冲区的字符的个数为 n-1 个,因为 vsnprintf() 
*			还要在结果的末尾追加 '\0'。如果格式化字符串长度大于 n-1,则多出的部分被丢弃。
*			一般这里传递的值就是 str 缓冲区的长度。
*	format	格式化模式。
*   arg		可变参数列表对象。
*	
*返回值:
*	如果成功,则返回成功保存到缓冲区中的字符的个数,不包括末尾追加的\0,否则返回一个负值。 
*/
int vsnprintf(char *str, size_t n, const char *format, va_list arg);
#include <stdio.h>
#include <stdarg.h>

#define MAX_SIZE 	256

char buf[MAX_SIZE];

// 该函数也可以用于封装 Arduino 的 Serial.printf() 函数,只需将里面的 printf() 进行替换即可
void my_printf(const char *format, ... )
{
	va_list args;
	
	va_start(args, format);
	vsnprintf(buf, SBUF_SIZE, format, args);
	va_end(args);
  
	printf("%s", buf);
}

4、__VA_ARGS__

该宏也可以实现对 printf() 函数的封装。
关于宏中的 #、## 符号使用问题,可以看我的另一篇文章:【C/C++】define的用法(高级用法)

#include <stdio.h>

#define SHOWLIST(...) 		printf(#__VA_ARGS__)
#define PRINT(format, ...) 	printf(format, ##__VA_ARGS__)

int main(void)
{
	SHOWLIST(Hello, 520, 3.14\n);
	PRINT("num = %d\n", 520);
	PRINT("Hello World!\n");

	return 0;
}
  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢老板不用蟹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值