亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

C++箴言:考慮可選的虛擬函數的替代方法

2019-11-17 05:09:04
字體:
來源:轉載
供稿:網友

  現在你工作在一個視頻游戲上,你在游戲中為角色設計了一個 hierarchy(繼續體系)。你的游戲中有著變化多端的惡劣環境,角色被傷害或者其它的健康狀態降低的情況并不罕見。因此你決定提供一個 member function(成員函數)healthValue,它返回一個象征角色健康狀況如何的整數。因為不同的角色計算健康值的方法可能不同,將 healthValue 聲明為 virtual(虛擬)似乎是顯而易見的設計選擇:

class GameCharacter {
public:
  virtual int healthValue() const;        // return character's health rating;
  ...                                     // derived classes may redefine this
};
  healthValue 沒有被聲明為 pure virtual(純虛)的事實暗示這里有一個計算健康值的缺省算法。

  這確實是一個顯而易見的設計選擇,而在某種意義上,這是它的缺點。因為這樣的設計過于顯而易見,你可能不會對它的其它可選方法給予足夠的關注。為了幫助你脫離 object-oriented design(面向對象設計)的習慣性道路,我們來考慮一些處理這個問題的其它方法。

  經由非虛擬接口慣用法實現的模板方法模式

  我們以一個主張 virtual functions(虛擬函數)應該幾乎總是為 PRivate(私有的)的有趣觀點開始。這一觀點的擁護者提出:一個較好的設計應該保留作為 public member function(公有成員函數)的 healthValue,但應將它改為 non-virtual(非虛擬的)并讓它調用一個 private virtual function(私有虛擬函數)來做真正的工作,也就是說,doHealthValue:

class GameCharacter {
public:
  int healthValue() const               // derived classes do not redefine
  {                                     // this - see Item 36

    ...                                 // do "before" stuff - see below

    int retVal = doHealthValue();       // do the real work

    ...                                 // do "after" stuff - see below

    return retVal;
  }
  ...

private:
  virtual int doHealthValue() const     // derived classes may redefine this
  {
    ...                                 // default algorithm for calculating
  }                                     // character's health
};
  在這個代碼(以及本文的其它代碼)中,我在類定義中展示 member functions(成員函數)的本體。這會將它們隱式聲明為 inline(內聯)。我用這種方法展示代碼僅僅是這樣更易于看到它在做些什么。我所描述的設計與是否 inline 化無關,所以不必深究 member functions(成員函數)定義在類的內部有什么意味深長的含義。根本沒有。

  這個基本的設計——讓客戶通過 public non-virtual member functions(公有非虛擬成員函數)調用 private virtual functions(私有虛擬函數)——被稱為 non-virtual interface (NVI) idiom(非虛擬接口慣用法)。這是一個更通用的被稱為 Template Method(一個模式,很不幸,與 C++ templates(模板)無關)的 design pattern(設計模式)的非凡形式。我將那個 non-virtual function(非虛擬函數)(例如,healthValue)稱為 virtual function's wrapper(虛擬函數的外殼)。

  NVI idiom(慣用法)的一個優勢通過 "do 'before' stuff" 和 "do 'after' stuff" 兩個注釋在代碼中標示出來。這些注釋標出的代碼片斷在做真正的工作的 virtual function(虛擬函數)之前或之后調用。這就意味著那個 wrapper(外殼)可以確保在 virtual function(虛擬函數)被調用前,特定的背景環境被設置,而在調用結束之后,這些背景環境被清理。例如,"before" stuff 可以包括鎖閉一個 mutex(互斥體),生成一條日志條目,校驗類變量和函數的 preconditions(前提條件)是否被滿足,等等。"after" stuff 可以包括解鎖一個 mutex(互斥體),校驗函數的 postconditions(結束條件),類不變量的恢復,等等。假如你讓客戶直接調用 virtual functions(虛擬函數),確實沒有好的方法能夠做到這些。

  涉及 derived classes(派生類)重定義 private virtual functions(私有虛擬函數)(這些重定義函數它們不能調用!)的 NVI idiom 可能會攪亂你的頭腦。這里沒有設計上的矛盾。重定義一個 virtual function(虛擬函數)指定如何做某些事。調用一個 virtual function(虛擬函數)指定什么時候去做?;ハ嘀g沒有關系。NVI idiom 答應 derived classes(派生類)重定義一個 virtual function(虛擬函數),這樣就給了它們控制功能如何實現的能力,但是 base class(基類)保留了決定函數何時被調用的權利。乍一看很希奇,但是 C++ 規定 derived classes(派生類)可以重定義 private inherited virtual functions(私有的通過繼續得到的函數)是非常明智的。

  在 NVI idiom 之下,virtual functions(虛擬函數)成為 private(私有的)并不是絕對必需的。在一些 class hierarchies(類繼續體系)中,一個 virtual function(虛擬函數)的 derived class(派生類)實現被期望調用其 base class(基類)的對應物,而為了這樣的調用能夠合法,虛擬必須成為 protected(保護的),而非 private(私有的)。有時一個 virtual function(虛擬函數)甚至必須是 public(公有的)(例如,polymorphic base classes(多態基類)中的 destrUCtors(析構函數)),但這樣一來 NVI idiom 就不能被真正應用。

  經由函數指針實現的策略模式

  NVI idiom 是 public virtual functions(公有虛擬函數)的有趣的可選替代物,但從設計的觀點來看,它比裝點門也多不了多少東西。究竟,我們還是在用 virtual functions(虛擬函數)來計算每一個角色的健康值。一個更引人注目的設計主張認為計算一個角色的健康值不依靠于角色的類型——這樣的計算根本不需要成為角色的一部分。例如,我們可能需要為每一個角色的 constructor(構造函數)傳遞一個指向健康值計算函數的指針,而我們可以調用這個函數進行實際的計算:


class GameCharacter;                               // forward declaration

// function for the default health calculation algorithm
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter {
public:
  typedef int (*HealthCalcFunc)(const GameCharacter&);

  eXPlicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
  : healthFunc(hcf)
  {}

  int healthValue() const
  { return healthFunc(*this); }

  ...

private:
  HealthCalcFunc healthFunc;
};
  這個方法是另一個通用 design pattern(設計模式)—— Strategy 的簡單應用,相對于基于 GameCharacter hierarchy(繼續體系)中的 virtual functions(虛擬函數)的方法,它提供了某些更引人注目的機動性:
  • 相同角色類型的不同實例可以有不同的健康值計算函數。例如:
class EvilBadGuy: public GameCharacter {
public:
  explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
  : GameCharacter(hcf)
  { ... }

  ...

};
int loseHealthQuickly(const GameCharacter&);    // health calculation
int loseHealthSlowly(const GameCharacter&);     // funcs with different
                                                // behavior

EvilBadGuy ebg1(loseHealthQuickly);             // same-type charac-
EvilBadGuy ebg2(loseHealthSlowly);              // ters with different
                                                // health-related
                                                // behavior
  • 對于一個指定的角色健康值的計算函數可以在運行時改變。例如,GameCharacter 可以提供一個 member function(成員函數)setHealthCalculator,它被答應代替當前的健康值計算函數。
  在另一方面,健康值計算函數不再是 GameCharacter hierarchy(繼續體系)的一個 member function(成員函數)的事實,意味著它不再擁有訪問它所計算的那個對象內部構件的特權。例如,defaultHealthCalc 不能訪問 EvilBadGuy 的 non-public(非公有)構件。假如一個角色的健康值計算能夠完全基于通過角色的 public interface(公有接口)可以得到的信息,這就沒什么問題,但是,假如準確的健康值計算需要 non-public(非公有)信息,就會有問題。實際上,在任何一個你要用 class(類)外部的等價機能(例如,經由一個 non-member non-friend function(非成員非友元函數)或經由另一個 class(類)的 non-friend member function(非友元成員函數))代替 class(類)內部的機能(例如,經由一個 member function(成員函數))的時候,它都是一個潛在的問題。這個問題將持續影響本 Item 的剩余部分,因為所有我們要考慮的其它設計選擇都包括 GameCharacter hierarchy(繼續體系)的外部函數的使用。

  作為一個通用規則,解決對“non-member functions(非成員函數)對類的 non-public(非公有)構件的訪問的需要”的唯一方法就是削弱類的 encapsulation(封裝性)。例如,class(類)可以將 non-member functions(非成員函數)聲明為 friends(友元),或者,它可以提供對“在其它情況下它更希望保持隱藏的本身的實現部分”的 public accessor functions(公有訪問者函數)。使用一個 function pointer(函數指針)代替一個 virtual function(虛擬函數)的優勢(例如,具有逐對象健康值計算函數的能力和在運行時改變這樣的函數的能力)是否能抵消可能的降低 GameCharacter 的 encapsulation(封裝性)的需要是你必須在設計時就做出決定的重要部分。 更多文章 更多內容請看C/C++進階技術文檔專題,或   經由 tr1::function 實現的策略模式


  一旦你習慣了 templates(模板)和 implicit interfaces(隱式接口)的應用,function-pointer-based(基于函數指針)的方法看上去就有些死板了。健康值的計算為什么必須是一個 function(函數),而不能是某種簡單的行為類似 function(函數)的東西(例如,一個 function object(函數對象))?假如它必須是一個 function(函數),為什么不能是一個 member function(成員函數)?為什么它必須返回一個 int,而不是某種能夠轉型為 int 的類型?

  假如我們用一個 tr1::function 類型的對象代替一個 function pointer(函數指針)(諸如 healthFunc),這些約束就會消失。這樣的對象可以持有 any callable entity(任何可調用實體)(例如,function pointer(函數指針),function object(函數對象),或 member function pointer(成員函數指針)),這些實體的標志性特征就是兼容于它所期待的東西。我們馬上就會看到這樣的設計,這次使用了 tr1::function:

class GameCharacter;                                 // as before
int defaultHealthCalc(const GameCharacter& gc);      // as before

class GameCharacter {
public:
   // HealthCalcFunc is any callable entity that can be called with
   // anything compatible with a GameCharacter and that returns anything
   // compatible with an int; see below for details
   typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
   explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
   : healthFunc(hcf)
   {}

   int healthValue() const
   { return healthFunc(*this);   }

   ...

private:
  HealthCalcFunc healthFunc;
};
  就像你看到的,HealthCalcFunc 是一個 tr1::function instantiation(實例化)的 typedef。這意味著它的行為類似一個普通的 function pointer(函數指針)類型。我們近距離看看 HealthCalcFunc 究竟是一個什么東西的 typedef:

std::tr1::function<int (const GameCharacter&)>
  這里我突出了這個 tr1::function instantiation(實例化)的“target signature(目標識別特征)”。這個 target signature(目標識別特征)是“取得一個引向 const GameCharacter 的 reference(引用),并返回一個 int 的函數”。這個 tr1::function 類型的(例如,HealthCalcFunc 類型的)對象可以持有兼容于這個 target signature(目標識別特征)的 any callable entity(任何可調用實體)。兼容意味著這個實體的參數能夠隱式地轉型為一個 const GameCharacter&,而它的返回類型能夠隱式地轉型為一個 int。

  與我們看到的最近一個設計(在那里 GameCharacter 持有一個指向一個函數的指針)相比,這個設計幾乎相同。僅有的區別是目前的 GameCharacter 持有一個 tr1::function 對象——指向一個函數的 generalized(泛型化)指針。除了達到“clients(客戶)在指定健康值計算函數時有更大的靈活性”的效果之外,這個變化是如此之小,以至于我寧愿對它視而不見:

short calcHealth(const GameCharacter&);          // health calculation
                                                 // function; note
                                                 // non-int return type

struct HealthCalculator {                        // class for health
  int Operator()(const GameCharacter&) const     // calculation function
  { ... }                                        // objects
};

class GameLevel {
public:
  float health(const GameCharacter&) const;      // health calculation
  ...                                            // mem function; note
};                                               // non-int return type


