例如:
int f1() {
return 3;
}
void f2(int *num) {
*num = 3;
}
int n1, n2;
n1 = f1();
f2(&n2);
使用f1,我们可以返回一个值并执行"variable=f1()"但是,void函数也可以做到这一点,该函数在给定地址的情况下更新变量的值,而不必执行"variable=f1()"。
那么,这是否意味着我们实际上可以对所有事情都使用void函数?或者是否存在void函数无法替代另一个int函数/(type)函数的功能?
将所有东西都设为void函数(在一些人的词典中被称为"例程")的主要问题是你不能轻易地将它们链接起来:
f(g(x))
成为,如果你真的想连锁它:
int gout;
f((g(x, &gout), gout))
这很痛苦。
是的,您可以对所有内容使用void
返回类型,并且完全依赖于通过修改的参数返回。事实上,您可以完全避免使用函数,并将所有内容都放在main
方法中。
与语言的任何其他功能一样,返回值会给您带来特殊的优势,您可以决定是否需要它们。以下是我脑海中浮现的回报值的一些优势:
- 返回的值可以分配给
const
变量,这可以使代码更容易推理 - 编译器可以对返回值应用某些类型的优化(这更适用于C++RVO,但也可能适用于C的结构;我不确定)
-
使用返回值的代码通常更容易阅读,尤其是当函数是数学函数时(例如,如果需要通过参数输出,则必须使用sin/cos/等手动声明大型数学运算的所有临时值)。比较:
double x = A*sin(a) + B*cos(b);
带有
double tmpA, tmpB; sin(&tmpA, a); cos(&tmpB, b); double x = A * tmpA + B * tmpB;
或者使用约翰·兹温克在他的回答中建议的类似结构:
double tmpA, tmpB; double x = A * (sin(&tmpA, a), tmpA) + B * (cos(&tmpB, b), tmpB);
-
无论函数内部发生什么,都可以保证设置该值,因为这是由编译器强制执行的(除了一些非常特殊的情况,如长跳)
- 您不需要担心是否使用了指定的值;您可以返回该值,如果请求者不需要它,他们可以忽略它(将其与在替代方法中处处需要NULL检查进行比较)
当然也有缺点:
- 你只得到一个返回值,所以如果你的函数在逻辑上返回多种类型的数据(并且它们不能在逻辑上组合成一个结构),那么通过参数返回可能会更好
- 由于需要复制大型对象,它们可能会带来性能损失(这就是在C++中引入RVO的原因,这使得问题大大减少)
那么,这是否意味着我们实际上可以对所有事情都使用void函数?
的确如此。事实证明,这样做是一种相当常见的编码风格。但是,与void
不同,这种样式通常规定返回值应始终保留给错误代码。
在实践中,你通常无法始终如一地坚持这种风格。在某些特殊情况下,不使用返回值会变得不方便。
例如,当编写标准C泛型函数bsearch
或qsort
所使用的类型的回调函数时。预期格式的回调
int compare (const void *p1, const void *p2);
其中函数返回小于零、大于零或零。从设计角度来看,保持传递的参数为只读是很重要的,你不希望你的通用搜索算法突然开始修改搜索内容。因此,虽然理论上没有理由说这类函数也不能是void
返回类型,但在实践中,这会使代码变得更丑陋、更难阅读。
当然可以;但这并不是一个好主意。
它可能并不总是方便的,也不总是导致易于理解的代码。返回void的函数不能直接用作表达式中的操作数。例如,当你可以写:
if( f1() == 3 )
{
...
}
对于f2()
,您必须写入:
f2( &answer ) ;
if( answer )
{
...
}
另一个问题是访问控制——通过传递指向函数的指针,您可以让该函数间接访问调用方的数据,只要该函数行为良好且没有溢出,这是可以的。指针可以指单个对象或对象数组——使用该指针的函数必须强加适当的规则,因此本质上不太安全。