在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

狀態機編程實例-狀態表法

碼農愛學習 ? 來源:碼農愛學習 ? 作者:碼農愛學習 ? 2023-06-20 09:05 ? 次閱讀

上篇文章,使用嵌套switch-case法的狀態機編程,實現了一個炸彈拆除小游戲。

本篇,繼續介紹狀態機編程的第二種方法:狀態表法,來實現炸彈拆除小游戲的狀態機編程。

1 狀態表法

狀態表法,顧名思義,就是通過一個狀態表,來實現狀態機中的狀態轉換,下面就先介紹下狀態表的基礎知識。

1.1 狀態表

狀態表 ,最常用的是使用一個2維狀態表:

  • 水平方向是各個事件
  • 豎直方向是各個狀態
  • 單元的內容是通過(執行動作,下一狀態)來表示各種轉換關系

結合上一篇設計炸彈拆除小游戲的狀態圖(2個狀態和4個事件):

可以設計出對應的狀態表,如下圖:

  • 水平方向的4種事件:UP、DOWN和ARM按鍵事件,TICK事件
  • 豎直方向的2種狀態:設置狀態和倒計時狀態
  • 單元的內容表示執行指定動作后,下一狀態是什么。比如設置狀態時按下UP鍵,執行setting_UP函數中的動作后,下一狀態還是留在設置狀態

注意:

  • (*):僅當(me->code == me->defuse),即密碼輸入正確時,才進行狀態轉換至“設置狀態”
  • ):僅當(me->fine_time == 0)和(me->timeout != 0),即每過一秒且倒計時未減到0時,才進行狀態轉換至“倒計時狀態”**

1.2 事件處理器

由于狀態表法可以使用一個非常有規律的數據結構(狀態表)來表現一個狀態機,因此編程時可以編寫一個通用的“事件處理器”來實現狀態機功能。

如下圖,通用的狀態表事件處理器,包含兩個主要結構:

  • 一個外部轉換的StateTable結構
  • 一個帶有事件參數和沒有事件參數的Event結構

此外,StateTable結構有兩個相關的函數:

  • init()函數用于觸發狀態機的初始轉換
  • dispatch()函數用于派送一個事件給狀態機處理

需體會的是,StateTable結構是一個抽象的結構,按照UML類圖的畫法,這是一個抽象類(使用《abstract》或斜體類名表示),需要通過派生出一個實例類,如圖中的Bomb2,來實現具體的業務功能。

在狀態機的應用程序中,狀態表僅包含執行轉換函數的指針,即函數指針,而不是(執行動作,下一狀態)的形式,使用這種方式,實際就是把狀態改變的邏輯,放到了轉換函數中,這樣做,使得編程更加靈活,因為狀態函數能方便地判斷某些監護條件并隨之改變。

2 狀態表法的實現

上面介紹了狀態表法的基礎知識,下面就來通過代碼來介紹狀態表法的具體實現。

2.1 通用狀態表事件處理器

上面說到,狀態表法可以使用一個非常有規律的狀態表數據結構來表現一個狀態機,因而在程序設計時,可以編寫一個通用的狀態表事件處理器。

2.1.1 接口定義

通用的狀態表事件處理器,先來通過接口定義,看下它的功能。

注意上面提到的它包含兩個主要結構:

  • 一個外部轉換的StateTable結構
  • 一個帶有事件參數和沒有事件參數的Event結構

以及StateTable結構的兩個相關的函數:

  • init()函數:用于觸發狀態機的初始轉換
  • dispatch()函數:用于派送一個事件給狀態機處理
// 用于進行狀態轉換的宏
#define TRAN(target) (((StateTable *)me)- >state = (uint8_t)(target))
?
typedef struct EventTag
{
  uint16_t sig; // 事件的信號
} Event;
?
struct StateTableTag; //提前聲明此變量
?
// 函數指針
typedef void (*Tran)(struct StateTableTag *me, Event const *e);
?
// 狀態表數據結構
typedef struct StateTableTag
{
  uint8_t state;           //當前狀態
  Tran const *state_table; //狀態表
  uint8_t n_states;        //狀態的個數
  uint8_t n_signals;       //事件(信號)的個數
  Tran initial;            //初始轉換
} StateTable;
?
void StateTable_ctor(StateTable *me, Tran const *table, uint8_t n_states, uint8_t n_signals, Tran initial);
void StateTable_init(StateTable *me);
void StateTable_dispatch(StateTable *me, Event const *e);
void StateTable_empty(StateTable *me, Event const *e);

