青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-341  評論-2670  文章-0  trackbacks-0

面向對象這個抽象的特例總是有說不完的話題,更糟糕的是很多語言都錯誤地實現了面向對象——class居然可以當一個變量類型什么的這只是讓人們寫代碼寫的更糟糕而已。當然這個話題第三篇文章已經說過了,現在來談談人們喜歡拿來裝逼的另一個話題——消息發送。

按照慣例先來點題外話。說到消息發送,有些人喜歡跳出來說,objective-c的消息做得多優雅啊,代碼都可以寫成一句話[golang screw:you you:suck]之類的。其實這個還做得不夠徹底。在幾年前易語言曾經火了一陣,但是為什么大家這么討厭他呢?其實顯然不是因為每個token都是漢字,而是因為他做的一點都不像中文,誰會說話的時候帶那么多符號呀。其實objective-c也一樣,沒人會因為想在一句英語里面用冒號來分割短語的。

當我還在讀大三的時候,我由于受到了Apple Script(也是蘋果做的)的啟發,試圖發明一門語言,讓他可以盡量寫起來像自然語言——當然他仍然是嚴格的編程語言。但是這門語言因為其奇特的語法結構,我只好自己想出了一個兩遍parse地方法。第一遍parse出所有函數頭,讓后用這些函數頭臨時組成一個parser,用它來parse語句的部分。后面整個也實現出來了,然后我就去做了一下調查,發現大家不喜歡,原因是要輸入的東西太多了。不過我這里還是貼一下當初是怎么設計的:

phrase print(content) is
    external function "writeln"
end phrase

phrase first (count) items of fibonacci sequence is
    if count equals to 1 then
        result is [1]
    else if count equals to 2 then
        result is [1,1]
    else
        let list be [1,1]
        repeat with i from 3 to count
            let list be list joins with item length of list - 1 of list + item length of list - 2 of list
        end
        result is list
    end
end phrase

phrase (number) is odd is
    result is number mod 2 is 0
end phrase alias odd number

phrase append (item) after (list) is
    let 0 element of list from length of list be [item]
end phrase

phrase ((item) is validated) in (list) is
    let filtered list be []
    repeat with item in list
        append item after filtered list if item is validated
    end
    result is filtered list
end phrase

phrase main is
    print odd number in first 10 items of fibonacci sequence
end phrase

倒數第二個函數聲明甚至連函數指針的聲明也如此的優雅(我自己認為的),整個程序組織起來,我們要輸出斐波那契數列里面前10個數字中間的奇數,于是就寫成了

print odd number in first 10 items of fibonacci sequence

看起來比objective-c要漂亮把。其實如果想把所有的東西換成中文,算法也不需要變化。現在用空格來分割一個一個的詞,中文直接用字符就好了,剩下的都一樣。要parse這個程序根本沒辦法用流行的那些方法來parse。當然我知道大家也不會關心這些特別復雜的問題,于是題外話就到這里結束了,這個語言的實現的代碼你們大概也永遠都不會看到的,啊哈哈哈哈。

為什么要提這件事情呢?我主要是想告訴大家,就算你在用面向對象語言,想在程序里面給一個對象發送一條消息,這個對象并不是非得寫在最前面的。為什么呢?有的時候對象不止一個——這個東西叫multiple dispatching,著名的問題就是如何給一堆面向對象的幾何體類做他們的求交函數——用面向對象的慣用做法做起來會特別的難受。不過現在我們先來看一下普通的消息發送是什么樣子的。

對于一個我們知道他是什么類型的對象來說,發送一個消息就跟直接調用一個函數一樣,因為你不需要去resolve一下這個函數到底是誰。譬如說下面的代碼:

class Language
{
public:
    void YouSuck(){ ... }
};

Language golang;
golang.YouSuck();

最終翻譯出來的結果會近似

struct Language
{
};

void Language_YouSuck(Language* const this)
{
    ...
}

Language golang;
Language_YouSuck(&golang);

很多人其實并不能在學習面向對象語言的時候就直接意識到這一點。其實我也是在高中的時候玩delphi突然就在網上看見了這么一篇文章,然后我才明白的。看起來這個過渡并不是特別的自然是不是。

