百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

现代C++函数式编程 c++数学函数

liebian365 2024-10-22 15:40 22 浏览 0 评论



导读: 本文作者从介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代C++的新特性以及一些模板云技巧实现了一个非常灵活的pipeline,展示了现代C++实现函数式编程的方法和技巧,同时也体现了现代C++的强大威力和无限可能。


1

概述


函数式编程是一种编程范式,它有下面的一些特征:


  • 函数是一等公民,可以像数据一样传来传去
  • 高阶函数
  • 递归
  • pipeline
  • 惰性求值
  • 柯里化
  • 偏应用函数


C++98/03中的函数对象,和C++11中的Lambda表达式、std::function和std::bind让C++的函数式编程变得容易。我们可以利用C++11/14里的新特性来实现高阶函数、链式调用、惰性求值和柯理化等函数式编程特性。本文将通过一些典型示例来讲解如何使用现代C++来实现函数式编程。


2

高阶函数和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。


3

实现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。


4

实现一个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之前或之后插入切面逻辑,切面逻辑可以不断地加到链条前面或者后面,实现很巧妙,使用很常灵活。


5

总结


本文通过介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代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/,在此表示感谢。

免费C++ 入门教程:点击下方了解更多

相关推荐

go语言也可以做gui,go-fltk让你做出c++级别的桌面应用

大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...

旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗

这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...

codeblocks和VS2019下的fltk使用中文

在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...

FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库

FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...

中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux

IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...

Linux 5.13内核有望合并对苹果M1处理器支持的初步代码

预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...

Ubuntu系统下COM口测试教程(ubuntu port)

1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...

湖北嵌入式软件工程师培训怎么选,让自己脱颖而出

很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...

新阁上位机开发---10年工程师的Modbus总结

前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...

创建你的第一个可运行的嵌入式Linux系统-5

@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...

如何在Linux下给zigbee CC2530实现上位机

0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...

Python实现串口助手 - 03串口功能实现

 串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...

为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口

UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...

嵌入式linux为什么可以通过PC上的串口去执行命令?

1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...

取消回复欢迎 发表评论: