• <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>

            隨筆 - 25, 文章 - 0, 評(píng)論 - 6, 引用 - 0
            數(shù)據(jù)加載中……

            c++ 函數(shù)重載

            ——每個(gè)現(xiàn)象后面都隱藏一個(gè)本質(zhì),關(guān)鍵在于我們是否去挖掘

            寫在前面:

            函數(shù)重載的重要性不言而明,但是你知道C++中函數(shù)重載是如何實(shí)現(xiàn)的呢(雖然本文談的是C++中函數(shù)重載的實(shí)現(xiàn),但我想其它語(yǔ)言也是類似的)?這個(gè)可以分解為下面兩個(gè)問(wèn)題

            • 1、聲明/定義重載函數(shù)時(shí),是如何解決命名沖突的?(拋開(kāi)函數(shù)重載不談,using就是一種解決命名沖突的方法,解決命名沖突還有很多其它的方法,這里就不論述了)
            • 2、當(dāng)我們調(diào)用一個(gè)重載的函數(shù)時(shí),又是如何去解析的?(即怎么知道調(diào)用的是哪個(gè)函數(shù)呢)

            這兩個(gè)問(wèn)題是任何支持函數(shù)重載的語(yǔ)言都必須要解決的問(wèn)題!帶著這兩個(gè)問(wèn)題,我們開(kāi)始本文的探討。本文的主要內(nèi)容如下:

            •  1、例子引入(現(xiàn)象)
              • 什么是函數(shù)重載(what)?
              • 為什么需要函數(shù)重載(why)?
            • 2、編譯器如何解決命名沖突的?
              • 函數(shù)重載為什么不考慮返回值類型
            • 3、重載函數(shù)的調(diào)用匹配
              • 模凌兩可的情況
            • 4、編譯器是如何解析重載函數(shù)調(diào)用的?
              • 根據(jù)函數(shù)名確定候選函數(shù)集
              • 確定可用函數(shù)
              • 確定最佳匹配函數(shù)
            • 5、總結(jié)

            1、例子引入(現(xiàn)象)

            1.1、什么是函數(shù)重載(what)?

            函數(shù)重載是指在同一作用域內(nèi),可以有一組具有相同函數(shù)名不同參數(shù)列表的函數(shù),這組函數(shù)被稱為重載函數(shù)。重載函數(shù)通常用來(lái)命名一組功能相似的函數(shù),這樣做減少了函數(shù)名的數(shù)量,避免了名字空間的污染,對(duì)于程序的可讀性有很大的好處。

            When two or more different declarations are specified for a single name in the same scope,  that name is said to overloaded.  By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function declarations can be overloaded; object and type declarations cannot be overloaded. ——摘自《ANSI C++ Standard. P290》

            看下面的一個(gè)例子,來(lái)體會(huì)一下:實(shí)現(xiàn)一個(gè)打印函數(shù),既可以打印int型、也可以打印字符串型。在C++中,我們可以這樣做:

            #include<iostream> 
            using namespace std;  
            void print(int i) 
            {         
            cout
            <<"print a integer :"<<i<<endl; 

            void print(string str) 
            {         
            cout
            <<"print a string :"<<str<<endl; 

            int main() 
            {         
            print(
            12);        
            print(
            "hello world!");
            return 0
            }

            通過(guò)上面代碼的實(shí)現(xiàn),可以根據(jù)具體的print()的參數(shù)去調(diào)用print(int)還是print(string)。上面print(12)會(huì)去調(diào)用print(int),print("hello world")會(huì)去調(diào)用print(string),如下面的結(jié)果:(先用g++ test.c編譯,然后執(zhí)行)

            C  函數(shù)重載例子1

            1.2、為什么需要函數(shù)重載(why)?

            • 試想如果沒(méi)有函數(shù)重載機(jī)制,如在C中,你必須要這樣去做:為這個(gè)print函數(shù)取不同的名字,如print_int、print_string。這里還只是兩個(gè)的情況,如果是很多個(gè)的話,就需要為實(shí)現(xiàn)同一個(gè)功能的函數(shù)取很多個(gè)名字,如加入打印long型、char*、各種類型的數(shù)組等等。這樣做很不友好!
            • 類的構(gòu)造函數(shù)跟類名相同,也就是說(shuō):構(gòu)造函數(shù)都同名。如果沒(méi)有函數(shù)重載機(jī)制,要想實(shí)例化不同的對(duì)象,那是相當(dāng)?shù)穆闊?/li>
            • 操作符重載,本質(zhì)上就是函數(shù)重載,它大大豐富了已有操作符的含義,方便使用,如+可用于連接字符串等!

            通過(guò)上面的介紹我們對(duì)函數(shù)重載,應(yīng)該喚醒了我們對(duì)函數(shù)重載的大概記憶。下面我們就來(lái)分析,C++是如何實(shí)現(xiàn)函數(shù)重載機(jī)制的。

            2、編譯器如何解決命名沖突的?

            為了了解編譯器是如何處理這些重載函數(shù)的,我們反編譯下上面我們生成的執(zhí)行文件,看下匯編代碼(全文都是在Linux下面做的實(shí)驗(yàn),Windows類似,你也可以參考《一道簡(jiǎn)單的題目引發(fā)的思考》一文,那里既用到Linux下面的反匯編和Windows下面的反匯編,并注明了Linux和Windows匯編語(yǔ)言的區(qū)別)。我們執(zhí)行命令objdump -d a.out >log.txt反匯編并將結(jié)果重定向到log.txt文件中,然后分析log.txt文件。

            發(fā)現(xiàn)函數(shù)void print(int i) 編譯之后為:(注意它的函數(shù)簽名變?yōu)?#8212;—_Z5printi

            image

            發(fā)現(xiàn)函數(shù)void print(string str) 編譯之后為:(注意它的函數(shù)簽名變?yōu)?#8212;—_Z5printSs

            image

            我們可以發(fā)現(xiàn)編譯之后,重載函數(shù)的名字變了不再都是print!這樣不存在命名沖突的問(wèn)題了,但又有新的問(wèn)題了——變名機(jī)制是怎樣的,即如何將一個(gè)重載函數(shù)的簽名映射到一個(gè)新的標(biāo)識(shí)?我的第一反應(yīng)是:函數(shù)名+參數(shù)列表,因?yàn)楹瘮?shù)重載取決于參數(shù)的類型、個(gè)數(shù),而跟返回類型無(wú)關(guān)。但看下面的映射關(guān)系:

            void print(int i)                    -->         _Z5printi 
            void print(string str)         -->         _Z5printSs

            進(jìn)一步猜想,前面的Z5表示返回值類型,print函數(shù)名,i表示整型int,Ss表示字符串string,即映射為返回類型+函數(shù)名+參數(shù)列表。最后在main函數(shù)中就是通過(guò)_Z5printi_Z5printSs來(lái)調(diào)用對(duì)應(yīng)的函數(shù)的:

            80489bc:       e8 73 ff ff ff          call   8048934 <_Z5printi> 
            …………… 
            80489f0:       e8 7a ff ff ff          call   804896f <_Z5printSs>

            我們?cè)賹憥讉€(gè)重載函數(shù)來(lái)驗(yàn)證一下猜想,如:

            void print(long l)           -->           _Z5printl 
            void print(char str)      -->           _Z5printc 
            可以發(fā)現(xiàn)大概是int->i,long->l,char->c,string->Ss….基本上都是用首字母代表,現(xiàn)在我們來(lái)現(xiàn)在一個(gè)函數(shù)的返回值類型是否真的對(duì)函數(shù)變名有影響,如:

            #include<iostream> using namespace std;
            int max(int a,int b) 
            {         
            return a>=b?a:b; 
            }  
            double max(double a,double b) 
            {         
            return a>=b?a:b; 

            int main() 
            {         
            cout
            <<"max int is: "<<max(1,3)<<endl;         
            cout
            <<"max double is: "<<max(1.2,1.3)<<endl;         return 0;
            }

            int max(int a,int b) 映射為_Z3maxiidouble max(double a,double b) 映射為_Z3maxdd,這證實(shí)了我的猜想,Z后面的數(shù)字代碼各種返回類型。更加詳細(xì)的對(duì)應(yīng)關(guān)系,如那個(gè)數(shù)字對(duì)應(yīng)那個(gè)返回類型,哪個(gè)字符代表哪重參數(shù)類型,就不去具體研究了,因?yàn)檫@個(gè)東西跟編譯器有關(guān),上面的研究都是基于g++編譯器,如果用的是vs編譯器的話,對(duì)應(yīng)關(guān)系跟這個(gè)肯定不一樣。但是規(guī)則是一樣的:“返回類型+函數(shù)名+參數(shù)列表”。

            既然返回類型也考慮到映射機(jī)制中,這樣不同的返回類型映射之后的函數(shù)名肯定不一樣了,但為什么不將函數(shù)返回類型考慮到函數(shù)重載中呢?——這是為了保持解析操作符或函數(shù)調(diào)用時(shí),獨(dú)立于上下文(不依賴于上下文),看下面的例子

            float sqrt(float); 
            double sqrt(double);  
            void f(double da, float fla) 
            {       
            float fl=sqrt(da);//調(diào)用sqrt(double)       
            double d=sqrt(da);//調(diào)用sqrt(double)        
            fl=sqrt(fla);//調(diào)用sqrt(float)
            d=sqrt(fla);//調(diào)用sqrt(float) 
            }

            如果返回類型考慮到函數(shù)重載中,這樣將不可能再獨(dú)立于上下文決定調(diào)用哪個(gè)函數(shù)。

            至此似乎已經(jīng)完全分析清楚了,但我們還漏了函數(shù)重載的重要限定——作用域。上面我們介紹的函數(shù)重載都是全局函數(shù),下面我們來(lái)看一下一個(gè)類中的函數(shù)重載,用類的對(duì)象調(diào)用print函數(shù),并根據(jù)實(shí)參調(diào)用不同的函數(shù):

            #include<iostream> 
            using namespace std;  
            class test

            public:         
            void print(int i)         
            {                 
            cout
            <<"int"<<endl;         
            }         
            void print(char c)         
            {                 
            cout
            <<"char"<<endl;         

            }; 
            int main() 
            {         
            test t;         
            t.print(
            1);         
            t.print(
            'a');         
            return 0;
             }

            我們現(xiàn)在再來(lái)看一下這時(shí)print函數(shù)映射之后的函數(shù)名:

            void print(int i)                    -->            _ZN4test5printEi

            void print(char c)               -->            _ZN4test5printEc

            注意前面的N4test,我們可以很容易猜到應(yīng)該表示作用域,N4可能為命名空間、test類名等等。這說(shuō)明最準(zhǔn)確的映射機(jī)制為:作用域+返回類型+函數(shù)名+參數(shù)列表

            3、重載函數(shù)的調(diào)用匹配

            現(xiàn)在已經(jīng)解決了重載函數(shù)命名沖突的問(wèn)題,在定義完重載函數(shù)之后,用函數(shù)名調(diào)用的時(shí)候是如何去解析的?為了估計(jì)哪個(gè)重載函數(shù)最適合,需要依次按照下列規(guī)則來(lái)判斷:

            • 精確匹配:參數(shù)匹配而不做轉(zhuǎn)換,或者只是做微不足道的轉(zhuǎn)換,如數(shù)組名到指針、函數(shù)名到指向函數(shù)的指針、T到const T;
            • 提升匹配:即整數(shù)提升(如bool 到 int、char到int、short 到int),float到double
            • 使用標(biāo)準(zhǔn)轉(zhuǎn)換匹配:如int 到double、double到int、double到long double、Derived*到Base*、T*到void*、int到unsigned int;
            • 使用用戶自定義匹配
            • 使用省略號(hào)匹配:類似printf中省略號(hào)參數(shù)

            如果在最高層有多個(gè)匹配函數(shù)找到,調(diào)用將被拒絕(因?yàn)橛衅缌x、模凌兩可)。看下面的例子:


            定義太少或太多的重載函數(shù),都有可能導(dǎo)致模凌兩可,看下面的一個(gè)例子:

            void f1(char); void f1(long);  void f2(char*); void f2(int*);  void k(int i) {        f1(i);//調(diào)用f1(char)? f1(long)?        f2(0);//調(diào)用f2(char*)?f2(int*)? }

            這時(shí)侯編譯器就會(huì)報(bào)錯(cuò),將錯(cuò)誤拋給用戶自己來(lái)處理:通過(guò)顯示類型轉(zhuǎn)換來(lái)調(diào)用等等(如f2(static_cast<int *>(0),當(dāng)然這樣做很丑,而且你想調(diào)用別的方法時(shí)有用做轉(zhuǎn)換)。上面的例子只是一個(gè)參數(shù)的情況,下面我們?cè)賮?lái)看一個(gè)兩個(gè)參數(shù)的情況:

            int pow(int ,int); double pow(double,double);  void g() {        double d=pow(2.0,2)//調(diào)用pow(int(2.0),2)? pow(2.0,double(2))? }

            4、編譯器是如何解析重載函數(shù)調(diào)用的?

            編譯器實(shí)現(xiàn)調(diào)用重載函數(shù)解析機(jī)制的時(shí)候,肯定是首先找出同名的一些候選函數(shù),然后從候選函數(shù)中找出最符合的,如果找不到就報(bào)錯(cuò)。下面介紹一種重載函數(shù)解析的方法:編譯器在對(duì)重載函數(shù)調(diào)用進(jìn)行處理時(shí),由語(yǔ)法分析、C++文法、符號(hào)表、抽象語(yǔ)法樹(shù)交互處理,交互圖大致如下:

            image 

            這個(gè)四個(gè)解析步驟所做的事情大致如下:

            • 由匹配文法中的函數(shù)調(diào)用,獲取函數(shù)名;
            • 獲得函數(shù)各參數(shù)表達(dá)式類型;
            • 語(yǔ)法分析器查找重載函數(shù),符號(hào)表內(nèi)部經(jīng)過(guò)重載解析返回最佳的函數(shù)
            • 語(yǔ)法分析器創(chuàng)建抽象語(yǔ)法樹(shù),將符號(hào)表中存儲(chǔ)的最佳函數(shù)綁定到抽象語(yǔ)法樹(shù)上

             

            下面我們重點(diǎn)解釋一下重載解析,重載解析要滿足前面《3、重載函數(shù)的調(diào)用匹配》中介紹的匹配順序和規(guī)則。重載函數(shù)解析大致可以分為三步:

            • 根據(jù)函數(shù)名確定候選函數(shù)集
            • 從候選函數(shù)集中選擇可用函數(shù)集合
            • 從可用函數(shù)集中確定最佳函數(shù),或由于模凌兩可返回錯(cuò)誤

            4.1、根據(jù)函數(shù)名確定候選函數(shù)集

            根據(jù)函數(shù)在同一作用域內(nèi)所有同名的函數(shù),并且要求是可見(jiàn)的(像private、protected、public、friend之類)。“同一作用域”也是在函數(shù)重載的定義中的一個(gè)限定,如果不在一個(gè)作用域,不能算是函數(shù)重載,如下面的代碼:

            void f(int);  void g() {         void f(double);         f(1); //這里調(diào)用的是f(double),而不是f(int) }

            內(nèi)層作用域的函數(shù)會(huì)隱藏外層的同名函數(shù)同樣的派生類的成員函數(shù)會(huì)隱藏基類的同名函數(shù)。這很好理解,變量的訪問(wèn)也是如此,如一個(gè)函數(shù)體內(nèi)要訪問(wèn)全局的同名變量要用“::”限定。

            為了查找候選函數(shù)集,一般采用深度優(yōu)選搜索算法:

            step1:從函數(shù)調(diào)用點(diǎn)開(kāi)始查找,逐層作用域向外查找可見(jiàn)的候選函數(shù) 
            step2:如果上一步收集的不在用戶自定義命名空間中,則用到了using機(jī)制引入的命名空間中的候選函數(shù),否則結(jié)束

            在收集候選函數(shù)時(shí),如果調(diào)用函數(shù)的實(shí)參類型為非結(jié)構(gòu)體類型,候選函數(shù)僅包含調(diào)用點(diǎn)可見(jiàn)的函數(shù);如果調(diào)用函數(shù)的實(shí)參類型包括類類型對(duì)象、類類型指針、類類型引用或指向類成員的指針,候選函數(shù)為下面集合的并:

            • (1)在調(diào)用點(diǎn)上可見(jiàn)的函數(shù);
            • (2)在定義該類類型的名字空間或定義該類的基類的名字空間中聲明的函數(shù);
            • (3)該類或其基類的友元函數(shù);

            下面我們來(lái)看一個(gè)例子更直觀:

            void f(); void f(int); void f(double, double = 314); names pace N {      void f(char3 ,char3); } classA{     public: operat or double() { } }; int main ( ) {     using names pace N; //using指示符     A a;     f(a);     return 0; }

             

            根據(jù)上述方法,由于實(shí)參是類類型的對(duì)象,候選函數(shù)的收集分為3步:

            (1)從函數(shù)調(diào)用所在的main函數(shù)作用域內(nèi)開(kāi)始查找函數(shù)f的聲明, 結(jié)果未找到。到main函數(shù) 
            作用域的外層作用域查找,此時(shí)在全局作用域找到3個(gè)函數(shù)f的聲明,將它們放入候選集合;

            (2)到using指示符所指向的命名空間 N中收集f ( char3 , char3 ) ;

            (3)考慮2類集合。其一為定義該類類型的名字空間或定義該類的基類的名字空間中聲明的函 
            數(shù);其二為該類或其基類的友元函數(shù)。本例中這2類集合為空。

            最終候選集合為上述所列的 4個(gè)函數(shù)f。

            4.2、確定可用函數(shù)

            可用的函數(shù)是指:函數(shù)參數(shù)個(gè)數(shù)匹配并且每一個(gè)參數(shù)都有隱式轉(zhuǎn)換序列。

            • (1)如果實(shí)參有m個(gè)參數(shù),所有候選參數(shù)中,有且只有 m個(gè)參數(shù);
            • (2)所有候選參數(shù)中,參數(shù)個(gè)數(shù)不足m個(gè),當(dāng)前僅當(dāng)參數(shù)列表中有省略號(hào);
            • (3)所有候選參數(shù)中,參數(shù)個(gè)數(shù)超過(guò) m個(gè),當(dāng)前僅當(dāng)?shù)趍 + 1個(gè)參數(shù)以后都有缺省值。如果可用 
              集合為空,函數(shù)調(diào)用會(huì)失敗。

            這些規(guī)則在前面的《3、重載函數(shù)的調(diào)用匹配》中就有所體現(xiàn)了。

            4.3、確定最佳匹配函數(shù)

            確定可用函數(shù)之后,對(duì)可用函數(shù)集中的每一個(gè)函數(shù),如果調(diào)用函數(shù)的實(shí)參要調(diào)用它計(jì)算優(yōu)先級(jí),最后選出優(yōu)先級(jí)最高的。如對(duì)《3、重載函數(shù)的調(diào)用匹配》中介紹的匹配規(guī)則中按順序分配權(quán)重,然后計(jì)算總的優(yōu)先級(jí),最后選出最優(yōu)的函數(shù)。

             

            5、總結(jié)

            本文介紹了什么是函數(shù)重載、為什么需要函數(shù)重載、編譯器如何解決函數(shù)重名問(wèn)題、編譯器如何解析重載函數(shù)的調(diào)用。通過(guò)本文,我想大家對(duì)C++中的重載應(yīng)該算是比較清楚了。說(shuō)明:在介紹函數(shù)名映射機(jī)制是基于g++編譯器,不同的編譯器映射有些差別;編譯器解析重載函數(shù)的調(diào)用,也只是所有編譯器中的一種。如果你對(duì)某個(gè)編譯器感興趣,請(qǐng)自己深入去研究。

            最后我拋給大家兩個(gè)問(wèn)題:

            • 1、在C++中加號(hào)+,即可用于兩個(gè)int型之間的相加、也可以用于浮點(diǎn)數(shù)數(shù)之間的相加、字符串之間的連接,那+算不算是操作符重載呢?換個(gè)場(chǎng)景C語(yǔ)言中加號(hào)+,即可用于兩個(gè)int型之間的相加、也可以用于浮點(diǎn)數(shù)數(shù)之間的相加,那算不算操作符重載呢?
            • 2、模板(template)的重載時(shí)怎么樣的?模板函數(shù)和普通函數(shù)構(gòu)成的重載,調(diào)用時(shí)又是如何匹配的呢?

            附錄:一種C++函數(shù)重載機(jī)制

            這個(gè)機(jī)制是由張素琴等人提出并實(shí)現(xiàn)的,他們寫了一個(gè)C++的編譯系統(tǒng)COC++(開(kāi)發(fā)在國(guó)產(chǎn)機(jī)上,UNIX操作系統(tǒng)環(huán)境下具有中國(guó)自己版權(quán)的C、C++和FORTRAN語(yǔ)言編譯系統(tǒng),這些編譯系統(tǒng)分別滿足了ISOC90、AT&T的C++85和ISOFORTRAN90標(biāo)準(zhǔn))。COC++中的函數(shù)重載處理過(guò)程主要包括兩個(gè)子過(guò)程:

            • 1、在函數(shù)聲明時(shí)的處理過(guò)程中,編譯系統(tǒng)建立函數(shù)聲明原型鏈表,按照換名規(guī)則進(jìn)行換名并在函數(shù)聲明原型鏈表中記錄函數(shù)換名后的名字(換名規(guī)則跟本文上面描述的差不多,只是那個(gè)int-》為哪個(gè)字符、char-》為哪個(gè)字符等等類似的差異)

            image

            圖附1、過(guò)程1-建立函數(shù)鏈表(說(shuō)明,函數(shù)名的編碼格式為:<原函數(shù)名>_<作用域換名><函數(shù)參數(shù)表編碼>,這跟g++中的有點(diǎn)不一樣)

            • 2、在函數(shù)調(diào)用語(yǔ)句翻譯過(guò)程中,訪問(wèn)符號(hào)表,查找相應(yīng)函數(shù)聲明原型鏈表,按照類型匹配原則,查找最優(yōu)匹配函數(shù)節(jié)點(diǎn),并輸出換名后的名字下面給出兩個(gè)子過(guò)程的算法建立函數(shù)聲明原型鏈表算法流程如圖附1,函數(shù)調(diào)用語(yǔ)句翻譯算法流程如圖附2。

            image

            圖附2、過(guò)程2- 重載函數(shù)調(diào)用,查找鏈表

            附-模板函數(shù)和普通函數(shù)構(gòu)成的重載,調(diào)用時(shí)又是如何匹配的呢?

            下面是C++創(chuàng)始人Bjarne Stroustrup的回答:

            1)Find the set of function template specializations that will take part in overload resolution.

            2)if two template functions can be called and one is more specified than the other, consider only the most specialized template function in the following steps.

            3)Do overload resolution for this set of functions, plus any ordinary functions as for ordinary functions.

            4)If a function and a specialization are equally good matches, the function is perferred.

            5)If no match is found, the call is an error.

             


            作者:吳秦
            出處:http://www.cnblogs.com/skynet/

            posted on 2013-03-07 11:13 chenjt3533 閱讀(380) 評(píng)論(0)  編輯 收藏 引用 所屬分類: C/C++

            99久久99久久精品国产片果冻| 精品久久久久久久久午夜福利| 99久久精品免费看国产| 狠狠色综合久久久久尤物| 久久久久综合国产欧美一区二区| 色青青草原桃花久久综合| 久久se精品一区精品二区| 国产2021久久精品| 亚洲av伊人久久综合密臀性色| MM131亚洲国产美女久久| 久久久久国色AV免费看图片| 亚洲国产精品无码久久SM| 伊人丁香狠狠色综合久久| 亚洲精品蜜桃久久久久久| 久久久久无码精品| 久久久久久久99精品免费观看| 亚洲欧美成人久久综合中文网| 国产精品一久久香蕉国产线看观看| 久久久久无码精品国产app| 久久久久中文字幕| 人人狠狠综合久久88成人| 久久影院亚洲一区| Xx性欧美肥妇精品久久久久久| 亚洲狠狠婷婷综合久久蜜芽| 久久99久久无码毛片一区二区| 久久久久99精品成人片欧美 | 国产精品久久久久a影院| 久久精品国产亚洲AV无码娇色| 亚洲精品国产自在久久| 久久AⅤ人妻少妇嫩草影院| 国产美女久久久| 久久99亚洲网美利坚合众国| 成人久久免费网站| 午夜精品久久久久久久| 亚洲国产一成人久久精品| 无码国内精品久久综合88| 日本高清无卡码一区二区久久| 久久WWW免费人成—看片| 久久91这里精品国产2020| 九九久久精品无码专区| 思思久久99热免费精品6|