StateTable_ctor是狀態表的“構造函數”,僅指向一個基本的初始化動作,不會觸發初始轉換。

StateTable_empty是一個默認的空動作,用于狀態表初始化時,某些需要空單元的地方使用。

另外,這里還要體會函數指針的用法。什么是函數指針,下面再來復習一下。

2.1.2 體會函數指針的用法

函數指針,本質是一個指針,其指向的一個函數,其類型定義為:

返回值類型 (* 函數名) ([形參列表]);

注意和指針函數的區別:

何為指針函數?

*指針函數,本質是一個函數,例如 int pfun(int, int); 其返回值是指針類型,即返回一個指針(或稱地址),這個指針指向的數據是什么類型都可以。

一個記憶小技巧:指針函數,可以類比int函數,它們都是函數,只是返回值不一樣,一個是返回指針,一個返回int。

首先來看函數指針的定義,以及基礎用法:

//定義一個函數指針pFUN,它指向一個返回類型為void,有一個參數類型為int的函數
void (*pFun)(int);
?
//定義一個返回類型為void,參數為int的函數。從指針層面上理解該函數,其函數名實際上是一個指針,該指針指向函數在內存中的首地址
void glFun(int a)
{
    printf("%d
", a);
}
?
int main()
{
    pFun = glFun; //將函數glFun的地址賦值給變量pFun
    (*pFun)(2);//“*pFun”是取pFun所指向地址的內容,即取出了函數glFun()的內容,然后給定參數為2
    
    return 0;
}

實際使用時,常常通過typedef的方式讓函數指針更直觀方便的進行使用:

//定義新的類型PTRFUN, 此類型的實際含義為函數指針,指向的函數的返回值是void,參數是int
typedef void (*PTRFUN)(int); 
?
//定義一個返回類型為void,參數為int的函數
void glFun(int a)
{ 
    printf("%d
", a);
} 
?
int main() 
{ 
    PTRFUN pFun; //使用定義的(函數指針)類型,實例化一個函數指針
    
    pFun = glFun; //把定義的glFun函數,以函數名(本質即指針)的形式為其賦值
    (*pFun)(2); //執行該函數指針指向的內容,即指向指向的函數,并指定參數2
    
    return 0;
}

關于函數指針的實際應用,也可參考我之前的這篇文章: STM32簡易多級菜單(數組查表法)

2.1.3 具體實現

看完了通用的狀態表事件處理器的接口定義,下面再來看下具體實現。

//狀態表的構造
void StateTable_ctor(StateTable *me,
                     Tran const *table, uint8_t n_states, uint8_t n_signals,
                     Tran initial)
{
    //第一個參數me為StateTable結構,由具體業務的派生狀態表的tateTable結構傳入
    me- >state_table = table;   //狀態表, 由具體業務的二維狀態表傳入
    me- >n_states = n_states;   //二維狀態表的狀態數量
    me- >n_signals = n_signals; //二維狀態表的信號(事件)數量
    me- >initial = initial;     //狀態表的初始準換函數
}
?
//狀態表的初始化
void StateTable_init(StateTable *me)
{
    me- >state = me- >n_states;
    (*me- >initial)(me, (Event *)0); //初始轉換
?
    assert(me- >state < me- >n_states); //確保事件范圍的合理
}
?
//狀態表的調度(派送一個事件給狀態機處理)
void StateTable_dispatch(StateTable *me, Event const *e)
{
    Tran t;
?
    assert(e- >sig < me- >n_signals); //確保信號范圍的合理
?
    //通過當前狀態與當前的信號,以及信號的總數,計算得到狀態表中要執行的轉換函數在狀態表(二維的函數指針數組)中的位置
    t = me- >state_table[me- >state * me- >n_signals + e- >sig];
    (*t)(me, e); //然后執行轉換函數
?
    assert(me- >state < me- >n_states); //確保狀態范圍的合理
}
?
//狀態表的空元素
void StateTable_empty(StateTable *me, Event const *e)
{
    (void)me; //用于消除參數未使用的警告
    (void)e;  
}

這里要體會一下狀態表的調度,即派送一個事件給狀態機處理的代碼邏輯,StateTable_dispatch的兩個參數,一個是StateTable結構的二維表,一個是Event結構的信號(事件),注意這個二維狀態表,存儲的函數指針(各種轉換函數),所以是一個二維的函數指針數組,根據信號,如何知道要執行二維數組中的哪個函數呢?還要借助當前狀態機所處的狀態,即可通過簡單的數學運算得出,示意如下圖:

2.2 應用邏輯(具體業務代碼)

看完了通用的狀態表事件處理器,就可以在此基礎上,編寫具體的狀態機業務代碼,實現上一篇介紹的炸彈拆除小游戲。

2.2.1 接口定義

還是先看下炸彈拆除小游戲這個具體業務邏輯用到的數據結構與接口定義,主要包括:

  • 炸彈狀態機的狀態與信號(事件)
  • 從狀態表事件處理器的Event結構派生的帶有事件參數的TickEvt結構
  • 從狀態表事件處理器的StateTable結構派生的具體的炸彈狀態機數據結構
  • 狀態表中用到的所有的轉換函數
// 炸彈狀態機的所有狀態
enum BombStates
{
  SETTING_STATE, // 設置狀態
  TIMING_STATE,   // 倒計時狀態
  STATE_MAX
};
?
// 炸彈狀態機的所有信號(事件)
enum BombSignals
{
  UP_SIG,   // UP鍵信號
  DOWN_SIG, // DOWN鍵信號
  ARM_SIG,  // ARM鍵信號
  TICK_SIG,  // Tick節拍信號
  SIG_MAX
};
?
typedef struct TickEvtTag
{
  Event super;       // 派生自Event結構
  uint8_t fine_time; // 精細的1/10秒計數器
} TickEvt;
?
// 炸彈狀態機數據結構
typedef struct Bomb2Tag
{
  StateTable super; // 派生自StateTable結構
  uint8_t timeout; // 爆炸前的秒數
  uint8_t code;    // 當前輸入的解除炸彈的密碼
  uint8_t defuse;  // 解除炸彈的拆除密碼
  uint8_t errcnt;  // 當前拆除失敗的次數
} Bomb2;
?
//炸彈構造
void Bomb2_ctor(Bomb2 *me, uint8_t defuse);
//狀態表中需要用到的轉換函數(函數指針)
void Bomb2_initial(Bomb2 *me, Event const *e);      //初始轉換
void Bomb2_setting_UP(Bomb2 *me, Event const *e);   //轉換函數, 設置狀態時, 處理UP事件
void Bomb2_setting_DOWN(Bomb2 *me, Event const *e); //轉換函數, 設置狀態時, 處理DOWN事件
void Bomb2_setting_ARM(Bomb2 *me, Event const *e);  //轉換函數, 設置狀態時, 處理ARM事件
void Bomb2_timing_UP(Bomb2 *me, Event const *e);    //轉換函數, 倒計時狀態時, 處理UP事件
void Bomb2_timing_DOWN(Bomb2 *me, Event const *e);  //轉換函數, 倒計時狀態時, 處理DOWN事件
void Bomb2_timing_ARM(Bomb2 *me, Event const *e);   //轉換函數, 倒計時狀態時, 處理ARM事件
void Bomb2_timing_TICK(Bomb2 *me, Event const *e);  //轉換函數, 倒計時狀態時, 處理Tick事件

2.2.2 具體實現

1)炸彈構造與初始化

炸彈構造與初始化的實現如下

//炸彈構造
void Bomb2_ctor(Bomb2 *me, uint8_t defuse)
{
  //定義炸彈狀態機的狀態表(函數指針構成的二維數組)
  static const Tran bomb2_state_table[STATE_MAX][SIG_MAX] = {
    {(Tran)&Bomb2_setting_UP, (Tran)&Bomb2_setting_DOWN, (Tran)&Bomb2_setting_ARM, &StateTable_empty},
    {(Tran)&Bomb2_timing_UP, (Tran)&Bomb2_timing_DOWN, (Tran)&Bomb2_timing_ARM, (Tran)&Bomb2_timing_TICK}
  };
?
  //使用通用的通用狀態表事件處理器構造狀態表
  StateTable_ctor(&me- >super, &bomb2_state_table[0][0], STATE_MAX, SIG_MAX, (Tran)&Bomb2_initial); 
  me- >defuse = defuse; //設置默認的拆除密碼
}
?
//炸彈初始化
void Bomb2_initial(Bomb2 *me, Event const *e)
{
  (void)e; //用于消除參數未使用的警告
  me- >timeout = INIT_TIMEOUT;
  me- >errcnt = 0;
  TRAN(SETTING_STATE); //默認進行設置狀態
}

2)各個轉換函數

