文章目录
C语言的不定参
C语言的不定参数最常见的应用示例就是printf
函数,如下,参数列表中的...
表示不定参数列表
c
#include <stdio.h>
int printf(const char *format, ...);
试着模拟实现C语言的printf函数
c
void myprintf(const char *fmt, ...)
{
//TODO
}
C语言中,对于...
不定参列表,要用va_*
系列宏函数操作
c
#include <stdarg.h>
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);
-
va_list
va_list可以理解为一个指针类型,开始时,通过调用va_start,它会指向不定参列表的第一个参数
-
va_start
我们知道,C/C++的函数参数通过压栈的方式传入,不定参列表的多个参数也需要压栈。
va_start的作用是使ap指向不定参列表的第一个参数。通过不定参数列表前的最后一个函数参数,找到不定参列表的首元素,并使ap(va_list类型的指针)指向首元素。因此,C函数使用
...
不定参数,前面至少包含一个其它参数,由它提供不定参数的起始位置。 -
va_arg
va_arg的作用是在不定参数列表中,从ap指向的参数开始,逐个返回type类型的数据,一般需要循环调用va_arg,在此过程中ap会不断往后走
-
va_end
ap使用结束后就失效了,为了避免野指针的引用,va_end可以销毁ap指针
-
va_copy
拷贝src到dest,使得二者指向同一个不定参数列表,src不用再次调用va_start,但要调用va_end
Test Demo:
c
void myprintf(size_t num, ...)
{
va_list ap1;
va_start(ap1, num);
va_list ap2;
va_copy(ap2, ap1);
for (int i = 0; i < num; i++)
{
char ch = va_arg(ap1, int);
std::cout << ch;
}
std::cout << std::endl;
for (int i = 0; i < num; i++)
{
char ch = va_arg(ap2, int);
std::cout << ch;
}
std::cout << std::endl;
va_end(ap1);
va_end(ap2);
}
int main()
{
myprintf(3, 'L', 'O', 'L');
return 0;
}
⭕输出结果
bash
[ckf@VM-8-12-centos 1.before]$ ./vargs
LOL
LOL
注意:va_arg
对于可变参数的处理是有一些规则的,特别是对于小整数类型(如char
和float
)。这些类型在传递给va_arg
时会被默认转换为int
和double
,而不是它们的实际类型。因此,在Test Demo中,对于传入的char
类型参数,在调用va_arg
时也应该指定type为int,获得返回值后再转换为实际类型char
。
以下函数可以对不定参数列表进行格式化操作。
c
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
它们的作用与printf
相同,都可以通过参数format,对不定参数进行格式化。区别在于:printf
使用不定参...
作为格式化的数据,而上面以v开头printf结尾的函数,使用ap指向的不定参数列表作为格式化的数据。除此之外,内部的区别就在于输出的方向了,vprintf
将格式化后的字符串输出到stdout,vfprintf
输出到指定的文件流,vsprintf
和vsprintf
输出到指定的内存空间str,且后者可以指定输出的字节个数。
Test Demo:
c
void myprintf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char str[64] = {0};
int len = vsnprintf(str, sizeof(str), fmt, ap);
str[len] = 0;
std::cout << str << std::endl;
va_end(ap);
}
int main()
{
myprintf("%s %c %d", "你好", '!', 2024);
return 0;
}
⭕输出结果
bash
[ckf@VM-8-12-centos 1.before]$ ./vargs
你好 ! 2024
C++的不定参
💭补充:
- args参数包作为参数传递给另一个函数时,记得加
...
,表示这个args参数是不定参数包 - 函数接收
Args...
类型的参数包时,通常可以将参数设置为&&万能引用,减少拷贝的成本 - C++11标准库中,很多构造方法都使用了以不定参数包为参数的条目,方便一些无法缺点参数个数的实例的构造
例如:
- std::make_shared,由于并不确定构造智能指针指向的对象类型需要传入多少参数,C++11使用了Args不定参数包
-
emplace函数,C++11很多容器都重载了这个函数,emplace支持可变参数包,并且用参数包中的参数作为新插入元素构造函数的参数。
Test Demo
cpp
template <class T>
void myprintfcpp(T val) // 终止函数
{
std::cout << val;
}
template <class T, class... Args>
void myprintfcpp(T val, Args &&...args)
{
std::cout << val;
myprintfcpp(std::forward<Args>(args)...);
}
int main()
{
myprintfcpp(3, 'L', 'O', 'L');
std::cout << std::endl;
return 0;
}
⭕输出结果
bash
[ckf@VM-8-12-centos 1.before]$ ./vargs
3LOL
END...