今天我正在阅读有关纯功能的内容,与其使用相混淆:
如果一个函数为同一组输入返回相同的值集并且没有任何可观察到的副作用,则该函数被认为是纯粹的。
e.g。 strlen()
是一个纯粹的函数,而rand()
是一个不纯的函数。
__attribute__ ((pure)) int fun(int i)
{
return i*i;
}
int main()
{
int i=10;
printf("%d",fun(i));//outputs 100
return 0;
}
上述程序的行为方式与没有pure
声明的情况相同。
将函数声明为pure
[如果输出没有变化]有什么好处?
答案 0 :(得分:143)
pure
让编译器知道它可以对函数进行某些优化:想象一下像
for (int i = 0; i < 1000; i++)
{
printf("%d", fun(10));
}
使用纯函数,编译器可以知道它只需要评估fun(10)
一次,而不是1000次。对于一个复杂的功能,这是一个巨大的胜利。
答案 1 :(得分:34)
当你说一个函数是'纯'时,你保证它没有外部可见的副作用(正如评论所说,如果你撒谎,可能会发生坏事)。知道函数是“纯粹的”对编译器有好处,编译器可以使用这些知识进行某些优化。
以下是GCC documentation关于pure
属性的说明:
纯
除了返回值及其返回值之外,许多函数都没有任何效果 值仅取决于参数和/或全局变量。 这样的功能可以受到共同的子表达式消除和 循环优化就像算术运算符一样。这些 函数应该使用pure属性声明。例如,
int square (int) __attribute__ ((pure));
Philip's answer已经显示了如何知道函数是'纯'可以帮助进行循环优化。
这是一个常见的子表达式消除(给定foo
是纯的):
a = foo (99) * x + y;
b = foo (99) * x + z;
可以成为:
_tmp = foo (99) * x;
a = _tmp + y;
b = _tmp + z;
答案 2 :(得分:27)
除了可能的运行时优势之外,在阅读代码时,更容易推理纯函数。此外,测试纯函数要容易得多,因为您知道返回值仅取决于参数的值。
答案 3 :(得分:15)
非纯函数
int foo(int x, int y) // possible side-effects
就像纯函数的扩展
int bar(int x, int y) // guaranteed no side-effects
除了显式函数参数x,y之外,你还有 宇宙的其余部分(或计算机可以与之通信的任何东西)作为隐含的潜在输入。同样,除了显式整数返回值之外,计算机可以写入的任何内容都隐含地是返回值的一部分。
应该清楚为什么推理纯函数比非纯函数更容易。
答案 4 :(得分:7)
就像附加组件一样,我想提一下C ++ 11使用constexpr关键字对某些内容进行编码。例如:
#include <iostream>
#include <cstring>
constexpr unsigned static_strlen(const char * str, unsigned offset = 0) {
return (*str == '\0') ? offset : static_strlen(str + 1, offset + 1);
}
constexpr const char * str = "asdfjkl;";
constexpr unsigned len = static_strlen(str); //MUST be evaluated at compile time
//so, for example, this: int arr[len]; is legal, as len is a constant.
int main() {
std::cout << len << std::endl << std::strlen(str) << std::endl;
return 0;
}
对constexpr使用的限制使得该功能可以证明是纯粹的。这样,编译器可以更积极地进行优化(只需确保你使用尾递归!)并在编译时而不是运行时评估函数。
所以,回答你的问题,如果你正在使用C ++(我知道你说C,但它们是相关的),用正确的样式编写一个纯函数允许编译器做各种很酷的事情功能: - )
答案 5 :(得分:4)
通常,Pure函数比编译器可以利用的不纯函数有3个优点:
假设您有纯函数f
被调用100000次,因为它是确定性的并且仅取决于其参数,编译器可以计算其值一次并在必要时使用它
纯函数不会读取或写入任何共享内存,因此可以在单独的线程中运行而不会产生任何意外后果
函数f(struct t)
按值获取其参数t
,另一方面,如果声明为纯t
,编译器可以通过引用传递f
同时保证t
的值不会改变并获得性能提升
除了编译时考虑因素之外,可以非常简单地测试纯函数:只需调用它们。
无需构建对象或模拟与DB /文件系统的连接。