Skip to content

Latest commit

 

History

History
300 lines (184 loc) · 9.26 KB

File metadata and controls

300 lines (184 loc) · 9.26 KB

十七、答案

第一章

  1. 什么是不可变函数?

不可变函数是不改变其参数值或程序状态的函数。

  1. 如何编写不可变函数?

如果你想让编译器帮你,做参数const

  1. 不可变函数如何支持代码简单性?

因为他们不改变他们的论点,他们从代码中移除了任何潜在的复杂性,从而允许程序员更好地理解它。

  1. 不可变函数如何支持简单的设计?

不可变函数很无聊,因为它们只做计算。因此,它们便于长期维护。

  1. 什么是高级功能?

高级函数是接收另一个函数作为参数的函数。

  1. 从 STL 可以举出哪些高级函数的例子?

STL 中有许多高级函数的例子,尤其是在算法中。sort是我们在本章中使用的例子;然而,如果你查看<algorithm>标题,你会发现很多其他标题,包括findfind_ifcountsearch等等。

  1. 功能循环相对于结构化循环有哪些优势?它们的潜在缺点是什么?

函数循环避免了逐个错误,并且更清楚地表达了代码的意图。它们也是可组合的,因此允许通过链接多个循环进行复杂的操作。但是,在合成时,它们需要多次通过集合,否则可以通过使用简单的循环来避免。

  1. 从艾伦·凯的角度来看,OOP 是什么?它与函数式编程有什么关系?

艾伦·凯把面向对象程序设计看作是一种根据细胞有机体原理来构造代码的方法。细胞是通过化学信号进行交流的独立实体。因此,小对象之间的通信是 OOP 最重要的部分。

这意味着我们可以对表示为对象的数据结构使用函数算法,而没有任何冲突。

第二章

  1. 什么是纯函数?

纯函数是具有两个约束的函数,如下所示:

  1. 不变性如何与纯函数相关?

纯函数是不可变的,因为它们不会改变程序状态中的任何东西。

  1. 如何告诉编译器防止对通过值传递的变量进行更改?

简单定义参数为const,如下:

int square(const int value)
  1. 如何告诉编译器防止对通过引用传递的变量进行更改?

简单定义参数为const&,如下:

int square(const int& value)
  1. 如何告诉编译器阻止对引用传递的指针地址的更改?

如果指针是通过值传递的,则不需要任何东西,因为所有的更改都是函数本地的:

int square(int* value)

如果指针是通过引用传递的,我们需要告诉编译器地址不能改变:

int square(int*& const value)
  1. 如何告诉编译器防止指针指向的值发生变化?

如果指针是通过值传递的,我们将应用与通过值传递的简单值相同的规则:

int square(const int* value)

为了防止通过引用传递指针时值和地址都发生变化,需要更多地使用const关键字:

int square(const int&* const value)

第三章

  1. 你能写的最简单的 lambda 是什么?

最简单的 lambda 不接收参数,返回一个常数;它可能如下所示:

auto zero = [](){return 0;};
  1. 如何编写一个 lambda 来连接作为参数传递的两个字符串值?

这个答案有一些变化,这取决于您首选的连接字符串的方式。使用 STL 最简单的方法如下:

auto concatenate = [](string first, string second){return first + second;};
  1. 如果其中一个值是一个被值捕获的变量,该怎么办?

答案类似于前面的解决方案,但使用了上下文中的值:

auto concatenate = [first](string second){return first + second;};

当然,我们也可以使用默认的按值捕获符号,如下所示:

auto concatenate = [=](string second){return first + second;};
  1. 如果其中一个值是被引用捕获的变量,该怎么办?

与之前的解决方案相比几乎没有变化,如下面的代码所示,除非您想要防止值的变化:

auto concatenate = [&first](string second){return first + second;};

如果你想防止数值变化,我们需要转换到const:

auto concatenate = [&firstValue = as_const(first)](string second){return firstValue + second;};
  1. 如果其中一个值是被值捕获的指针,该怎么办?

我们可以忽略不变性,如下所示:

auto concatenate = [=](string second){return *pFirst + second;};

或者,我们可以使用指向const类型的指针:

const string* pFirst = new string("Alex");
auto concatenate = [=](string second){return *pFirst + second;};