class EvilBadGuy: public GameCharacter {         // as before
  ...
};
class EyeCandyCharacter:   public GameCharacter {  // another character
  ...                                              // type; assume same
};                                                 // constructor as
                                                   // EvilBadGuy


EvilBadGuy ebg1(calcHealth);                       // character using a
                                                   // health calculation
                                                   // function


EyeCandyCharacter ecc1(HealthCalculator());        // character using a
                                                   // health calculation
                                                   // function object

GameLevel currentLevel;
...
EvilBadGuy ebg2(                                   // character using a
  std::tr1::bind(&GameLevel::health,               // health calculation
          currentLevel,                            // member function;
          _1)                                      // see below for details
);
  就個人感覺而言:我發現 tr1::function 能讓你做的事情是如此讓人驚喜,它令我渾身興奮異常。假如你沒有感到興奮,那可能是因為你正目不轉睛地盯著 ebg2 的定義并對 tr1::bind 的調用會發生什么迷惑不解。請耐心地聽我解釋。

  比方說我們要計算 ebg2 的健康等級,應該使用 GameLevel class(類)中的 health member function(成員函數)?,F在,GameLevel::health 是一個被聲明為取得一個參數(一個引向 GameCharacter 的引用)的函數,但是它實際上取得了兩個參數,因為它同時得到一個隱式的 GameLevel 參數——指向 this。然而,GameCharacters 的健康值計算函數只取得單一的參數:將被計算健康值的 GameCharacter。假如我們要使用 GameLevel::health 計算 ebg2 的健康值,我們必須以某種方式“改造”它,以使它適應只取得唯一的參數(一個 GameCharacter),而不是兩個(一個 GameCharacter 和一個 GameLevel)。在本例中,我們總是要使用 currentLevel 作為 GameLevel 對象來計算 ebg2 的健康值,所以每次調用 GameLevel::health 計算 ebg2 的健康值時,我們就要 "bind"(凝固)currentLevel 來作為 GameLevel 的對象來使用。這就是 tr1::bind 的調用所做的事情:它指定 ebg2 的健康值計算函數應該總是使用 currentLevel 作為 GameLevel 對象。

  我們跳過一大堆的細節,諸如為什么 "_1" 意味著“當為了 ebg2 調用 GameLevel::health 時使用 currentLevel 作為 GameLevel 對象”。這樣的細節并沒有什么啟發性,而且它們將轉移我所關注的基本點:在計算一個角色的健康值時,通過使用 tr1::function 代替一個 function pointer(函數指針),我們將答應客戶使用 any compatible callable entity(任何兼容的可調用實體)。很酷是不是?

  “經典的”策略模式

  假如你比 C++ 更加深入地進入 design patterns(設計模式),一個 Strategy 的更加習以為常的做法是將 health-calculation function(健康值計算函數)做成一個獨立的 health-calculation hierarchy(健康值計算繼續體系)的 virtual member function(虛擬成員函數)。做成的 hierarchy(繼續體系)設計看起來就像這樣:


C++箴言:考慮可選的虛擬函數的替代方法
  假如你不熟悉 UML 記法,這不過是在表示當把 EvilBadGuy 和 EyeCandyCharacter 作為 derived classes(派生類)時,GameCharacter 是這個 inheritance hierarchy(繼續體系)的根;HealthCalcFunc 是另一個帶有 derived classes(派生類)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(繼續體系)的根;而每一個 GameCharacter 類型的對象包含一個指向“從 HealthCalcFunc 派生的對象”的指針。

  這就是相應的框架代碼:

class GameCharacter;                            // forward declaration

class HealthCalcFunc {
public:

  ...
  virtual int calc(const GameCharacter& gc) const
  { ... }
  ...

};

HealthCalcFunc defaultHealthCalc;

class GameCharacter {
public:
  explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
  : pHealthCalc(phcf)
  {}

  int healthValue() const
  { return pHealthCalc->calc(*this);}