各個轉換函數(函數指針)的具體實現如下,其特征為短函數

/*.................設置狀態下的事件處理函數......................*/
void Bomb2_setting_UP(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  if (me- >timeout < 60)
  {
    ++me- >timeout; //設置超時時間+1
    bsp_display_set_time(me- >timeout); //顯示設置的超時時間
  }
}
?
void Bomb2_setting_DOWN(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  if (me- >timeout > 1)
  {
    --me- >timeout; //設置超時時間-1
    bsp_display_set_time(me- >timeout); //顯示設置的超時時間
  }
}
?
void Bomb2_setting_ARM(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  me- >code = 0;
  TRAN(TIMING_STATE); /* transition to "timing" */
}
?
/*...................倒計時狀態下的事件處理函數................*/
void Bomb2_timing_UP(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  me- >code < <= 1;
  me- >code |= 1; //添加一個1
  bsp_display_user_code(me- >code);
}
?
void Bomb2_timing_DOWN(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  me- >code < <= 1; //添加一個0
  bsp_display_user_code(me- >code);
}
?
void Bomb2_timing_ARM(Bomb2 *me, Event const *e)
{
  (void)e; /* avoid compiler warning about unused parameter */
  if (me- >code == me- >defuse)
  {
    TRAN(SETTING_STATE); //轉換到設置狀態
    bsp_display_user_success(); //炸彈拆除成功
    Event *e = NULL;
    Bomb2_initial(me, e);
  }
  else
  {
    me- >code = 0;
    bsp_display_user_code(me- >code);
    bsp_display_user_err(++me- >errcnt);
  }
}
?
void Bomb2_timing_TICK(Bomb2 *me, Event const *e)
{
  if (((TickEvt const *)e)- >fine_time == 0)
  {
    --me- >timeout;
    bsp_display_remain_time(me- >timeout);
    if (me- >timeout == 0)
    {
      bsp_display_bomb(); //顯示爆炸效果
      Event *e = NULL;
      Bomb2_initial(me, e);
    }
  }
}

2.3 主函數

本篇同樣使用Arduino控制器進行測試,對應的主函數代碼如下:

static Bomb2 l_bomb;
?
void setup(void)
{
  Serial.begin(115200);
  bsp_display_init();
  bsp_display_hello();
  bsp_key_init();
?
  Bomb2_ctor(&l_bomb, 0x0D); // 構造, 密碼1101
  StateTable_init((StateTable *)&l_bomb); // 初始轉化
}
?
void loop(void)
{
  static TickEvt tick_evt = {TICK_SIG, 0};
  delay(100);
?
  if (++tick_evt.fine_time == 10)
  {
    tick_evt.fine_time = 0;
  }
?
  char tmp_buffer[256];
  sprintf(tmp_buffer, "T(%1d)%c", tick_evt.fine_time, (tick_evt.fine_time == 0) ? '
' : ' ');
  Serial.print(tmp_buffer);
?
  StateTable_dispatch((StateTable *)&l_bomb, (Event *)&tick_evt);
?
  BombSignals userSignal = bsp_key_check_signal();
  if (userSignal != SIG_MAX)
  {
    static Event const up_evt = {UP_SIG};
    static Event const down_evt = {DOWN_SIG};
    static Event const arm_evt = {ARM_SIG};
    Event const *e = (Event *)0;
?
    switch (userSignal)
    {
      case UP_SIG: //UP鍵事件
      {
        Serial.print("
UP  : ");
        e = &up_evt;
        break;
      }
      case DOWN_SIG: //DOWN鍵事件
      {
        Serial.print("
DOWN: ");
        e = &down_evt;
        break;
      }
      case ARM_SIG: //ARM鍵事件
      {
        Serial.print("
ARM : ");
        e = &arm_evt;
        break;
      }
      default:break;
    }
?
    /* keyboard event available? */
    if (e != (Event *)0)
    {
       StateTable_dispatch((StateTable *)&l_bomb, e); /* dispatch the event */
    }
  }
}

3 總結

本編介紹了狀態機編程的第2種方法——狀態表法,通過一個非常有規律的二維表數據結構,以及函數指針,實現炸彈拆除小游戲中的狀態機功能。

本篇,需要重點體會的點包括:

  • 通用的“事件處理器”的結構與功能
  • 函數指針的妙用,一般與數組查表法結合
  • 根據實際的狀態轉換與處理的業務需求,利用狀態表法,實現狀態機編程

    審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    5082

    文章

    19123

    瀏覽量

    305150
  • 編程
    +關注

    關注

    88

    文章

    3615

    瀏覽量

    93731
  • 狀態機
    +關注

    關注

    2

    文章

    492

    瀏覽量

    27539
  • 函數指針
    +關注

    關注

    2

    文章

    56

    瀏覽量

    3781
收藏 人收藏

    評論

    相關推薦

    Simulink中的狀態機建模方法 Simulink數據可視化與分析功能

    1. Simulink中的狀態機建模方法 1.1 理解狀態機的基本概念 在開始建模之前,了解狀態機的基本概念是必要的。狀態機由以下幾個部分組成:
    的頭像 發表于 12-12 09:27 ?404次閱讀

    FPGA中有狀態表項的存儲與管理

    一篇2014年的論文:《CACHE FOR FLOW CONTENT: SOLUTION TODEPENDENT PACKET PROCESSING IN FPGA》,主要講述在FPGA中有狀態表項的存儲與管理。感興趣的可以閱讀原文。
    的頭像 發表于 10-27 16:06 ?247次閱讀
    FPGA中有<b class='flag-5'>狀態表</b>項的存儲與管理

    觸發器和狀態機的關系是什么

    觸發器和狀態機在數字電路設計中有著緊密的關系,它們共同構成了時序邏輯電路的基礎,用于實現數據的存儲、處理和傳輸。
    的頭像 發表于 08-12 11:24 ?459次閱讀

    如何在FPGA中實現狀態機

    在FPGA(現場可編程門陣列)中實現狀態機是一種常見的做法,用于控制復雜的數字系統行為。狀態機能夠根據當前的輸入和系統狀態,決定下一步的動作和新的
    的頭像 發表于 07-18 15:57 ?601次閱讀

    玩轉Spring狀態機

    說起Spring狀態機,大家很容易聯想到這個狀態機和設計模式中狀態模式的區別是啥呢?沒錯,Spring狀態機就是狀態模式的一種實現,在介紹S
    的頭像 發表于 06-25 14:21 ?953次閱讀
    玩轉Spring<b class='flag-5'>狀態機</b>

    關于SMU狀態機的問題求解

    我有一些關于 SMU 狀態機的問題。 假設由于某種原因,SMU 已進入故障狀態。 手冊指出,要返回運行狀態并將 FSP 恢復到無故障狀態,應調用IfxSmu_releaseFSP()。
    發表于 05-29 08:18

    在Verilog中實現Moore型和Mealy型狀態機的方法簡析

    編寫能夠被綜合工具識別的狀態機,首先需要理解狀態機的基本概念和分類。狀態機(FSM)是表示有限個狀態以及在這些狀態之間轉換的邏輯結構。
    的頭像 發表于 05-01 11:38 ?1605次閱讀

    嵌入式編程,如何用 C 語言實現狀態機設計?

    狀態機模式是一種行為模式,通過多態實現不同狀態的調轉行為的確是一種很好的方法,只可惜在嵌入式環境下,有時只能寫純C代碼,并且還需要考慮代碼的重入和多任務請求跳轉等情形,因此實現起來著實需要一番考慮
    發表于 04-23 11:00

    求助LabVIEW,狀態機里面反饋節點如何初始化問題

    求助labview,狀態機里面反饋節點如何初始化,下次執行這個狀態的時候初始化一次!謝謝謝謝!
    發表于 03-25 18:17

    如何采用“狀態機”解析UART數據幀

    如果一個系統接收上述“不定長度”的協議幀,將會有一個挑戰--如何高效接收與解析。 為簡化系統設計,我們強烈建議您采用“狀態機”來解析UART數據幀。
    的頭像 發表于 03-25 14:29 ?697次閱讀
    如何采用“<b class='flag-5'>狀態機</b>”解析UART數據幀

    關于FX3使用4個線程進行FPGA到USB的數據傳輸-狀態機設置的問題求解

    狀態機進行測試 其中WAIT到TH0的轉移條件Buffer_Ready是一個外部輸入信號,TH0到TH3是4個線程,我在固件中為每個線程都設置了一個Bulk In endpoint,在測試時發現
    發表于 02-27 06:40

    什么是有限狀態機?如何解決傳統有限狀態機狀態爆炸」問題?

    有限狀態機(Finite State Machine,簡稱FSM)是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內所經歷的狀態序列以及如何響應來自外界的各種事件。
    的頭像 發表于 02-17 16:09 ?6247次閱讀
    什么是有限<b class='flag-5'>狀態機</b>?如何解決傳統有限<b class='flag-5'>狀態機</b>「<b class='flag-5'>狀態</b>爆炸」問題?

    Verilog狀態機+設計實例

    在verilog中狀態機的一種很常用的邏輯結構,學習和理解狀態機的運行規律能夠幫助我們更好地書寫代碼,同時作為一種思想方法,在別的代碼設計中也會有所幫助。 一、簡介 在使用過程中我們常說
    的頭像 發表于 02-12 19:07 ?4138次閱讀
    Verilog<b class='flag-5'>狀態機</b>+設計<b class='flag-5'>實例</b>

    狀態機該怎么監控

    狀態機卡住的場景——通過狀態跳轉條件的DFX信號去判斷卡住的原因
    的頭像 發表于 01-15 10:03 ?420次閱讀
    <b class='flag-5'>狀態機</b>該怎么監控

    如何對狀態指示燈進行編程

    以下步驟將介紹如何對狀態指示燈進行編程狀態指示燈指示加熱室的工作模式。當加熱室處于工作狀態時,變量 LED 的信號狀態設置為“1”并開啟
    的頭像 發表于 01-02 14:22 ?1004次閱讀
    如何對<b class='flag-5'>狀態</b>指示燈進行<b class='flag-5'>編程</b>
    主站蜘蛛池模板: 亚洲第一精品夜夜躁人人爽| 四虎影院海外永久| 三级高清| aaaa级日本片免费视频| 俄罗斯一级特黄黄大片| 亚洲三级免费观看| 国产亚洲一区二区精品| 狠狠色丁香久久婷婷综合丁香| 特一级黄色毛片| 久草视频在线免费看| 久久国内| 又粗又大又猛又爽免费视频 | 欧美一级免费观看| 又粗又硬又爽又黄毛片| qvod高清在线成人观看| 色爱综合区| 亚洲播播| 日本免费一区视频| 欧美久操| xx毛片| 能直接看黄的网站| 日本特级淫片免费看| 天天草夜夜爽| 国产成人亚洲毛片| 国产三级精品在线观看| chinesevideo普通话对白| 四虎影院久久| 国产码一区二区三区| 在线观看网站国产| 日本偷偷操| 久久草在线播放| 速度与激情10| 天天干天天干天天天天天天爽| 人人揉揉香蕉大青草| 色多多福利| 免费观看一级一片| 亚洲一区二区综合| 男人j进女人j视频| 黄色毛片免费网站| 男人和女人做爽爽视频在线观看| 伊人精品成人久久综合欧美|