當你要寫一個獨立的class,不繼承自任何東西的時候,這個class的作用只有兩個。第一個是封裝,這個第三篇文章已經提到過了。第二個作用就是給里面的那些函數做一個匿名的namespace。這是什么意思呢?就像上面的代碼一樣,你寫golang.YouSuck(),編譯器會知道golang是一個Language,然后去調用Language::YouSuck()。如果你調用lisp.YouSuck()的時候,說不定lisp是另一個叫做BetterThanGolangLanguage的類型,然后他就去里面找了YouSuck。這里并不會因為兩個YouSuck的名字一樣,編譯器就把它搞混了。這個東西這跟重載也差不多,我就曾經在Microsoft Research里面看見過一個人做了一個語言(主要是用來驗證語言本身的正確性的),其中a.b(c, d)是b(a, c, d)的語法糖,這個“.”毫無特別之處。

有一天,情況變了。專門開發蹩腳編譯器的AMD公司看見golang很符合他們的口味,于是也寫了一個golang的實現。那這個事情應該怎么建模呢?因為golang本身是一套標準,你可也可以稱呼他為協議,然后下面有若干個實現。所以Language本身作為一個category也只好跟著golang變成interface了。為了程序簡單我們只看其中的一個小片段:

class IGolang
{
public:
    virtual void YouSuck()=0;
};

class GoogleGolang : public IGolang
{
public:
    void YouSuck()override{ /*1*/ }
};

class AmdGolang : public IGolang
{
public:
    void YouSuck()override{ /*2*/ }
};

IGolang* golang = new GoogleGolang;
golang->YouSuck();

我很喜歡VC++的專有關鍵字override,他可以在我想override但是不小心寫錯了一點的時候提示我,避免了我大量的錯誤的發生。當然這個東西別的編譯器不支持,所以我在我的代碼的靠前的地方寫了一個宏,發現不是VC++再編譯,我就把override給#define成空的。反正我的程序里面不會用關鍵字來當變量名的。

看著這個程序,已經不能單純的用GoogleGolang_YouSuck(golang)來代替這個消息發送了,因為類型是IGolang的話說不定下面是一個AmdGolang。所以在這里我們就要引入虛函數表了。一旦引入了虛函數表,代碼就會瞬間變得復雜起來。我見過很多人問,虛函數表那么大,要是每一個類的實例都帶一個表的話豈不是很浪費內存?這種人就應該先去看《Inside the C++ Object Model》,然后再反省一下自己的問題有多么的——呃——先看帶有虛函數表的程序長什么樣子好了:

struct vtable_IGolang
{
    void (*YouSuck)(IGolang* const this);
};

struct IGolang
{
    vtable_IGolang* vtable;
};

//---------------------------------------------------

vtable_IGolang vtable_GoogleGolang;
vtable_GoogleGolang.YouSuck = &vtable_GoogleGolang_YouSuck;

struct GoogleGolang
{
    IGolang parent;
};

void vtable_GoogleGolang_YouSuck(IGolang* const this)
{
    int offset=(int)(&((GoogleGolang*)0)->parent);
    GoogleGolang_YouSuck((GoogleGolang*)((char*)this-offset));
}

void GoogleGolang_YouSuck(GoogleGolang* const this)
{
    /*1*/
}

void GoogleGolang_ctor(GoogleGolang* const this)
{
    this->parent->vtable = &vtable_GoogleGolang;
}

//---------------------------------------------------
// AmdGolang略,長得都一樣
//---------------------------------------------------

GoogleGolang* tmp = (GoogleGolang*)malloc(sizeof(GoogleGolang));
GoogleGolang_ctor(tmp);
IGolang* golang = &tmp->parent;
golang->vtable->YouSuck(golang);

基本上已經面目全非了。當然實際上C++生成的代碼比這個要復雜得多。我這里只是不想把那些細節牽引進來,針對我們的那個例子寫了個可能的實現。面向對象的語法糖多么的重要啊,盡管你也可以在需要的時候用C語言把這些東西寫出來(就跟那個愚蠢的某著名linux GUI框架一樣),但是可讀性已經完全喪失了吧。明明那么幾行就可以表達出來的東西,我們為了達到同樣的性能,用C寫要把代碼寫成屎。東西一多,名字用完了,都只好對著代碼發呆了,決定把C扔了,完全用C++來寫。萬一哪天用到了virtual繼承——在某些情況下其實是相當好用的,譬如說第三篇文章講的,在C++里面用interface,而且也很常見——那用C就只能呵呵呵了,寫出來的代碼再也沒法讀了,沒法再把OOP實踐下去了。