  ...

private:
  HealthCalcFunc *pHealthCalc;
};
  這個方法的吸引力在于對于熟悉“標準的”Strategy pattern(策略模式)實現的人可以很快地識別出來,再加上它提供了通過在 HealthCalcFunc hierarchy(繼續體系)中增加一個 derived class(派生類)而微調已存在的健康值計算算法的可能性。

  小結

  本文的基本建議是當你為嘗試解決的問題尋求一個設計時,你應該考慮可選的 virtual functions(虛擬函數)的替代方法。以下是對我們考察過的可選方法的一個簡略的回顧:
  • 使用 non-virtual interface idiom (NVI idiom)(非虛擬接口慣用法),這是用 public non-virtual member functions(公有非虛擬成員函數)包裝可訪問權限較小的 virtual functions(虛擬函數)的 Template Method design pattern(模板方法模式)的一種形式。
  • function pointer data members(函數指針數據成員)代替 virtual functions(虛擬函數),一種 Strategy design pattern(策略模式)的顯而易見的形式。
  • tr1::function data members(數據成員)代替 virtual functions(虛擬函數),這樣就答應使用兼容于你所需要的東西的 any callable entity(任何可調用實體)。這也是 Strategy design pattern(策略模式)的一種形式。
  • virtual functions in another hierarchy(另外一個繼續體系中的虛擬函數)代替 virtual functions in one hierarchy(單獨一個繼續體系中的虛擬函數)。這是 Strategy design pattern(策略模式)的習以為常的實現。
  這不是一個可選的 virtual functions(虛擬函數)的替代設計的詳盡無遺的列表,但是它足以使你確信這些是可選的方法。此外,它們之間互為比較的優劣應該使你考慮它們時更為明確。

  為了避免陷入 object-oriented design(面向對象設計)的習慣性道路,時不時地給車輪一些有益的顛簸。有很多其它的道路。值得花一些時間去考慮它們。

  Things to Remember
  • 可選的 virtual functions(虛擬函數)的替代方法包括 NVI 慣用法和 Strategy design pattern(策略模式)的各種變化形式。NVI 慣用法本身是 Template Method design pattern(模板方法模式)的一個實例。
  • 將一個機能從一個 member function(成員函數)中移到 class(類)之外的某個函數中的一個危害是 non-member function(非成員函數)沒有訪問類的 non-public members(非公有成員)的途徑。
  • tr1::function 對象的行為類似 generalized function pointers(泛型化的函數指針)。這樣的對象支持所有兼容于一個給定的目標特征的 callable entities(可調用實體)。
