我正在编写一个API来更新结构中的许多不同字段.
我可以通过使更新函数variadic来帮助添加将来的字段:
update(FIELD_NAME1, 10, FIELD_NAME2, 20);
然后添加FIELD_NAME3
更改任何现有的调用:
update(FIELD_NAME1, 10, FIELD_NAME2, 20, FIELD_NAME3, 30);
请问智慧的话语?
一般来说,没有.
Varargs抛出了很多类型安全 - 你可以传递指针,浮点数等,而不是整数,它将编译没有问题.滥用varargs(例如省略参数)可能会因堆栈损坏或读取无效指针而引入奇怪的崩溃.
例如,以下调用将编译并导致崩溃或其他奇怪的行为:
UpdateField(6, "Field1", 7, "Field2", "Foo");
最初的6是预期的参数数量.它会将字符串指针"Foo"转换为一个int以放入Field2,它将尝试读取和解释其他两个不存在的参数,这可能会导致崩溃,从而解除引用堆栈噪声.
我相信在C语言中实现varargs是一个错误(考虑到今天的环境 - 它可能在1972年完全合理.)实现是你在栈上传递一堆值然后被调用者将在栈中拾取参数,基于关于它对一些初始控制参数的解释.这种类型的实现基本上会让您在可能非常难以诊断的方式中犯错.C#的实现,传递一个带有方法属性的对象数组,只是必须更加理智,尽管不能直接映射到C语言.
我倾向于避免使用varargs,除非在一个非常有用的特定情况下.除了单个函数调用可以完成的任务之外,变量参数并没有真正提供所有好处,尤其是在您的情况下.
在可读性方面(通常我比原始速度更喜欢除了非常特殊的情况),以下两个选项之间没有真正的区别(我已经为varargs版本添加了一个计数,因为你需要一个计数或者哨兵来检测数据的结尾):
update(2, FIELD_NAME1, 10, FIELD_NAME2, 20); update(3, FIELD_NAME3, 10, FIELD_NAME4, 20, FIELD_NAME5, 30); /* ========== */ update(FIELD_NAME1, 10); update(FIELD_NAME2, 20); update(FIELD_NAME3, 10); update(FIELD_NAME4, 20); update(FIELD_NAME5, 30);
事实上,随着varargs版本变得越来越长,无论如何你都需要将其拆分,以便进行格式化:
update(5, FIELD_NAME1, 10, FIELD_NAME2, 20, FIELD_NAME3, 10, FIELD_NAME4, 20, FIELD_NAME5, 30);
这样做"每个字段名称一次调用"的方式导致函数本身的代码更简单,并且不会降低调用的可读性.此外,它允许编译器正确检测它不能对varargs执行的某些错误,例如不正确的类型或用户提供的计数与实际计数之间的不匹配.
如果你真的必须能够调用一个函数来执行它,我会选择:
void update (char *k1, int v1) { ... } void update2 (char *k1, int v1, char *k2, int v2) { update (k1, v1); update (k2, v2); } void update3 (char *k1, int v1, char *k2, int v2, char *k3, int v3) { update (k1, v1); /* or these two could be a single */ update (k2, v2); /* update2 (k1, v1, k2, v2); */ update (k3, v3); } /* and so on. */
如果您愿意,您甚至可以将更高级别的功能用作宏,而不会丢失类型安全性.
我倾向于使用varargs函数的唯一地方是提供相同的功能printf()
- 例如,我偶尔必须编写具有诸如logPrintf()
提供相同功能的功能的日志库.在我需要使用它的时候,我想不出任何其他时间在我的长期(我的意思是,长期:-)时间.
顺便说一句,如果你决定使用varargs,我倾向于选择哨兵而不是计数,因为这可以防止在添加字段时出现不匹配.您可能很容易忘记调整计数并最终得到:
update (2, k1, v1, k2, v2, k3, v3);
添加时,这是阴险的,因为它默默地跳过k3/v3,或者:
update (3, k1, v1, k2, v2);
删除时,这对于程序的成功运行几乎肯定是致命的.
有哨兵可以防止这种情况(当然,只要你不忘记哨兵):
update (k1, v1, k2, v2, k3, v3, NULL);
C语言中的varargs的一个问题是你不知道传递了多少个参数,所以你需要将它作为另一个参数:
update(2, FIELD_NAME1, 10, FIELD_NAME2, 20); update(3, FIELD_NAME1, 10, FIELD_NAME2, 20, FIELD_NAME3, 30);