或者,我们可以只使用该值,如下所示:

string* pFirst = new string("Alex");
first = *pFirst;
auto concatenate = [=](string second){return first + second;}
  1. 如果其中一个值是被引用捕获的指针,该怎么办?

这允许我们改变指向的值和 lambda 内部的指针地址。

最简单的方法是忽略不变性,如下所示:

auto concatenate = [&](string second){return *pFirst + second;};

如果我们想要约束不变性,我们可以使用强制转换来const:

auto concatenate = [&first = as_const(pFirst)](string second){return *first + second;};

但是,通常最好直接使用该值,如下所示:

string first = *pFirst;
auto concatenate = [=](string second){return first + second;};
  1. 如果使用默认捕获说明符按值捕获两个值会怎么样?

该解决方案不需要参数,只需要从上下文中获取两个值:

auto concatenate = [=](){return first + second;};
  1. 如果使用默认捕获说明符通过引用捕获两个值,会怎样?

如果我们不关心值的变化,我们可以做以下事情:

auto concatenate = [&](){return first + second;};

为了保持不变性,我们需要对const进行强制转换:

auto concatenate = [&firstValue = as_const(first), &secondValue = as_const(second)](){return firstValue + secondValue;}

仅仅使用默认的引用捕获说明符是无法确保不变性的。请改用按值捕获。

  1. 在一个有两个字符串值作为数据成员的类中,如何将同一个 lambda 写成数据成员?

在一个类中,我们需要指定 lambda 变量的类型,以及我们是捕获两个数据成员还是这个。

以下代码显示了如何使用[=]语法通过复制来捕获值:

function<string()> concatenate = [=](){return first + second;};

下面的代码显示了如何捕获this:

function<string()> concatenate = [this](){return first + second;};
  1. 怎么能把同一个 lambda 写成同一个类的静态变量?

我们需要接收数据成员作为参数,如下所示:

static function<string()> concatenate;
...
function<string()> AClass::concatenate = [](string first, string second){return first + second;};

我们已经看到,这比将AClass的整个实例作为参数传递要好,因为它减少了函数和类之间的耦合面积。

第四章

  1. 什么是功能成分?

功能组合是对功能的操作。它接受两个函数, fg ,并创建第三个函数, C ,任何参数都有以下属性: xC(x) = f(g(x))

  1. 函数式组合有一个通常与数学运算相关的性质。这是什么?

函数组合是不可交换的。例如,对一个数的增量求平方与对一个数的平方求平方是不一样的。

  1. 如何把两个参数的加法函数变成一个参数的两个函数?

考虑以下功能:

auto add = [](const int first, const int second){ return first + second; };

我们可以将前面的函数转换为下面的函数:

auto add = [](const int first){ 
    return [first](const int second){
        return first + second;
    };
};
  1. 如何编写一个包含两个单参数函数的 C++ 函数?

在这一章中,我们看到借助模板和auto类型的魔力很容易做到这一点:

template <class F, class G>
auto compose(F f, G g){
  return [=](auto value){return f(g(value));};
}
  1. 功能成分有哪些优势?

函数组合允许我们通过组合非常简单的函数来创建复杂的行为。此外,它允许我们删除某些类型的重复。它还允许以无限的方式重组小函数,从而提高了重用的可能性。

  1. 在函数上实现操作有哪些潜在的缺点?

对函数的操作可能有非常复杂的实现,并且可能变得非常难以理解。抽象是有代价的,程序员必须始终平衡可组合性和小代码的好处与使用抽象操作的代价。

第五章

  1. 什么是偏函数应用?

部分函数应用是从函数中获取 N-1 参数的新函数的操作,该函数通过将其中一个参数绑定到一个值来获取 N 参数。

  1. 什么是拍马屁?

Currying 是将取 N 个参数的函数拆分成 N 个函数的操作,每个函数取一个参数。

  1. curry 如何帮助实现部分应用?

给定 curried 函数 f(x)(y) ,只需调用 f 就可以得到 fx =值上的部分应用,取值如下: g = f(值)

  1. 如何在 C++ 中实现部分应用?

部分应用可以在 C++ 中手动实现,但是使用functional头中的bind函数更容易实现。