更多文章 更多內容請看C/C++進階技術文檔專題,或

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧亚中文在线| 欧美激情videos| 欧美日韩国产中文字幕| 亚洲视频在线观看视频| 日韩av手机在线观看| 日本成人黄色片| 精品国产91久久久| 国产精品2018| 精品国偷自产在线视频99| 久久久久久久91| 久久久天堂国产精品女人| 欧美在线视频导航| 成人免费淫片aa视频免费| 亚洲国产精品专区久久| 亚洲一级片在线看| 91精品国产91久久久久久| 亚洲综合中文字幕68页| 国产91亚洲精品| 日韩av电影中文字幕| 亚洲自拍欧美色图| 97超级碰碰碰| 中文字幕亚洲一区二区三区五十路| 国产精品一区二区三区在线播放| 国产视频自拍一区| 国产精品久久久久久久久久三级| 国产精品免费久久久久影院| 日韩第一页在线| 久久国产天堂福利天堂| 国产日韩综合一区二区性色av| 美女视频黄免费的亚洲男人天堂| 国产精自产拍久久久久久蜜| 中文字幕亚洲一区二区三区五十路| 国产脚交av在线一区二区| 91亚洲精品一区二区| 国产成人精品免费视频| 98精品国产自产在线观看| 亚洲www在线| 久久影院资源网| 91精品国产自产在线观看永久| 97国产成人精品视频| 日韩精品久久久久久福利| 久久精品一偷一偷国产| 国产一区二区三区久久精品| 成人黄色av网| 欧美激情一区二区三区成人| 欧美一区二区三区……| 国产香蕉精品视频一区二区三区| 欧美韩日一区二区| 狠狠做深爱婷婷久久综合一区| 欧美综合在线第二页| 久久精品国产成人| 久久国产精品首页| 国产精品av网站| 欧美精品18videos性欧美| 亚洲国产欧美一区二区三区久久| 成人国产精品色哟哟| 久久精品国产久精国产思思| 亚洲精品一区二三区不卡| 亚洲欧美精品伊人久久| 最近中文字幕mv在线一区二区三区四区| 亚洲在线一区二区| 操人视频在线观看欧美| 日韩成人中文电影| 欧美福利视频在线观看| 精品国产91乱高清在线观看| 久久精品一偷一偷国产| 17婷婷久久www| 亚洲天堂免费观看| 日韩精品一区二区视频| 91嫩草在线视频| 国产成一区二区| 亚洲福利在线观看| 久久激情视频久久| 国产mv久久久| 韩日精品中文字幕| 欧日韩不卡在线视频| 亚洲一区二区三区在线视频| 超碰日本道色综合久久综合| 欧美人与性动交| 91影院在线免费观看视频| 亚洲精品国产精品自产a区红杏吧| 国产一区深夜福利| 欧美日韩国产丝袜美女| 国产精品视频xxxx| 色爱av美腿丝袜综合粉嫩av| 亚洲最大的成人网| 欧美日韩精品在线播放| 国产精品欧美一区二区三区奶水| 久久久久久免费精品| 久久97精品久久久久久久不卡| 成人高h视频在线| 国产欧美日韩视频| 国产精品久久久av| 亚州精品天堂中文字幕| 成人免费高清完整版在线观看| 一个人www欧美| 日韩在线观看你懂的| 国产精品久久久久福利| 久久久亚洲精品视频| 欧美日韩福利在线观看| 国产亚洲美女久久| 日韩大片免费观看视频播放| 亚洲精品免费一区二区三区| 亚洲精品网站在线播放gif| 国产视频久久久| 国产91在线播放| 久久久久五月天| 欧美色另类天堂2015| 亚洲精品在线不卡| 欧美国产日韩一区二区在线观看| 热久久视久久精品18亚洲精品| 国产成人精品免高潮费视频| 欧美亚洲在线播放| 国产69精品久久久久99| 成人在线一区二区| 中文字幕久热精品在线视频| 国产精品爽爽爽爽爽爽在线观看| 亚洲欧洲在线观看| 欧美国产日韩一区二区| 国内精品国产三级国产在线专| 亚洲视频在线播放| 日本高清久久天堂| 91国偷自产一区二区三区的观看方式| 日本午夜在线亚洲.国产| 亚洲自拍偷拍色片视频| 国产亚洲欧美日韩一区二区| 91高清视频免费观看| 日韩一级黄色av| 国产亚洲在线播放| 日韩av电影中文字幕| 欧美大片大片在线播放| 亚洲偷欧美偷国内偷| 国产成人精品免费视频| 日韩在线视频中文字幕| 国产999视频| 国产精品美女www爽爽爽视频| 国产精品久久久久影院日本| 久久精品国产亚洲7777| 亚洲自拍中文字幕| 久久伊人免费视频| 国产精品一区二区av影院萌芽| 日韩av色综合| 日本一区二区三区四区视频| 日韩成人在线视频观看| 日韩精品高清视频| 欧美大荫蒂xxx| 97成人精品视频在线观看| 欧美巨猛xxxx猛交黑人97人| 午夜精品福利视频| 午夜精品久久久久久久久久久久| 国产suv精品一区二区三区88区| 国产在线精品自拍| 九九热在线精品视频| 国产精品一区专区欧美日韩| 日韩视频免费大全中文字幕| 久久这里只有精品视频首页| 久久天天躁日日躁| 国产91在线播放精品91| 欧美视频中文在线看| 欧美区二区三区| 日韩在线视频免费观看高清中文| 91老司机在线| 欧美激情在线播放| 久久久久久久久久国产|