好了,消息發送的簡單的實現大概也就講到這里了。只要不是C++,其他語言譬如說只有單根繼承的Delphi,實現OOP大概也就是上面這個樣子。于是我們圍繞著消息發送的語法糖玩了很久,終于遇到了兩大終極問題。這兩個問題說白了都是開放和封閉的矛盾。我們用基類和一大堆子類的結構來寫程序的時候,需要把邏輯都封裝在虛函數里面,不然的話你就得cast了,cast是將程序最終導向失控的根源之一。這個時候我們對類型擴展是開放的,而對邏輯擴展是封閉的。這是什么意思呢?讓我們來看下面這個例子:

class Shape
{
public:
    virtual double GetArea()=0;
    virtual bool HitTest(Point p)=0;
};

class Circle : public Shape ...;
class Rectangle : public Shape ... ;

我們每當添加一個新形狀的時候,只要實現GetArea和HitTest,那么事情就做完了。所以你可以無限的添加新形狀——所以類型擴展是開放的。但是你卻永遠只能做GetArea和HitTest——對邏輯擴展是封閉的。你如果想做除了GetArea和HitTest以外的更多的事情的話,這個時候你就被迫做cast了。那么在類型相對穩定的情況下有沒有別的方法呢?設計模式告訴我們,我們可以用Visitor來把情況扭轉過來——做成對類型擴展封閉,而對邏輯擴展開放的:

class IShapeVisitor
{
public:
    virtual void Visit(Circle* shape)=0;
    virtual void Visit(Rectangle* shape)=0;
};

class Shape
{
public:
    virtual void Accept(IShapeVisitor* visitor)=0;
};

class Circle : public Shape
{
public:
    ...

    void Accept(IShapeVIsitor* visitor)override
    {
        visitor->Visit(this);  // 因為重載的關系,會調用到第一個Visit函數
    }
};

class Rectangle : public Shape
{
public:
    ...

    void Accept(IShapeVIsitor* visitor)override
    {
        visitor->Visit(this);  // 因為重載的關系,會調用到第二個Visit函數
    }
};

//------------------------------------------

class GetAreaVisitor : public IShapeVisitor
{
public:
    double result;

    void Visit(Circle* shape)
    {
        result = ...;
    }

    void Visit(Rectangle* shape)
    {
        result = ...;
    }
};

class HitTestVisitor : public IShapeVisitor ...;

這個時候GetArea可能調用起來就不是那么方便了,不過我們總是可以把它寫成一個函數:

double GetArea(Shape* shape)
{
    GetAreaVisitor visitor;
    shape->Accept(&visitor);
    return visitor.result;
}

這個時候你可以隨意的做新的事情了,但是一旦需要添加新類型的時候,你需要改動很多東西,首先是Visitor的接口,其實是讓所有的邏輯都支持新類型,這樣你就不能僅僅通過添加新代碼來擴展新類型了。所以這就是對邏輯擴展開放,而對類型擴展封閉了。

所以第一個問題就是:能不能做成類型擴展也開放,邏輯擴展也開放呢?在回答這個問題之前,我們先來看下一個問題。我們要對兩個Shape進行求交,看看他們是不是有重疊在一起的部分。但是每一個具體的Shape,譬如Circle啊Rectangle啊,定義都是不一樣的,沒辦法有通用的處理辦法,所以我們只能寫3個函數了(RR, CC, CR)。如果有3各類型,那么我們就需要6個函數。如果有4個類型,那我們就需要有10個函數——才能處理所有情況。公式倒是可以一下子看出來,函數數量就等于1+2+ … +n,n等于類型的數量。

這看起來好像是一個類型擴展開放的問題是吧,但是實際上他只能用邏輯擴展的方法來做。為什么呢?你看我們的一個visitor其實很像是我們對一個一個的具體類型都試一下看看shape是不是這個類型,從而做出正確的處理。不過這跟我們直接用if地方法相比有兩個優點:1、快;2、編譯器替你查錯有保證。

那實際上應該怎么做呢?想想,我們這里有兩次“if type”。第一次針對第一個參數,第二次針對第二個參數。所以我們一共需要n+1=3個visitor。寫的方法倒是不復雜,首先我們得準備好RR,CC,CR三個邏輯,然后用visitor去識別類型然后調用它們:

bool IntersectCC(Circle* s1, Circle* s2){ ... }
bool IntersectCR(Circle* s1, Rectangle* s2){ ... }
bool IntersectRR(Rectangle* s1, Rectangle* s2){ ... }
// RC和CR是一樣的

class IntersectWithCircleVisitor : public IShapeVisitor
{
public:
    Circle* s1;
    bool result;

    void Visit(Circle* shape)
    {
        result=IntersectCC(s1, shape);
    }

    void Visit(Rectangle* shape)
    {
        result=IntersectCR(s1, shape);
    }
};

class IntersectWithRectangleVisitor : public IShapeVisitor
{
public:
    Rectangle* s1;
    bool result;

    void Visit(Circle* shape)
    {
        result=IntersectCR(shape, s1);
    }

    void Visit(Rectangle* shape)
    {
        result=IntersectRR(s1, shape);
    }
};

class IntersectVisitor : public IShapeVisitor
{
public:
    bool result;
    IShape* s2;

    void Visit(Circle* shape)
    {
        IntersectWithCircleVisitor visitor;
        visitor.s1=shape;
        s2->Accept(&visitor);
        result=visitor.result;
    }

    void Visit(Rectangle* shape)
    {
        IntersectWithRectangleVisitor visitor;
        visitor.s1=shape;
        s2->Accept(&visitor);
        result=visitor.result;
    }
};

bool Intersect(Shape* s1, Shape* s2)
{
    IntersectVisitor visitor;
    visitor.s2=s2;
    s1->Accept(&visitor);
    return visitor.result;
}

我覺得你們現在心里的想法肯定是:“我屮艸芔茻。”嗯,這種事情在物理引擎里面是經常要碰到的。然后當你需要添加一個新的形狀的時候,呵呵呵呵呵呵呵呵。不過這也是沒辦法的,誰讓現在的要求運行時性能的面向對象語言都這么做呢?

當然,如果在不要求性能的情況下,我們可以用ruby和它的mixin來做。至于說怎么辦,其實你們應該發現了,添加一個Visitor和添加一個虛函數的感覺是差不多的。所以只要把Visitor當成虛函數的樣子,讓Ruby給mixin一堆新的函數進各種類型就好了。不過只有支持運行時mixin的語言才能做到這一點。強類型語言我覺得是別想了。

Mixin地方法倒是很直接,我們只要把每一個Visitor里面的Visit函數都給加進去就好了,大概感覺上就類似于:

class Shape
{
public:
    // Mixin的時候等價于給每一個具體的Shape類都添加下面三個虛函數的重寫
    virtual bool Intersect(Shape* s2)=0;
    virtual bool IntersectWithCircle(Circle* s1)=0;
    virtual bool IntersectWithRectangle(Rectangle* s1)=0;
};

//--------------------------------------------

bool Circle::Intersect(Shape* s2)
{
    return s2->IntersectWithCircle(this);
}

bool Rectangle::Intersect(Shape* s2)
{
    return s2->IntersectWithRectangle(this);
}

//--------------------------------------------

bool Circle::IntersectWithCircle(Circle* s1)
{
    return IntersectCC(s1, this);
}

bool Rectangle::IntersectWithCircle(Circle* s1)
{
    return IntersectCR(s1, this);
}

//--------------------------------------------

bool Circle::IntersectWithRectangle(Rectangle* s1)
{
    return IntersectCR(this, s1);
}

bool Rectangle::IntersectWithRectangle(Rectangle* s1)
{
    return IntersectRR(s1, this);
}

這下子應該看出來為什么我說這種方法只能用Visitor了吧,否則就要把所有類型都寫進Shape,就會很奇怪了。如果這樣的邏輯一多,類型也有四五個的話,那每加一個邏輯就得添加一批虛函數,Shape類很快就會被玩壞了。而代表邏輯的Visitor是可以放在不同的地方的,互相之間是隔離的,維護起來就會比較容易。

那現在我們就要有第二個問題了:在擁有兩個“this”的情況下,我們要如何做才能把邏輯做成類型擴展也開放,邏輯擴展也開放呢?然后參考我們的第一個問題:能不能做成類型擴展也開放,邏輯擴展也開放呢?你應該心里有數了吧,答案當然是——不能做。

這就是語言的極限了。面向對象才用的single dispatch的方法,能做到的東西是很有限的。情況稍微復雜那么一點點——就像上面對兩個形狀求交這種正常的問題——寫起來都這么難受。

