前言:为什么会写这一篇
点击查看
本来,Sukazyo 是专攻 Java,偶尔会玩会儿网页前后端的,C++ 这种东西只在当年参加 OI 的时候用到了(当然初学编程时也学了点)。
写这篇的原因是最近联络上了曾经在一个机房上过课的 LightingUZ,然后今天翻它的介绍时发现了这个:
R1()
:快读,Read One
的简写。原本想用不确定长度参数列表写一个形如Rd(a,b,c,d,e...)
的函数,然后就珂以一次读完所有要读的整数了。没有成功,但是R1
这个名字保留了下来。 (如果您会写Rd函数,私信我,我会很感谢你的)
想起来之前 Sukazyo 研究标准库还挺多的,就写一个教程帮帮它顺便领个好人卡。
C++ 中可变参数函数的探讨
说到可变参数,就一定会想起来这样一个函数:
int printf(const char* format, ...); // example: printf("The nums is:[%d], [%d]", a, b);
这个应该是最常见的了吧。那么下面就用这个函数说一下可变参数的实现原理。
可变参数的实现原理
根据可变参数的定义可以发现,可变参数的可变部分并没有参数名。那么怎么访问呢?用地址。
C++ 在进行可变参数的调用时,会把传过来的参数按照倒序存放在栈里。这样的话,只要知道一个参数的内存位置,就可以计算出其它参数的位置,也就可以访问了。如下图展示了在内存中参数的位置(手绘):

format
是在参数表中定义好的,那么a
和b
就也可以访问了。
可变参数的使用
按照printf(const char* format, …)
的定义,可变参数函数的可变部分就是使用...
来作为一个参数,表示之后传入的参数是可变参数的一部分。但是,在...
之前,必须有一个确定的参数,否则,就无法确定位置了(实际上这在传统的C中会被编译器认为是语法错误)。
void fuc1(int a, char c, ...); // 正确并且能够使用 void fuc2(...); // C错误,C++正确,但是无法使用 void fuc3(..., string str); // 错误,因为...后的参数不会被编译器认可
C++ STL 中,头文件stdarg.h
定义了一些为了可变参数准备的数据类型和宏(函数) 如下表(资料来源:维基百科)。
名称 | 描述 | 引入版本 |
数据类型va_list | 用来保存宏va_arg与宏va_end所需信息 | C89 |
宏va_start | 使va_list 指向起始的参数 | C89 |
宏va_arg | 检索参数 | C89 |
宏va_end | 释放va_list | C89 |
宏va_copy | 拷贝va_list 的内容 | C99 |
一般来说只需要用到前4个,va_copy
一般不会用得到。实际上经过测试,最后不写va_end
也不会出现运行问题,不过,为了安全性,还是写上。
具体使用方法如下例:
#include <stdarg.h> void func (type a/* 一个固定的参数 */, ...) { va_list args; // 定义的可变参数表 va_start(args, a); // 通过使用已知的参数初始化可变参数 while(/* 判断代码 */a < 0) { /** * 使用 va_arg() 来获取一个可变参数 * args 是参数表,type 是这个参数的类型 * 获取的参数为常量,不可修改 * 函数获取的顺序为从前到后, * 即如果调用的的是 func(a, one, another); * 第一次获取到的就会是 one,第二次是 another */ a = va_arg(args, type); /* 执行代码 */ } va_end(args); // 释放参数表 }
这里还有一个wiki上的例子
#include <stdio.h> #include <stdarg.h> void printargs(int arg1, ...) /* 輸出所有int型態的參數,直到-1結束 */ { va_list ap; int i; va_start(ap, arg1); for (i = arg1; i != -1; i = va_arg(ap, int)) printf("%d ", i); va_end(ap); putchar('\n'); } int main(void) { printargs(5, 2, 14, 84, 97, 15, 24, 48, -1); printargs(84, 51, -1); printargs(-1); printargs(1, -1); return 0; }
输出:
5 2 14 84 97 15 24 48 84 51 1
由于标准库并没有(实际上是不能)获取可变参数的数量,所以为了控制函数流程,一般会在固定参数中要求提供参数个数,或者占位符。
LightningUZ 想要的Rd()
有了以上铺垫,就可以写出一个可变参数版的Rd()
了:
/** * 为 LightningUZ 提供的可变参数版 Rd() * * 内核部分使用了 UZ 的 R1() 函数。 * * 因为一直没有调好引用的实现方案,就暂时使用了指针凑合着, * 调用的时候记得使用像 Rd(3, &a, &b, &c); 这样传递地址 * * 使用时记得添加前置<stdarg.h> * * @param count 需要读取的数量 * */ void Rd(int count, ...) { va_list args; va_start(args, count); for (register int i = 0; i < count; i++) { int* x = va_arg(args, int*); *x = 0; char c = getchar(); int f = 1; while (c < '0' or c > '9') f = (c == '-') ? -1 : 1, c = getchar(); while (c >= '0' and c <= '9') *x = (*x << 1) + (*x << 3) + (c ^ 48), c = getchar(); *x = (f == 1) ? *x : -*x; } va_end(args); }
后记
话说不知不觉就下午2点多了而 Sukazyo 为了写这篇还没吃饭…
3 条评论
Sukazyo · 2020-01-11 下午2:33
会不会讲的有点奇怪?
这里还有一篇别人的可以参考:
https://blog.csdn.net/jackystudio/article/details/17523523
LightningUZ · 2020-01-11 下午2:35
谢谢姐姐,姐姐最好了
LightningUZ · 2020-01-22 下午10:52
(虽然我不是这个意思,但似乎真就发好人卡了)