日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

现代C++函数式编程

發布時間:2025/7/25 c/c++ 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 现代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 3tfn_add_one add_one_functor;add_one_functor(1); //result is 2return 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 0return 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 + yadd(1, 2) // 3 add(7, 3) // 10
  • 柯里化之后
def add(x:Int) = (y:Int) => x + yadd(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 functorBefore before_; ///< curryed argumentsAfter after_; ///< curryed argumentspublic: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 tuplereturn tuple_apply(f_, std::tuple_cat(before_, make_tuple(std::forward<Args>(args)...), after_));}// currying from left to righttemplate <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 lefttemplate <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); //0result = fn.curry_before(1).curry_before(2)(3); //0result = fn.curry_before(1).curry_before(2).curry_before(3)(); //0result = fn.curry_before(1).curry_after(2).curry_before(3)(); //2result = 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); //0result = (fn << 1 << 2)(3); //0result = (fn << 1 << 2 << 3)(); //0result = (fn << 1 >> 2 << 3)(); //2result = (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 chaintemplate< 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 chaintemplate <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 chaincompose_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 reducevector<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 evalauto 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/,在此表示感謝。


總結

以上是生活随笔為你收集整理的现代C++函数式编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 最新国产露脸在线观看 | 美日韩久久| 涩涩网站免费 | av一级网站 | 日本不卡一区二区在线观看 | 亚洲午夜剧场 | 九九九九精品九九九九 | 成人羞羞国产免费 | 国产剧情一区在线 | 欧美综合网 | 午夜激情福利在线 | 午夜久草 | 男男大尺度 | 白白色在线播放 | 精品国产九九九 | 国产良妇出轨视频在线观看 | 色综合久久久久久 | 性欧美在线视频 | 波多野结衣一本一道 | 久草国产视频 | 国产1区二区 | 亚洲综合精品国产 | 三级av网址 | 可以免费看av的网址 | 18禁一区二区 | 97超碰超碰| 日本伦理中文字幕 | 亚洲美女屁股眼交8 | 精品人妻一区二区三区潮喷在线 | 无码人妻一区二区三区线 | 被黑人猛躁10次高潮视频 | 九九视频免费 | 伊人网综合 | 黄色一级免费片 | 国产成人无码AA精品区 | 极品久久 | 熟妇人妻久久中文字幕 | 深爱五月综合网 | 成人综合社区 | 色哟哟免费视频 | 超碰h| 日韩精品色哟哟 | 91在线看片 | 高清国产一区 | 天天干天天色 | 午夜看片福利 | 五月天堂网 | 日日夜夜爱爱 | 4hu最新网址 | 狠狠插影院| 国产欧美精品一区二区三区app | 国产明星换脸xxxx色视频 | 午夜婷婷在线观看 | 女优视频在线观看 | 黄色大片久久 | 国产精品区一区二区三 | 欧美寡妇性猛交ⅹxxx | www亚洲成人 | 夫妻性生活自拍 | 日韩在线观看免费 | 国产日韩欧美在线观看视频 | 国产成人免费电影 | 少妇无内裤下蹲露大唇视频 | 国产丝袜视频在线观看 | 欧美男人又粗又长又大 | 美女网站一区 | 欧美日韩tv| 天天天天天天操 | 久久精品亚洲一区二区 | 免费观看黄色网 | 久久久久亚洲色欲AV无码网站 | 一本一道久久a久久精品蜜桃 | 久久久香蕉视频 | 欧美精品欧美极品欧美激情 | 日本欧美不卡 | 又大又粗欧美黑人aaaaa片 | 男生插女生视频在线观看 | fc2ppv色の美マンに中出し | 日韩免费高清一区二区 | 日韩精品一区二区三区视频在线观看 | 无套白嫩进入乌克兰美女 | 日韩专区在线观看 | 桃花久久| 美女一区二区三区 | xxxxx在线观看 | 婷婷精品进入 | 毛片免费一区二区三区 | 婷婷综合社区 | 欧洲av无码放荡人妇网站 | 国产二三区 | 日韩成人专区 | 色99999| 麻豆网址 | 丝袜制服中文字幕 | 毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 久久久夜夜夜 | 日本韩国在线 | 吻胸摸激情床激烈视频大胸 | 一级全黄裸体免费视频 |