那呼應一下標題,如果我們要設計一門語言,來支持上面這種multiple dispatch,那可以怎么修改語法呢?這里面分為兩種,第一種是像C++這樣運行時load dll不增加符號的,第二種是像C#這樣運行時load dll會增加符號的。對于前一種,其實我們可以簡單的修改一下語法:

bool Intersect(switch Shape* s1, switch Shape* s2);

bool Intersect(case Circle* s1, case Circle* s2){ ... }
bool Intersect(case Circle* s1, case Rectangle* s2){ ... }
bool Intersect(case Rectangle* s1, case Circle* s2){ ... }
bool Intersect(case Rectangle* s1, case Rectangle* s2){ ... }

然后修改一下編譯器,把這些東西翻譯成虛函數塞回原來的Shape類里面就行了。對于第二種嘛,其實就相當于Intersect的根節點、Circle和CC寫在dll1,Rectangle和CR、RC、RR寫在dll2,然后dll1運行時把dll2給動態地load了進來,再之后調用Intersect的時候就好像“虛函數已經進去了”一樣。至于要怎么做,這個大家回去慢慢思考一下吧,啊哈哈哈。

posted on 2013-05-24 19:08 陳梓瀚(vczh) 閱讀(11514) 評論(5)  編輯 收藏 引用 所屬分類: 啟示

評論:
# re: 如何設計一門語言(五)——面向對象和消息發送 2013-05-24 20:29 | n00b
SICP Exercise 2.76是類似的問題  回復  更多評論
  
# re: 如何設計一門語言(五)——面向對象和消息發送 2013-05-25 00:07 | rink1969
這個說的就是Expression Problem嘛
兩種方法其實就是代數方法和共代數方法。
編程語言擴展的根本問題就是新的方法和數據類型在反序列化過程中如何保證整個類型系統不出問題。
要求不嚴格的話,用宏隨便搞搞,夠用就行了。  回復  更多評論
  
# re: 如何設計一門語言(五)——面向對象和消息發送 2013-05-25 03:58 | 溪流
學習了  回復  更多評論
  
# re: 如何設計一門語言(五)——面向對象和消息發送 2013-05-25 22:36 | Marvin
面向對象很簡單,只是語言給做復雜了  回復  更多評論
  
