前言:为什么会写这一篇

点击查看

本来,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是在参数表中定义好的,那么ab就也可以访问了。

可变参数的使用

按照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_listC89
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 为了写这篇还没吃饭…

分类: C++

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

    (虽然我不是这个意思,但似乎真就发好人卡了)

发表评论

电子邮件地址不会被公开。 必填项已用*标注