(转)现代C++函数式编程
本文转自:http://geek.csdn.net/news/detail/96636
-
现代C++函数式编程
作者简介: 祁宇,武汉烽火云创软件技术有限公司研发中心技术总监,《深入应用C++11》作者,C++开源社区purecpp.org创始人,致力于C++11的应用、研究和推广。乐于研究和分享技术,爱好C++,爱好开源。
导读: 本文作者从介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代C++的新特性以及一些模板云技巧实现了一个非常灵活的pipeline,展示了现代C++实现函数式编程的方法和技巧,同时也体现了现代C++的强大威力和无限可能。
概述
函数式编程是一种编程范式,它有下面的一些特征:
- 函数是一等公民,可以像数据一样传来传去。
- 高阶函数
- 递归
- pipeline
- 惰性求值
- 柯里化
- 偏应用函数
C++98/03中的函数对象,和C++11中的Lambda表达式、std::function和std::bind让C++的函数式编程变得容易。我们可以利用C++11/14里的新特性来实现高阶函数、链式调用、惰性求值和柯理化等函数式编程特性。本文将通过一些典型示例来讲解如何使用现代C++来实现函数式编程。
高阶函数和pipeline的表现形式
高阶函数就是参数为函数或返回值为函数的函数,经典的高阶函数就是map、filter、fold和compose函数,比如Scala中高阶函数:
map
numbers.map((i: Int) => i * 2)
对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。
filter
numbers.filter((i: Int) => i % 2 == 0)
移除任何对传入函数计算结果为false的元素。
fold
numbers.fold(0) { (z, i) =>
a + i
}将一个初始值和一个二元函数的结果累加起来。
compose
val fComposeG = f _ compose g _
fComposeG("x")组合其它函数形成一个新函数f(g(x))。
上面的例子中,有的是参数为函数,有的是参数和返回值都是函数。高阶函数不仅语义上更加抽象泛化,还能实现“函数是一等公民”,将函数像data一样传来传去或者组合,非常灵活。其中,compose还可以实现惰性求值,compose的返回结果是一个函数,我们可以保存起来,在后面需要的时候调用。
pipeline把一组函数放到一个数组或是列表中,然后把数据传给这个列表。数据就像一个链条一样顺序地被各个函数所操作,最终得到我们想要的结果。它的设计哲学就是让每个功能就做一件事,并把这件事做到极致,软件或程序的拼装会变得更为简单和直观。
Scala中的链式调用是这样的:
s(x) = (1 to x) |> filter (x => x % 2 == 0) |> map (x => x * 2)
用法和Unix Shell的管道操作比较像,|前面的数据或函数作为|后面函数的输入,顺序执行直到最后一个函数。
这种管道方式的函数调用让逻辑看起来更加清晰明了,也非常灵活,允许你将多个高阶函数自由组合成一个链条,同时还可以保存起来实现惰性求值。现代C++实现这种pipeline也是比较容易的,下面来讲解如何充分借助C++11/14的新特性来实现这些高阶函数和pipeline。
实现pipeline的关键技术
根据前面介绍的pipeline表现形式,可以把pipeline分解为几部分:高阶函数,惰性求值,运算符|、柯里化和pipeline,把这几部分实现之后就可以组成一个完整的pipeline了。下面来分别介绍它们的实现技术。
高阶函数
函数式编程的核心就是函数,它是一等公民,最灵活的函数就是高阶函数,现代C++的算法中已经有很多高阶函数了,比如for_each, transform:
std::vector<int> vec{1,2,3,4,5,6,7,8,9}
//接受一个打印的Lambda表达式
std::for_each(vec.begin(), vec.end(), [](auto i){ std::cout<<i<<std::endl; });
//接受一个转换的Lambda表达式
transform(vec.begin(), vec.end(), vec.begin(), [](int i){ return i*i; });
这些高阶函数不仅可以接受Lambda表达式,还能接受std::function、函数对象、普通的全局函数,很灵活。需要注意的是,普通的全局函数在pipeline时存在局限性,因为它不像函数对象一样可以保存起来延迟调用,所以我们需要一个方法将普通的函数转换为函数对象。std::bind也可以将函数转化为函数对象,但是bind不够通用,使用的时候它只能绑定有限的参数,如果函数本身就是可变参数的就无法bind了,所以,这个函数对象必须是泛化的,类似于这样:
class universal_functor
{
public:
template <typename... Args> auto operator()(Args&&... args) const ->decltype(globle_func(std::forward<Args>(args)...))
{
return globle_func(std::forward<Args>(args)...);
}
};
上面的函数对象内部包装了一个普通函数的调用,当函数调用的时候实际上会调用普通函数globle_func,但是这个代码不通用,它无法用于其他的函数。为了让这个转换变得通用,我们可以借助一个宏来实现function到functor的转换。
#define define_functor_type(func_name) class tfn_##func_name {\
public: template <typename... Args> auto operator()(Args&&... args) const ->decltype(func_name(std::forward<Args>(args)...))\
{ return func_name(std::forward<Args>(args)...); } }
//test code
int add(int a, int b)
{
return a + b;
}
int add_one(int a)
{
return 1 + a;
}
define_functor_type(add);
define_functor_type(add_one);
int main()
{
tnf_add add_functor;
add_functor(1, 2); //result is 3
tfn_add_one add_one_functor;
add_one_functor(1); //result is 2
return 0;
}
我们先定义了一个宏,这个宏根据参数来生成一个可变参数的函数对象,这个函数对象的类型名为tfn_加普通函数的函数名,之所以要加一个前缀tfn_,是为了避免类型名和函数名重名。define_functor_type宏只是定义了一个函数对象的类型,用起来略感不便,还可以再简化一下,让使用更方便。我们可以再定义一个宏来生成转换后的函数对象:
#define make_globle_functor(NAME, F) const auto NAME = define_functor_type(F);
//test code
make_globle_functor(fn_add, add);
make_globle_functor(fn_add_one, add_one);
int main()
{
fn_add(1, 2);
fn_add_one(1);
return 0;
}
make_globle_functor生成了一个可以直接使用的全局函数对象,使用起来更方便了。用这个方法就可以将普通函数转成pipeline中的函数对象了。接下来我们来探讨实现惰性求值的关键技术。
惰性求值
惰性求值是将求值运算延迟到需要值时候进行,通常的做法是将函数或函数的参数保存起来,在需要的时候才调用函数或者将保存的参数传入函数实现调用。现代C++里已经提供可以保存起来的函数对象和lambda表达式,因此需要解决的问题是如何将参数保存起来,然后在需要的时候传给函数实现调用。我们可以借助std::tuple、type_traits和可变模版参数来实现目标。
template<typename F, size_t... I, typename ... Args>
inline auto tuple_apply_impl(const F& f, const std::index_sequence<I...>&, const std::tuple<Args...>& tp)
{
return f(std::get<I>(tp)...);
}
template<typename F, typename ... Args>
inline auto tuple_apply(const F& f, const std::tuple<Args...>& tp) -> decltype(f(std::declval<Args>()...))
{
return tuple_apply_impl(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
}
int main()
{
//test code
auto f = [](int x, int y, int z) { return x + y - z; };
//将函数调用需要的参数保存到tuple中
auto params = make_tuple(1, 2, 3);
//将保存的参数传给函数f,实现函数调用
auto result = tuple_apply(f, params); //result is 0
return 0;
}
上面的测试代码中,我们先把参数保存到一个tuple中,然后在需要的时候将参数和函数f传入tuple_apply,最终实现了f函数的调用。tuple_apply实现了一个“魔法”将tuple变成了函数的参数,来看看这个“魔法”具体是怎么实现的。
tuple_apply_impl实现的关键是在于可变模版参数的展开,可变模版参数的展开又借助了std::index_sequence
运算符operator|
pipeline的一个主要表现形式是通过运算符|来将data和函数分隔开或者将函数和函数组成一个链条,比如像下面的unix shell命令:
ps auwwx | awk '{print $2}' | sort -n | xargs echo
C++实现类似的调用可以通过重载运算符来实现,下面是data和函数通过|连接的实现代码:
template<typename T, class F>
auto operator|(T&& param, const F& f) -> decltype(f(std::forward<T>(param)))
{
return f(std::forward<T>(param));
}
//test code
auto add_one = [](auto a) { return 1 + a; };
auto result = 2 | add_one; //result is 3
除了data和函数通过|连接之外,还需要实现函数和函数通过|连接,我们通过可变参数来实现:
template<typename... FNs, typename F>
inline auto operator|(fn_chain<FNs...> && chain, F&& f)
{
return chain.add(std::forward<F>(f));
}
//test code
auto chain = fn_chain<>() | (filter >> [](auto i) { return i % 2 == 0; }) | ucount | uprint;
其中fn_chain是一个可以接受任意个函数的函数对象,它的实现将在后面介绍。通过|运算符重载我们可以实现类似于unix shell的pipeline表现形式。
柯里化
函数式编程中比较灵活的一个地方就是柯里化(currying),柯里化是把多个参数的函数变换成单参数的函数,并返回一个新函数,这个新函数处理剩下的参数。以Scala的柯里化为例:
- 未柯里化的函数
def add(x:Int, y:Int) = x + y
add(1, 2) // 3
add(7, 3) // 10
- 柯里化之后
def add(x:Int) = (y:Int) => x + y
add(1)(2) // 3
add(7)(3) // 10
currying之后add(1)(2)等价于add(1,2),这种currying默认是从左到右的,如果希望从右到左呢,然而大部分编程语言没有实现更灵活的curring。C++11里面的std::bind可以实现currying,但要实现向左或向右灵活的currying比较困难,可以借助tuple和前面介绍的tuple_apply来实现一个更灵活的currying函数对象。
template<typename F, typename Before = std::tuple<>, typename After = std::tuple<>>
class curry_functor {
private:
F f_; ///< main functor
Before before_; ///< curryed arguments
After after_; ///< curryed arguments
public:
curry_functor(F && f) : f_(std::forward<F>(f)), before_(std::tuple<>()), after_(std::tuple<>()) {}
curry_functor(const F & f, const Before & before, const After & after) : f_(f), before_(before), after_(after) {}
template <typename... Args>
auto operator()(Args... args) const -> decltype(tuple_apply(f_, std::tuple_cat(before_, make_tuple(args...), after_)))
{
// execute via tuple
return tuple_apply(f_, std::tuple_cat(before_, make_tuple(std::forward<Args>(args)...), after_));
}
// currying from left to right
template <typename T>
auto curry_before(T && param) const
{
using RealBefore = decltype(std::tuple_cat(before_, std::make_tuple(param)));
return curry_functor<F, RealBefore, After>(f_, std::tuple_cat(before_, std::make_tuple(std::forward<T>(param))), after_);
}
// currying from righ to left
template <typename T>
auto curry_after(T && param) const
{
using RealAfter = decltype(std::tuple_cat(after_, std::make_tuple(param)));
return curry_functor<F, Before, RealAfter>(f_, before_, std::tuple_cat(after_, std::make_tuple(std::forward<T>(param))));
}
};
template <typename F>
auto fn_to_curry_functor(F && f)
{
return curry_functor<F>(std::forward<F>(f));
}
//test code
void test_count()
{
auto f = [](int x, int y, int z) { return x + y - z; };
auto fn = fn_to_curry_functor(f);
auto result = fn.curry_before(1)(2, 3); //0
result = fn.curry_before(1).curry_before(2)(3); //0
result = fn.curry_before(1).curry_before(2).curry_before(3)(); //0
result = fn.curry_before(1).curry_after(2).curry_before(3)(); //2
result = fn.curry_after(1).curry_after(2).curry_before(2)(); //1
}
从测试代码中可以看到这个currying函数对象,既可以从左边currying又可以从右边currying,非常灵活。不过使用上还不太方便,没有fn(1)(2)(3)这样方便,我们可以通过运算符重载来简化书写,由于C++标准中不允许重载全局的operater()符,并且operater()符无法区分到底是从左边还是从右边currying,所以我们选择重载<<和>>操作符来分别表示从左至右currying和从右至左currying。
// currying from left to right
template<typename UF, typename Arg>
auto operator<<(const UF & f, Arg && arg) -> decltype(f.template curry_before<Arg>(std::forward<Arg>(arg)))
{
return f.template curry_before<Arg>(std::forward<Arg>(arg));
}
// currying from right to left
template<typename UF, typename Arg>
auto operator>>(const UF & f, Arg && arg) -> decltype(f.template curry_after<Arg>(std::forward<Arg>(arg)))
{
return f.template curry_after<Arg>(std::forward<Arg>(arg));
}
有了这两个重载运算符,测试代码可以写得更简洁了。
void test_currying()
{
auto f = [](int x, int y, int z) { return x + y - z; };
auto fn = fn_to_curry_functor(f);
auto result = (fn << 1)(2, 3); //0
result = (fn << 1 << 2)(3); //0
result = (fn << 1 << 2 << 3)(); //0
result = (fn << 1 >> 2 << 3)(); //2
result = (fn >> 1 >> 2 << 3)(); //1
}
curry_functor利用了tuple的特性,内部有两个空的tuple,一个用来保存left currying的参数,一个用来保存right currying的参数,不断地currying时,通过tuple_cat把新currying的参数保存到tuple中,最后调用的时候将tuple成员和参数组成一个最终的tuple,然后通过tuple_apply实现调用。有了前面这些基础设施之后我们实现pipeline也是水到渠成。
pipeline
通过运算符|重载,我们可以实现一个简单的pipeline:
template<typename T, class F>
auto operator|(T&& param, const F& f) -> decltype(f(std::forward<T>(param)))
{
return f(std::forward<T>(param));
}
//test code
void test_pipe()
{
auto f1 = [](int x) { return x + 3; };
auto f2 = [](int x) { return x * 2; };
auto f3 = [](int x) { return (double)x / 2.0; };
auto f4 = [](double x) { std::stringstream ss; ss << x; return ss.str(); };
auto f5 = [](string s) { return "Result: " + s; };
auto result = 2|f1|f2|f3|f4|f5; //Result: 5
}
这个简单的pipeline虽然可以实现管道方式的链式计算,但是它只是将data和函数通过|连接起来了,还没有实现函数和函数的连接,并且是立即计算的,没有实现延迟计算。因此我们还需要实现通过|连接函数,从而实现灵活的pipeline。我们可以通过一个function chain来接受任意个函数并组成一个函数链。利用可变模版参数、tuple和type_traits就可以实现了。
template <typename... FNs>
class fn_chain {
private:
const std::tuple<FNs...> functions_;
const static size_t TUPLE_SIZE = sizeof...(FNs);
template<typename Arg, std::size_t I>
auto call_impl(Arg&& arg, const std::index_sequence<I>&) const ->decltype(std::get<I>(functions_)(std::forward<Arg>(arg)))
{
return std::get<I>(functions_)(std::forward<Arg>(arg));
}
template<typename Arg, std::size_t I, std::size_t... Is>
auto call_impl(Arg&& arg, const std::index_sequence<I, Is...>&) const ->decltype(call_impl(std::get<I>(functions_)(std::forward<Arg>(arg)), std::index_sequence<Is...>{}))
{
return call_impl(std::get<I>(functions_)(std::forward<Arg>(arg)), std::index_sequence<Is...>{});
}
template<typename Arg>
auto call(Arg&& arg) const-> decltype(call_impl(std::forward<Arg>(arg), std::make_index_sequence<sizeof...(FNs)>{}))
{
return call_impl(std::forward<Arg>(arg), std::make_index_sequence<sizeof...(FNs)>{});
}
public:
fn_chain() : functions_(std::tuple<>()) {}
fn_chain(std::tuple<FNs...> functions) : functions_(functions) {}
// add function into chain
template< typename F >
inline auto add(const F& f) const
{
return fn_chain<FNs..., F>(std::tuple_cat(functions_, std::make_tuple(f)));
}
// call whole functional chain
template <typename Arg>
inline auto operator()(Arg&& arg) const -> decltype(call(std::forward<Arg>(arg)))
{
return call(std::forward<Arg>(arg));
}
};
// pipe function into functional chain via | operator
template<typename... FNs, typename F>
inline auto operator|(fn_chain<FNs...> && chain, F&& f)
{
return chain.add(std::forward<F>(f));
}
#define tfn_chain fn_chain<>()
//test code
void test_pipe()
{
auto f1 = [](int x) { return x + 3; };
auto f2 = [](int x) { return x * 2; };
auto f3 = [](int x) { return (double)x / 2.0; };
auto f4 = [](double x) { std::stringstream ss; ss << x; return ss.str(); };
auto f5 = [](string s) { return "Result: " + s; };
auto compose_fn = tfn_chain|f1|f2|f3|f4|f5; //compose a chain
compose_fn(2); // Result: 5
}
测试代码中用一个fn_chain和运算符|将所有的函数组合成了一个函数链,在需要的时候调用,从而实现了惰性求值。
fn_chain的实现思路是这样的:内部有一个std::tuple
template<typename Arg, std::size_t I>
auto call_impl(Arg&& arg, const std::index_sequence<I>&) const ->decltype(std::get<I>(functions_)(std::forward<Arg>(arg)))
{
return std::get<I>(functions_)(std::forward<Arg>(arg));
}
template<typename Arg, std::size_t I, std::size_t... Is>
auto call_impl(Arg&& arg, const std::index_sequence<I, Is...>&) const ->decltype(call_impl(std::get<I>(functions_)(std::forward<Arg>(arg)), std::index_sequence<Is...>{}))
{
return call_impl(std::get<I>(functions_)(std::forward<Arg>(arg)), std::index_sequence<Is...>{});
}
在调用call_impl的过程中,将std::index_sequence不断展开,先从tuple中获取第I个function,然后调用获得第I个function的执行结果,将这个执行结果作为下次调用的参数,不断地递归调用,直到最后一个函数完成调用为止,返回最终的链式调用的结果。
至此我们实现具备惰性求值、高阶函数和currying特性的完整的pipeline,有了这个pipeline,我们可以实现经典的流式计算和AOP,接下来我们来看看如何利用pipeline来实现流式的mapreduce和灵活的AOP。
实现一个pipeline形式的mapreduce和AOP
前面的pipeline已经可以实现链式调用了,要实现pipeline形式的mapreduce关键就是实现map、filter和reduce等高阶函数。下面是它们的具体实现:
// MAP
template <typename T, typename... TArgs, template <typename...>class C, typename F>
auto fn_map(const C<T, TArgs...>& container, const F& f) -> C<decltype(f(std::declval<T>()))>
{
using resultType = decltype(f(std::declval<T>()));
C<resultType> result;
for (const auto& item : container)
result.push_back(f(item));
return result;
}
// REDUCE (FOLD)
template <typename TResult, typename T, typename... TArgs, template <typename...>class C, typename F>
TResult fn_reduce(const C<T, TArgs...>& container, const TResult& startValue, const F& f)
{
TResult result = startValue;
for (const auto& item : container)
result = f(result, item);
return result;
}
// FILTER
template <typename T, typename... TArgs, template <typename...>class C, typename F>
C<T, TArgs...> fn_filter(const C<T, TArgs...>& container, const F& f)
{
C<T, TArgs...> result;
for (const auto& item : container)
if (f(item))
result.push_back(item);
return result;
}
这些高阶函数还需要转换成支持currying的functor,前面我们已经定义了一个普通的函数对象转换为柯里化的函数对象的方法:
template <typename F>
auto fn_to_curry_functor(F && f)
{
return curry_functor<F>(std::forward<F>(f));
}
通过下面这个宏让currying functor用起来更简洁:
#define make_globle_curry_functor(NAME, F) define_functor_type(F); const auto NAME = fn_to_curry_functor(tfn_##F());
make_globle_curry_functor(map, fn_map);
make_globle_curry_functor(reduce, fn_reduce);
make_globle_curry_functor(filter, fn_filter);
我们定义了map、reduce和filter支持柯里化的三个全局函数对象,接下来我们就可以把它们组成一个pipeline了。
void test_pipe()
{
//test map reduce
vector<string> slist = { "one", "two", "three" };
slist | (map >> [](auto s) { return s.size(); })
| (reduce >> 0 >> [](auto a, auto b) { return a + b; })
| [](auto a) { cout << a << endl; };
//test chain, lazy eval
auto chain = tfn_chain | (map >> [](auto s) { return s.size(); })
| (reduce >> 0 >> [](auto a, auto b) { return a + b; })
| ([](int a) { std::cout << a << std::endl; });
slist | chain;
}
上面的例子实现了pipeline的mapreduce,这个pipeline支持currying还可以任意组合,非常方便和灵活。
有了这个pipeline,实现灵活的AOP也是很容易的:
struct person
{
person get_person_by_id(int id)
{
this->id = id;
return *this;
}
int id;
std::string name;
};
void test_aop()
{
const person& p = { 20, "tom" };
auto func = std::bind(&person::get_person_by_id, &p, std::placeholders::_1);
auto aspect = tfn_chain | ([](int id) { cout << "before"; return id + 1; })
| func
| ([](const person& p) { cout << "after" << endl; });
aspect(1);
}
上面的测试例子中,核心逻辑是func函数,我们可以在func之前或之后插入切面逻辑,切面逻辑可以不断地加到链条前面或者后面,实现很巧妙,使用很常灵活。
总结
本文通过介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代C++的新特性和一些模版元技巧实现了一个非常灵活的pipeline,展示了现代C++实现函数式编程的方法和技巧,同时也提现了现代C++的强大威力和无限的可能性。文中完整的代码可以从我的GitHub(https://github.com/qicosmos/cosmos/blob/master/modern_functor.hpp)上查看。
本文的代码和思路参考和借鉴了http://vitiy.info/templates-as-first-class-citizens-in-cpp11/,在此表示感谢。
2016 年 9 月 23-24 日,由 CSDN 和创新工场联合主办的“MDCC 2016 移动开发者大会• 中国”(Mobile Developer Conference China)将在北京• 国家会议中心召开,来自 iOS、Android、跨平台开发、产品设计、VR 开发、移动直播、人工智能、物联网、硬件开发、信息无障碍10个领域的技术专家将分享他们在各自行业的真知灼见。
从 8 月 8 日起至 9 月 4 日,MDCC 大会门票处于 6.8 折优惠票价阶段,五人以上团购更有特惠,限量供应(票务详情链接,6.8折优惠,欲购从速!)
(转)现代C++函数式编程的更多相关文章
- angular2系列教程(六)两种pipe:函数式编程与面向对象编程
今天,我们要讲的是angualr2的pipe这个知识点. 例子
- [学习笔记]JavaScript之函数式编程
欢迎指导与讨论:) 前言 函数式编程能使我们的代码结构变得简洁,让代码更接近于自然语言,易于理解. 一.减少不必要的函数嵌套代码 (1)当存在函数嵌套时,若内层函数的参数与外层函数的参数一致时,可以这 ...
- 函数式编程之柯里化(curry)
函数式编程curry的概念: 只传递给函数一部分参数来调用函数,然后返回一个函数去处理剩下的参数. var add = function(x) { return function(y) { retur ...
- 关于Java8函数式编程你需要了解的几点
函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异. 函数作为一等公民 在理解函数作为一等公民这句话时,让我们先来看一下一种非常常 ...
- Haskell 函数式编程快速入门【草】
什么是函数式编程 用常规编程语言中的函数指针.委托和Lambda表达式等概念来帮助理解(其实函数式编程就是Lambda演算延伸而来的编程范式). 函数式编程中函数可以被非常容易的定义和传递. Hask ...
- java1.8函数式编程概念
有关函数式编程 ·1 函数作为一等公民 特点:将函数作为参数传递给另外一个函数:函数可以作为另外一个函数的返回值 ·2 无副作用 函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外 ...
- 让JavaScript回归函数式编程的本质
JavaScript是一门被误会最深的语言,这话一点不假,我们看下它的发展历史. 1995年,Netscape要推向市场,需要一门脚本语言来配套它.是使用一门已有的语言,还是发明一门新的语言,这也不是 ...
- python基础-函数式编程
python基础-函数式编程 高阶函数:map , reduce ,filter,sorted 匿名函数: lambda 1.1函数式编程 面向过程编程:我们通过把大段代码拆成函数,通过一层一层 ...
- python函数 与 函数式编程
「函数」一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,具体区别,我们后面会讲,编程中的函数在英文中也有很多不同的叫法.在BASIC中叫做subroutine(子过程或子程序), ...
- Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程
Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程 这里的函数式编程的设计以muduo为例进行对比说明: Reactor实现架构对比 面向对象的设计类图如下: 函数式编程以muduo为例 ...
随机推荐
- iOS闪烁效果工具 GlitchLabel
GlitchLabel 用于给文字添加闪烁效果,效果图如下: 开发环境: iOS 7.0+ Swift 2.2 Xcode 7 示例代码:https://www.yuedecaifu.com 1 2 ...
- adaboost算法
三 Adaboost 算法 AdaBoost 是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器,即弱分类器,然后把这些弱分类器集合起来,构造一个更强的最终分类器.(很多博客里说的三个臭皮匠 ...
- Wythoff's game
这个问题就是OJ题里出现的取石子游戏,http://en.wikipedia.org/wiki/Wythoff%27s_game. 维基里面的通项公式并不适用于算法求解.需要理解下面两条规律: 1.A ...
- Emacs常用命令
1.离开Emacs 挂起Emacs C-z 退出Emacs C-x C-c 2.文件 打开文件 C-x C-f 保存文件 C-x C-s 保存所有的文件 C-x s 将一个文件的内容插入到当前buff ...
- iOS App 转移
此文章只是为了记录一个Apple ID下的APP,转移到另外一个Apple ID 账户下.为了说的清楚下面用A账户(有App,要转出去)B账户(接收A账户App,接收者),来说明. 1. 登 ...
- html释疑
解析<button>和<input type="button"> 的区别(转) 一.定义和用法 <button> 标签定义的是一个按钮. 在 b ...
- Android启动组件的三种主流及若干非主流方式
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处 启动组件的一些方式,今天做一个简要的总结 Service:通过startService()启动,或者写 ...
- C++类的运用 和 三大函数
在<数据结构与算法分析C++描述>一书中给出了三段代码,简单描述了C++类的接口.实现.与调用: #ifndef INTCELL_H_INCLUDED #define INTCELL_H_ ...
- CODEVS1073 家族 (并查集)
一道裸的并查集,练练手不错. program CODEVS1073; var i,j,m,n,q,x,y,k1,k2,z:longint; f:..] of longint; function fin ...
- VS2010命令行编译C#和VC项目
VS2010命令行编译C#和VC项目 VS2010命令行编译C#和VC项目 根据需要动态创建数据库字段后,需要动态创建或者调整页面,那就需要编译这些页面和后台文件.因此使用命令行编译将会非常方便,对于 ...