# re: 如何設計一門語言(五)——面向對象和消息發送 2013-05-26 05:58 | cscope
哼哼,CLOS——————multimethod  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            激情欧美一区二区三区| 欧美一级艳片视频免费观看| 久久综合给合| 亚洲电影激情视频网站| 欧美成人国产va精品日本一级| 国产精品久久影院| 欧美电影在线观看| 欧美精品亚洲精品| 国产精品免费看| 狠狠色综合网| 一本色道久久综合| 欧美一区二区黄| 久久亚洲精品一区| 亚洲精品日产精品乱码不卡| 亚洲欧美日韩久久精品| 猛干欧美女孩| 国产欧美日韩免费看aⅴ视频| 亚洲福利视频二区| 亚洲欧美视频一区| 欧美黄色一区| 亚洲图片欧美午夜| 鲁大师成人一区二区三区| 欧美性做爰毛片| 亚洲国产精品久久久久秋霞蜜臀| 亚洲一区三区视频在线观看 | 亚洲国产精品第一区二区| 在线亚洲美日韩| 美女诱惑黄网站一区| 国产精品午夜久久| 亚洲国产高清一区二区三区| 亚洲欧美亚洲| 亚洲伦伦在线| 久久久久一区二区三区| 国产精品亚洲美女av网站| 亚洲人成人一区二区三区| 久久精品成人一区二区三区蜜臀 | 欧美在线高清| 欧美激情久久久久| 久久精品99久久香蕉国产色戒| 欧美高清视频免费观看| 一区二区三区中文在线观看 | 国产精品综合av一区二区国产馆| 在线看片成人| 久久精品欧美| 亚洲一品av免费观看| 欧美高清视频一区二区三区在线观看 | 午夜精品视频一区| 亚洲精品国产精品国自产观看浪潮 | 亚洲理论在线| 男女激情久久| 久久国产色av| 国产综合久久久久影院| 欧美一级网站| 91久久精品日日躁夜夜躁欧美 | 亚洲视频专区在线| 欧美激情在线播放| 久久av红桃一区二区小说| 国产精品久久一区二区三区| 一个色综合av| 亚洲第一主播视频| 美女诱惑一区| 亚洲欧洲综合| 亚洲国产精品www| 久久蜜桃精品| 韩国一区二区三区在线观看| 欧美专区在线观看一区| 亚洲欧美日韩视频一区| 国产视频欧美| 欧美在线一区二区| 国内精品视频在线观看| 欧美激情一区二区三区四区| 亚洲国内精品| 欧美国产第一页| 欧美成人精品h版在线观看| 亚洲国产精品久久久久秋霞影院| 美女爽到呻吟久久久久| 久久综合一区二区| 伊甸园精品99久久久久久| 麻豆免费精品视频| 免费日韩av电影| 亚洲国产精品一区二区第一页 | 欧美激情2020午夜免费观看| 久久国产88| 极品少妇一区二区三区精品视频| 久久久免费精品视频| 久久综合狠狠| 亚洲精品久久久蜜桃| 欧美成人资源网| 欧美极品aⅴ影院| 一区二区三区精品视频| 亚洲神马久久| 国产午夜精品美女毛片视频| 久久一区二区视频| 欧美高清在线视频| 中文国产成人精品久久一| 亚洲精品在线观| 国产精品视频一区二区高潮| 久久露脸国产精品| 欧美激情一区二区三区蜜桃视频| 亚洲一级在线| 亚洲女女女同性video| 国内一区二区三区| 欧美大片在线观看| 欧美日韩国产三区| 欧美在线视频导航| 免费在线亚洲欧美| 亚洲男人天堂2024| 久久久久久有精品国产| 9久re热视频在线精品| 欧美日韩91| 国产亚洲欧美激情| 亚洲高清久久| 国产精品美女久久久久久2018| 亚洲在线国产日韩欧美| 欧美亚洲网站| 9色porny自拍视频一区二区| 性欧美1819sex性高清| 精品91视频| 亚洲理论在线观看| 国产日韩av在线播放| 欧美国产乱视频| 国产精品视频网站| 亚洲黄色尤物视频| 国产精品免费网站在线观看| 欧美a级一区| 国产精品卡一卡二| 亚洲高清视频在线观看| 国产乱人伦精品一区二区| 在线成人小视频| 免费不卡中文字幕视频| 欧美午夜欧美| 欧美激情在线狂野欧美精品| 国产亚洲欧美在线| 亚洲精品在线观| 怡红院精品视频| 亚洲欧美国产精品桃花| 夜夜嗨av色一区二区不卡| 久久精品中文| 性做久久久久久| 欧美日韩国产三级| 欧美黄在线观看| 国产又爽又黄的激情精品视频| 一片黄亚洲嫩模| 亚洲二区免费| 欧美在线二区| 国产亚洲精品一区二555| 久久久精品国产免大香伊| 欧美日本高清| 亚洲福利电影| 亚洲国产激情| 久久久精品五月天| 亚洲人成7777| 国产精品色午夜在线观看| 亚洲精品在线一区二区| 亚洲欧洲日夜超级视频| 久久激情视频免费观看| 欧美一区二区在线观看| 欧美性猛片xxxx免费看久爱| 亚洲人永久免费| 亚洲精品视频在线观看网站| 美女精品国产| 欧美电影免费观看高清| 在线精品一区| 久久人人97超碰人人澡爱香蕉| 久久蜜桃av一区精品变态类天堂| 国产精品无码永久免费888| 一区二区三区日韩精品| 亚洲一级二级| 欧美亚洲自偷自偷| 欧美1区2区视频| 亚洲第一在线| 亚洲精品久久久久久一区二区| 欧美**人妖| 亚洲激情另类| 一本色道久久综合亚洲精品婷婷| 欧美高清在线视频观看不卡| 亚洲第一在线综合在线| 日韩视频免费观看| 欧美日韩国产色视频| 一区二区高清在线观看| 亚洲欧美清纯在线制服| 国产精品家庭影院| 亚洲女爱视频在线| 久久精品亚洲热| 一区二区视频欧美| 狂野欧美一区| 欧美福利影院| 一本久久青青| 欧美午夜一区| 一本色道久久综合亚洲精品不卡 | 亚洲人成网站色ww在线| 亚洲理论在线观看| 欧美日韩一区二区在线| 亚洲美女诱惑| 亚洲激情影院| 国产女人精品视频| 久久成年人视频| 免费在线亚洲欧美| 亚洲国产日韩综合一区| 欧美精品午夜|