现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

17 | 函数式编程:一种越来越流行的编程范式

返回结果是唯一后果
参数影响结果
函数式风格
说明式改造
传统命令式编程
纯函数
代码实现
Y组合子的定义
递归的lambda表达式
不可变性和无副作用
函数式编程对并行并发友好
优缺点
区别
remove_if
partition
copy_if
C++中的过滤
accumulate
C++中的归并
transform
C++中的映射
adder
fmap
accumulate
transform
sort
计算文件总文本行数
代码简化
易于理解和推理
函数行为像数学上的函数
实现惰性的过滤器
Y组合子
不可变性和并发
命令式编程和说明式编程
Filter
Reduce
Map
高阶函数
例子
特点
课后思考
函数式编程

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
上一讲我们初步介绍了函数对象和 lambda 表达式,今天我们来讲讲它们的主要用途——函数式编程。

一个小例子

按惯例,我们还是从一个例子开始。想一下,如果给定一组文件名,要求数一下文件里的总文本行数,你会怎么做?
我们先规定一下函数的原型:
int count_lines(const char** begin,
const char** end);
也就是说,我们期待接受两个 C 字符串的迭代器,用来遍历所有的文件名;返回值代表文件中的总行数。
要测试行为是否正常,我们需要一个很小的 main 函数:
int main(int argc,
const char** argv)
{
int total_lines = count_lines(
argv + 1, argv + argc);
cout << "Total lines: "
<< total_lines << endl;
}
最传统的命令式编程大概会这样写代码:
int count_file(const char* name)
{
int count = 0;
ifstream ifs(name);
string line;
for (;;) {
getline(ifs, line);
if (!ifs) {
break;
}
++count;
}
return count;
}
int count_lines(const char** begin,
const char** end)
{
int count = 0;
for (; begin != end; ++begin) {
count += count_file(*begin);
}
return count;
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

函数式编程是一种流行的编程范式,特别适合并行和并发编程。本文以C++代码为例,展示了函数式编程的应用场景和特点。通过一个文件行数统计的例子,演示了如何使用函数式编程思想来简化代码,提高可读性和简洁性。文章强调了函数式编程的特点,包括纯函数的概念、高阶函数的应用以及命令式编程和说明式编程的对比。作者建议结合命令式和说明式编程的优势,写出易读且高性能的代码。另外,文章还介绍了Y组合子的概念和在C++中的实现。总的来说,函数式编程的优势在于代码简洁、易于推理和易于理解,对于提高代码质量和开发效率有着积极的影响。文章还提供了一些参考资料,方便读者深入学习函数式编程。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(20)

  • 最新
  • 精选
  • 驰骋
    Y-Combinator被你说高深了。递归就是自己调用自己。lamda表达式想递归,困难在于不知道自己的函数名,怎么办?调用不了自己,难道还调用不了别人。所以lamda表达式调用了Y-Combinator去间接调用自己,而Y-Combinator只不过:一,记录lamda表达式;二,转调lamda表达式。这就好比普京受制于连任时间限制,如果想继续连任,则找个代言人Y-Combinator继任。代言人的唯一作用就是到期传位普京。

    作者回复: 哈哈,有意思的比喻。以前学过函数式编程?

    2020-01-07
    2
    53
  • 罗 乾 林
    参考 istream_line_reader 实现的,望老师斧正 template<typename _InIt, typename _Fun> class filter_view { public: class iterator { // 实现 InputIterator public: using iterator_category = input_iterator_tag; using value_type = typename _InIt::value_type; using difference_type = typename _InIt::difference_type; using pointer = typename _InIt::pointer; using reference = value_type&; iterator(_InIt _First, _InIt _Last, _Fun f) :_First(_First), _Last(_Last), _fun(f) { ++(*this); } reference operator*() const noexcept { return *_Cur; } pointer operator->() const noexcept { return &(*_Cur); } iterator& operator++() { while (_First != _Last && !_fun(*_First)) { _First++; } _Cur = _First; if (_First != _Last) { _First++; } return *this; } iterator operator++(int) { iterator temp(*this); ++(*this); return temp; } bool operator==(const iterator& rhs) const noexcept { return _Cur == rhs._Cur; } bool operator!=(const iterator& rhs) const noexcept { return !operator==(rhs); } private: _InIt _First; _InIt _Last; _InIt _Cur; _Fun _fun; }; filter_view(_InIt _First, _InIt _Last, _Fun f) :_First(_First), _Last(_Last), _fun(f) { } iterator begin() const noexcept { return iterator(_First, _Last, _fun); } iterator end() const noexcept { return iterator(_Last, _Last, _fun); } private: _InIt _First; _InIt _Last; _Fun _fun; };

    作者回复: OK,没啥大问题。 代码风格要稍微说明一下。你似乎是模拟了库代码的风格,这还是有点风险的。在一般的用户代码里,不应该出现双下划线打头、或者下划线加大写字母打头的标识符——这是给系统保留的。 详见: https://zh.cppreference.com/w/cpp/language/identifiers

    2020-01-03
    4
    5
  • Resolution
    调不了自己, 就调别人 auto factorial = [](int n) { auto F = [](auto f, int n) -> int { if (n == 0) return 1; else return n * f(f,n - 1); }; return F(F, n); };

    作者回复: 😋

    2023-10-04归属地:四川
    2
  • Frank
    吴老师,为什么c++不能在返回值优化,支持把rvalue通过move构造给一个lvalue引用。我想实现模板中的协变,函数返回local variable,只能通过声明返回值类型为引用和指针才能协变支持返回值多态。把一个生命周期短的variable自动move给返回值不是应该的吗?这个场景在工厂场景下应该很常见吧。 class Base{ } class Derive : public Base {} class Factory { Bae & create(){ return Base(); } class SubFactory : public Factory{ Base & create (){ return Derive(); } } 这里的返回值类型也有可能是泛型参数,现在看来只能用智能指针包一层,通过move构造返回,这看起来不太方便。 }

    作者回复: 首先,按你目前代码的写法(非virtual方法),把create函数的返回值改成 Base 和 Derived 是可以工作的。即,如果能做到静态多态的话,各个函数返回实际类型即可。 可是,如果按照一般的做法,Base 加上虚析构,create 标成 virtual 的话,情况就不一样了。虚函数必须返回同样的类型,或者如果是指针/引用类型的话,按“协变”的要求,覆写的虚函数返回的指针/引用是被覆写的虚函数的返回类型的子类。因为虚函数被调用时,编译器是不知道实际调用的是哪个函数,如果返回非指针/引用的子类对象的话,编译器连怎么拷贝、移动这个对象都不知道了……

    2021-03-31
    6
    2
  • 泰伦卢
    请问老师,map和reduce.那是最新的语句吗?还是有第三方库?那个TBB?

    作者回复: map-reduce 是一种方法,已经有很久了。在 C++ 里的直接对应是 transform 和 accumulate。TBB 见参考资料 [7]。

    2020-01-03
    2
  • geek
    课后思考的一种实现,请老师指正一下其中存在的问题。 template <typename T = std::vector<int>> class filter_view { public: filter_view(){} filter_view(typename T::iterator begin, typename T::iterator end, std::function<bool(int)> func):begin_(begin),end_(end),func_(func){} class iterator { public: iterator(typename T::iterator end):end_(end){} iterator(typename T::iterator begin, std::function<bool(int)> func):begin_(begin), func_(func) {} int operator*() { if (!func_(*begin_)) return 0; return *begin_; } int operator->() { if (!func_(*begin_)) return 0; return *begin_; } iterator operator++(int n) { ++begin_; return *this; } iterator operator++() { iterator t = *this; operator++(0); return t; } bool operator==(const iterator& o) const { return begin_ == o.begin_; } bool operator!=(const iterator& o) const { return !(begin_ == o.end_); } private: typename T::iterator begin_; typename T::iterator end_; std::function<bool(int)> func_; }; iterator begin() { if (!func_) { throw std::logic_error("func_ is null"); } return iterator(begin_,func_); } iterator end() { if (!func_) { throw std::logic_error("func_ is null"); } return iterator(end_); } private: typename T::iterator begin_; typename T::iterator end_; std::function<bool(int)> func_; };

    作者回复: 看一下“加餐”里我的参考实现吧。你的这个问题还不少。不够通用,而且前置和后置++的理解反了,返回类型也不对——前置应返回引用,后置返回对象。后置++的参数是假的,永远不需要传递,也不应该给出名字。你写 it++; 时,编译器会自动调用 it.operator++(int)(示意,不是合法代码)。

    2021-03-17
    2
    1
  • Geek_595be5
    请问演示并行读取文件的代码例子中,最后有补充说“并行读取性能一般也不会快于顺序读取”,为什么并行和顺序读取性能差不多呢?

    作者回复: 因为瓶颈多半不在计算,而在I/O上。如果是普通磁盘,并发读取还可能更慢,因为寻道需要额外的时间。

    2023-08-23归属地:中国香港
  • 常振华
    函数式编程在并发场合下的确有优势,但是普通应用,比如递归,实现起来比命令式复杂那么多,可读性更差,意义何在。而且函数式编程的代码简洁性不如直接命令式+注释

    作者回复: 可读性问题,仁者见仁,智者见智。我不认为函数式编程一定更优越,但有些场景,函数式写法就是有优势的(反过来的场合当然也有)。C++的多范式,就是允许你在同一程序里根据需求自由使用不同的范式。 说函数式编程代码简洁性不如直接命令式,我想我开头的例子已经可以反驳这个观点了。函数式风格通常代码还容易重用,这也是特点之一。

    2021-10-15
  • chang
    我也贴下我的实现,参考了前面一些同学的,望老师指正不足之处: template<typename It, typename Comp> class filter_view { public: class iterator { public: //支持内置指针 using value_type = typename std::iterator_traits<It>::value_type; using difference_type = typename std::iterator_traits<It>::difference_type; using pointer = value_type*; using reference = value_type&; using iterator_category = std::input_iterator_tag; iterator(It it, It e, Comp cmp) : it_(it), e_(e), cmp_(cmp) { skipNoMatch(); } iterator& operator++() { ++it_; skipNoMatch(); return *this; } auto operator*() { return *it_; } bool operator==(const iterator &rhs) const { return it_ == rhs.it_; } bool operator!=(const iterator &rhs) const { return !(*this == rhs); } private: void skipNoMatch() { while (it_ != e_ && !cmp_(*it_)) { ++it_; } } private: It it_; It e_; Comp cmp_; }; filter_view(It b, It e, Comp cmp) : b_(b), e_(e), cmp_(cmp) {} iterator begin() const { return iterator(b_, e_, cmp_); } iterator end() const { return iterator(e_, e_, cmp_); } private: It b_; It e_; Comp cmp_; };

    作者回复: OK,没什么大问题。 要完全满足将来的概念检查的话,最好再加上后置 ++ 和 -> 的实现。

    2021-06-09
  • Frank
    吴老师,我重写了map-reduce逻辑(lazy evaluation),但是目前的flatten功能输出有点混乱,我暂时没排查出结果。能帮我看下吗?https://github.com/franklucky001/fp-map-reduce/blob/master/main.cpp

    作者回复: 对不起,我目前没时间看这么复杂的代码。

    2021-04-06
    2
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部