不定参数的应用

本文介绍了C/C++中不定参数(varargs)的应用,包括原理、如何确定参数个数和访问参数,同时讨论了使用不定参数可能遇到的潜在问题,如类型匹配和重载函数的限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不定参数的应用

不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。

原理

刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打印不定个数的变量到屏幕,如:

printf("%d", 3);
printf("%d,%d",3,4);

 上述代码看似简单,实际上却需要我们解决许多问题。在我们设计printf的时候,我们是不知道到底会传入几个参数的。在这种未知的情况下,我们需要解决下面几个问题:

  1. 怎么告诉printf我们会传入几个参数
  2. printf怎么去访问这些参数
  3. 函数调用完成后,系统怎么把参数从传递用的堆栈中释放

为了解决这些问题,我们首先要解释cdecl调用约定(参见论调用约定),所有使用不定参数的函数必须是使用cdecl(全局函数)或者this call(类成员函数)调用约定。该约定对于参数传递规定如下:

  1. 参数从右向左入栈(也就是如果你调用f(a,b,c),则c先入栈,然后是b,最后是a入栈)
  2. 调用者负责清理堆栈

其中第二点直接解决了前面三个问题中的第三个问题。我们来详细说说其他两个问题。

确定参数的个数

在一个函数中,一般有如下prolog代码:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,48h
执行上述代码之后,func(a,b,c)函数所处的堆栈上下文就变成如下布局:

堆栈布局

其中,ebp指向保存旧的ebp的堆栈内存的下一个字的地址,ebp+8指向eip地址,ebp+12则指向函数调用的第一个参数,而ebp和esp之间是用于临时变量(也就是堆栈变量)的空间。

注意,由于上述prolog代码的存在,我们很容易通过ebp得到第一个参数的地址,对于不定参数列表之前的类型固定的参数,我们也可以根据类型信息得到其实际的位置(例如,第一个参数的位置偏移第一个参数的大小,就是第二个参数的地址)。

注意不定参数函数有个限制,就是不定参数的列表必须在整个函数的参数列表的最后。我们不可以定义如下的函数:

void func(int a, ..., int c)

所有类型固定的参数都必须出现在参数列表的开始。这样根据前面的论述,我们就可以得到所有类型固定的参数。

在设计具有不定参数列表的函数的时候,我们有两种方法来确定到底多少参数会被传递进来。

方法1是在类型固定的参数中指明后面有多少个参数以及他们的类型。printf就是采用的这种方法,它的format参数指明后面每个参数的类型。

方法2是指定一个结束参数。这种情况一般是不定参数拥有同样的类型,我们可以指定一个特定的值来表示参数列表结束。下面这个sum函数就是一个例子:

int  sumi( int  c, ...)
{
### JavaScript 中不定参数(Rest Parameters)的使用方法 #### 定义与基本概念 在 JavaScript 中,ES6 引入了剩余参数(Rest Parameters),它允许函数接受任意数量的参数并将这些参数作为数组来处理[^1]。这使得开发者能够更方便地操作多个输入值。 #### 语法结构 剩余参数通过三个点 `...` 和一个变量名表示,这个变量会自动成为一个数组,存储传递给函数的所有额外参数[^2]。其一般形式如下: ```javascript function functionName(param1, param2, ...restParams) { // 函数体 } ``` 在此例子中,`param1` 和 `param2` 是固定参数,而 `restParams` 则是一个数组,包含了超出前两个位置的所有其他参数。 #### 实际应用案例 下面展示如何利用剩余参数简化代码逻辑并增强功能灵活性。 ##### 示例 1: 计算总和 假设我们需要编写一个计算若干数之和的函数,可以这样实现: ```javascript function sum(...numbers) { return numbers.reduce((acc, curr) => acc + curr, 0); } console.log(sum(1, 2, 3, 4)); // 输出:10 ``` 这里,`numbers` 自动成为 `[1, 2, 3, 4]` 数组,便于后续调用数组方法完成累加操作[^3]。 ##### 示例 2: 函数柯里化 另一个高级用途是在函数式编程领域内的 **柯里化** 技术上。借助剩余参数,我们可以轻松构建支持部分求值的功能模块: ```javascript function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn(...args); } else { return function (...moreArgs) { return curried(...args.concat(moreArgs)); }; } }; } function add(a, b, c) { return a + b + c; } const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 输出:6 ``` 上述片段展示了如何逐步提供参数直到满足最终执行条件的过程[^4]。 #### 特性和优势总结 - 剩余参数本质上就是一个普通的数组实例,因此可以直接运用各种内置 API 方法。 - 提供了一种更加直观清晰的方式来管理可变长度的输入列表。 - 支持复杂的模式匹配以及链式调用设计风格。
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值