国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C++標準之(ravalue reference) 右值引用介紹

C++標準之(ravalue reference) 右值引用介紹

2020-11-11 16:57C++教程網 C/C++

臨時對象的產生和拷貝所帶來的效率折損,一直是C++所為人詬病的問題,下面簡單地介紹一下Copy Elision、RVO,對此不感興趣的可以直接跳過

1、右值引用引入的背景
臨時對象的產生和拷貝所帶來的效率折損,一直是C++所為人詬病的問題。但是C++標準允許編譯器對于臨時對象的產生具有完全的自由度,從而發展出了CopyElision、RVO(包括NRVO)等編譯器優化技術,它們可以防止某些情況下臨時對象產生和拷貝。下面簡單地介紹一下CopyElision、RVO,對此不感興趣的可以直接跳過:
(1)CopyElision
CopyElision技術是為了防止某些不必要的臨時對象產生和拷貝,例如:

復制代碼 代碼如下:


structA{
A(int){}
A(constA&){}
};
Aa=42;


理論上講,上述Aa=42;語句將分三步操作:第一步由42構造一個A類型的臨時對象,第二步以臨時對象為參數拷貝構造a,第三步析構臨時對象。如果A是一個很大的類,那么它的臨時對象的構造和析構將造成很大的內存開銷。我們只需要一個對象a,為什么不直接以42為參數直接構造a呢?CopyElision技術正是做了這一優化。
【說明】:你可以在A的拷貝構造函數中加一打印語句,看有沒有調用,如果沒有被調用,那么恭喜你,你的編譯器支持CopyElision。但是需要說明的是:A的拷貝構造函數雖然沒有被調用,但是它的實現不能沒有訪問權限,不信你將它放在private權限里試試,編譯器肯定會報錯。
(2)返回值優化(RVO,ReturnValueOptimization)
返回值優化技術也是為了防止某些不必要的臨時對象產生和拷貝,例如:

復制代碼 代碼如下:


structA{
A(int){}
A(constA&){}
};
Aget(){returnA(1);}
Aa=get();


理論上講,上述Aa=get();語句將分別執行:首先get()函數中創建臨時對象(假設為tmp1),然后以tmp1為參數拷貝構造返回值(假設為tmp2),最后再以tmp2為參數拷貝構造a,其中還伴隨著tmp1和tmp2的析構。如果A是一個很大的類,那么它的臨時對象的構造和析構將造成很大的內存開銷。返回值優化技術正是用來解決此問題的,它可以避免tmp1和tmp2兩個臨時對象的產生和拷貝。
【說明】:a)你可以在A的拷貝構造函數中加一打印語句,看有沒有調用,如果沒有被調用,那么恭喜你,你的編譯器支持返回值優化。但是需要說明的是:A的拷貝構造函數雖然沒有被調用,但是它的實現不能沒有訪問權限,不信你將它放在private權限里試試,編譯器肯定會報錯。
b)除了返回值優化,你可能還聽說過一個叫具名返回值優化(NamedReturnValueOptimization,NRVO)的優化技術,從程序員的角度而言,它其實跟RVO同樣的邏輯。只是它的臨時對象具有變量名標識,例如修改上述get()函數為:

復制代碼 代碼如下:


Aget(){
Atmp(1);//#1
//dosomething
returntmp;
}
Aa=get();//#2


想想上述修改后A類型共有幾次對象構造?雖然#1處看起來有一次顯示地構造,#2處看起來也有一次顯示地構造,但如果你的編譯器支持NRVO和CopyElision,你會發現整個Aa=get();語句的執行過程,只有一次A對象的構造。如果你在get()函數return語句前打印tmp變量的地址,在Aa=get();語句后打印a的地址,你會發現兩者地址相同,這就是應用了NRVO技術的結果。
(3)CopyElision、RVO無法避免的臨時對象的產生和拷貝
雖然CopyElision和NVO(包括NRVO)等技術能避免一些臨時對象的產生和拷貝,但某些情況下它們卻發揮不了作用,例如:

復制代碼 代碼如下:


template<typenameT>
voidswap(T&a,T&b){
Ttmp(a);
a=b;
b=tmp;
}


我們只是想交換a和b兩個對象所擁有的數據,但卻不得不使用一個臨時對象tmp備份其中一個對象,如果T類型對象擁有指向(或引用)從堆內存分配的數據,那么深拷貝所帶來的內存開銷是可以想象的。為此,C++11標準引入了右值引用,使用它可以使臨時對象的拷貝具有move語意,從而可以使臨時對象的拷貝具有淺拷貝般的效率,這樣便可以從一定程度上解決臨時對象的深度拷貝所帶來的效率折損。

2、C++03標準中的左值與右值
要理解右值引用,首先得區分左值(lvalue)和右值(rvalue)。
C++03標準中將表達式分為左值和右值,并且“非左即右”:
Everyexpressioniseitheranlvalueoranrvalue.
區分一個表達式是左值還是右值,最簡便的方法就是看能不能夠對它取地址:如果能,就是左值;否則,就是右值。
【說明】:由于右值引用的引入,C++11標準中對表達式的分類不再是“非左即右”那么簡單,不過為了簡單地理解,我們暫時只需區分左值右值即可,C++11標準中的分類后面會有描述。

3、右值引用的綁定規則
右值引用(rvaluereference,&&)跟傳統意義上的引用(reference,&)很相似,為了更好地區分它們倆,傳統意義上的引用又被稱為左值引用(lvaluereference)。下面簡單地總結了左值引用和右值引用的綁定規則(函數類型對象會有所例外):
(1)非const左值引用只能綁定到非const左值;
(2)const左值引用可綁定到const左值、非const左值、const右值、非const右值;
(3)非const右值引用只能綁定到非const右值;
(4)const右值引用可綁定到const右值和非const右值。
測試例子如下:

復制代碼 代碼如下:


structA{A(){}};
Alvalue;//非const左值對象
constAconst_lvalue;//const左值對象
Arvalue(){returnA();}//返回一個非const右值對象
constAconst_rvalue(){returnA();}//返回一個const右值對象
//規則一:非const左值引用只能綁定到非const左值
A&lvalue_reference1=lvalue;//ok
A&lvalue_reference2=const_lvalue;//error
A&lvalue_reference3=rvalue();//error
A&lvalue_reference4=const_rvalue();//error
//規則二:const左值引用可綁定到const左值、非const左值、const右值、非const右值
constA&const_lvalue_reference1=lvalue;//ok
constA&const_lvalue_reference2=const_lvalue;//ok
constA&const_lvalue_reference3=rvalue();//ok
constA&const_lvalue_reference4=const_rvalue();//ok
//規則三:非const右值引用只能綁定到非const右值
A&&rvalue_reference1=lvalue;//error
A&&rvalue_reference2=const_lvalue;//error
A&&rvalue_reference3=rvalue();//ok
A&&rvalue_reference4=const_rvalue();//error
//規則四:const右值引用可綁定到const右值和非const右值,不能綁定到左值
constA&&const_rvalue_reference1=lvalue;//error
constA&&const_rvalue_reference2=const_lvalue;//error
constA&&const_rvalue_reference3=rvalue();//ok
constA&&const_rvalue_reference4=const_rvalue();//ok
//規則五:函數類型例外
voidfun(){}
typedefdecltype(fun)FUN;//typedefvoidFUN();
FUN&lvalue_reference_to_fun=fun;//ok
constFUN&const_lvalue_reference_to_fun=fun;//ok
FUN&&rvalue_reference_to_fun=fun;//ok
constFUN&&const_rvalue_reference_to_fun=fun;//ok


【說明】:(1)一些支持右值引用但版本較低的編譯器可能會允許右值引用綁定到左值,例如g++4.4.4就允許,但g++4.6.3就不允許了,clang++3.2也不允許,據說VS2010beta版允許,正式版就不允許了,本人無VS2010環境,沒測試過。
(2)右值引用綁定到字面值常量同樣符合上述規則,例如:int&&rr=123;,這里的字面值123雖然被稱為常量,可它的類型為int,而不是constint。對此C++03標準文檔4.4.1節及其腳注中有如下說明:
IfTisanon-classtype,thetypeofthervalueisthecv-unqualifiedversionofT.
InC++classrvaluescanhavecv-qualifiedtypes(becausetheyareobjects).ThisdiffersfromISOC,inwhichnon-lvaluesneverhavecv-qualifiedtypes.
因此123是非const右值,int&&rr=123;語句符合上述規則三。

4、C++11標準中的表達式分類
右值引用的引入,使得C++11標準中對表達式的分類不再是非左值即右值那么簡單,下圖為C++11標準中對表達式的分類:

C++標準之(ravalue reference) 右值引用介紹

 
簡單解釋如下:
(1)lvalue仍然是傳統意義上的左值;
(2)xvalue(eXpiringvalue)字面意思可理解為生命周期即將結束的值,它是某些涉及到右值引用的表達式的值(Anxvalueistheresultofcertainkindsofexpressionsinvolvingrvaluereferences),例如:調用一個返回類型為右值引用的函數的返回值就是xvalue。
(3)prvalue(purervalue)字面意思可理解為純右值,也可認為是傳統意義上的右值,例如臨時對象和字面值等。
(4)glvalue(generalizedvalue)廣義的左值,包括傳統的左值和xvalue。
(5)rvalue除了傳統意義上的右值,還包括xvalue。
上述lvalue和prvalue分別跟傳統意義上的左值和右值概念一致,比較明確,而將xvalue描述為『某些涉及到右值引用的表達式的值』,某些是哪些呢?C++11標準給出了四種明確為xvalue的情況:

復制代碼 代碼如下:


[Note:Anexpressionisanxvalueifitis:
--theresultofcallingafunction,whetherimplicitlyorexplicitly,whosereturntypeisanrvaluereferencetoobjecttype,
--acasttoanrvaluereferencetoobjecttype,
--aclassmemberaccessexpressiondesignatinganon-staticdatamemberofnon-referencetypeinwhichtheobjectexpressionisanxvalue,or
--a.*pointer-to-memberexpressioninwhichthefirstoperandisanxvalueandthesecondoperandisapointertodatamember.
Ingeneral,theeffectofthisruleisthatnamedrvaluereferencesaretreatedaslvaluesandunnamedrvaluereferencestoobjectsaretreatedasxvalues;rvaluereferencestofunctionsaretreatedaslvalueswhethernamedornot.--endnote]
[Example:
structA{
intm;
};
A&&operator+(A,A);
A&&f();
Aa;
A&&ar=static_cast<A&&>(a);
Theexpressionsf(),f().m,static_cast<A&&>(a),anda+aarexvalues.Theexpressionarisanlvalue.
--endexample]


簡單地理解就是:具名的右值引用(namedrvaluereference)屬于左值,不具名的右值引用(unamedrvaluereference)就屬于xvalue,而引用函數類型的右值引用不論是否具名都當做左值處理。看個例子更容易理解:
[/code]
Arvalue(){returnA();}
A&&rvalue_reference(){returnA();}
fun();//返回的是不具名的右值引用,屬于xvalue
A&&ra1=rvalue();//ra1是具名右值應用,屬于左值
A&&ra2=ra1;//error,ra1被當做左值對待,因此ra2不能綁定到ra1(不符合規則三)
A&la=ra1;//ok,非const左值引用可綁定到非const左值(符合規則一)

復制代碼 代碼如下:


5、move語意
現在,我們重新顧到1-(3),其中提到move語意,那么怎樣才能使臨時對象的拷貝具有move語意呢?下面我們以一個類的實現為例:
[code]
classA{
public:
A(constchar*pstr=0){m_data=(pstr!=0?strcpy(newchar[strlen(pstr)+1],pstr):0);}
//copyconstructor
A(constA&a){m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);}
//copyassigment
A&operator=(constA&a){
if(this!=&a){
delete[]m_data;
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
return*this;
}
//moveconstructor
A(A&&a):m_data(a.m_data){a.m_data=0;}
//moveassigment
A&operator=(A&&a){
if(this!=&a){
m_data=a.m_data;
a.m_data=0;
}
return*this;
}
~A(){delete[]m_data;}
private:
char*m_data;
};


從上例可以看到,除了傳統的拷貝構造(copyconstructor)和拷貝賦值(copyassigment),我們還為A類的實現添加了移動拷貝構造(moveconstructor)和移動賦值(moveassigment)。這樣,當我們拷貝一個A類的(右值)臨時對象時,就會使用具有move語意的移動拷貝構造函數,從而避免深拷貝中strcpy()函數的調用;當我們將一個A類的(右值)臨時對象賦值給另一個對象時,就會使用具有move語意的移動賦值,從而避免拷貝賦值中strcpy()函數的調用。這就是所謂的move語意。

6、std::move()函數的實現
了解了move語意,那么再來看1-(3)中的效率問題:

復制代碼 代碼如下:


template<typenameT>//如果T是classA
voidswap(T&a,T&b){
Ttmp(a);//根據右值引用的綁定規則三可知,這里不會調用moveconstructor,而會調用copyconstructor
a=b;//根據右值引用的綁定規則三可知,這里不會調用moveassigment,而會調用copyassigment
b=tmp;//根據右值引用的綁定規則三可知,這里不會調用moveassigment,而會調用copyassigment
}


從上例可以看到,雖然我們實現了moveconstructor和moveassigment,但是swap()函數的例子中仍然使用的是傳統的copyconstructor和copyassigment。要讓它們真正地使用move語意的拷貝和復制,就該std::move()函數登場了,看下面的例子:

復制代碼 代碼如下:


voidswap(A&a,A&b){
Atmp(std::move(a));//std::move(a)為右值,這里會調用moveconstructor
a=std::move(b);//std::move(b)為右值,這里會調用moveassigment
b=std::move(tmp);//std::move(tmp)為右值,這里會調用moveassigment
}


我們不禁要問:我們通過右值應用的綁定規則三和規則四,知道右值引用不能綁定到左值,可是std::move()函數是如何把上述的左值a、b和tmp變成右值的呢?這就要從std::move()函數的實現說起,其實std::move()函數的實現非常地簡單,下面以libcxx庫中的實現(在<type_trait>頭文件中)為例:

復制代碼 代碼如下:


template<class_Tp>
inlinetypenameremove_reference<_Tp>::type&&move(_Tp&&__t){
typedeftypenameremove_reference<_Tp>::type_Up;
returnstatic_cast<_Up&&>(__t);
}


其中remove_reference的實現如下:

復制代碼 代碼如下:


template<class_Tp>structremove_reference{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&>{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&&>{typedef_Tptype;};


從move()函數的實現可以看到,move()函數的形參(Parameter)類型為右值引用,它怎么能綁定到作為實參(Argument)的左值a、b和tmp呢?這不是仍然不符合右值應用的綁定規則三嘛!簡單地說,如果move只是個普通的函數(而不是模板函數),那么根據右值應用的綁定規則三和規則四可知,它的確不能使用左值作為其實參。但它是個模板函數,牽涉到模板參數推導,就有所不同了。C++11標準文檔14.8.2.1節中,關于模板函數參數的推導描述如下:
Templateargumentdeductionisdonebycomparingeachfunctiontemplateparametertype(callitP)withthetypeofthecorrespondingargumentofthecall(callitA)asdescribedbelow.(14.8.2.1.1)
IfPisareferencetype,thetypereferredtobyPisusedfortypededuction.IfPisanrvaluereferencetoacvunqualifiedtemplateparameterandtheargumentisanlvalue,thetype"lvaluereferencetoA"isusedinplaceofAfortypededuction.(14.8.2.1.3)
大致意思是:模板參數的推導其實就是形參和實參的比較和匹配,如果形參是一個引用類型(如P&),那么就使用P來做類型推導;如果形參是一個cv-unqualified(沒有const和volatile修飾的)右值引用類型(如P&&),并且實參是一個左值(如類型A的對象),就是用A&來做類型推導(使用A&代替A)。

復制代碼 代碼如下:


template<class_Tp>voidf(_Tp&&){/*dosomething*/}
template<class_Tp>voidg(const_Tp&&){/*dosomething*/}
intx=123;
f(x);//ok,f()模板函數形參為非const非volatile右值引用類型,實參x為int類型左值,使用int&來做參數推導,因此調用f<int&>(int&)
f(456);//ok,實參為右值,調用f<int>(int&&)
g(x);//error,g()函數模板參數為const右值引用類型,會調用g<int>(constint&&),通過右值引用規則四可知道,const右值引用不能綁定到左值,因此會導致編譯錯誤


了解了模板函數參數的推導過程,已經不難理解std::move()函數的實現了,當使用左值(假設其類型為T)作為參數調用std::move()函數時,實際實例化并調用的是std::move<T&>(T&),而其返回類型T&&,這就是move()函數左值變右值的過程(其實左值本身仍是左值,只是被當做右值對待而已,被人“抄了家”,變得一無所有)。
【說明】:C++的始祖BjarneStroustrup說:如果move()函數改名為rval()可能會更好些,但是move()這個名字已經被使用了好些年了(Maybeitwouldhavebeenbetterifmove()hadbeencalledrval(),butbynowmove()hasbeenusedforyears.)。

7、完整的示例
至此,我們已經了解了不少右值引用的知識點了,下面給出了一個完整地利用右值引用實現move語意的例子:

復制代碼 代碼如下:


#include<iostream>
#include<cstring>
#definePRINT(msg)do{std::cout<<msg<<std::endl;}while(0)
template<class_Tp>structremove_reference{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&>{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&&>{typedef_Tptype;};
template<class_Tp>
inlinetypenameremove_reference<_Tp>::type&&move(_Tp&&__t){
typedeftypenameremove_reference<_Tp>::type_Up;
returnstatic_cast<_Up&&>(__t);
}
classA{
public:
A(constchar*pstr){
PRINT("constructor");
m_data=(pstr!=0?strcpy(newchar[strlen(pstr)+1],pstr):0);
}
A(constA&a){
PRINT("copyconstructor");
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
A&operator=(constA&a){
PRINT("copyassigment");
if(this!=&a){
delete[]m_data;
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
return*this;
}
A(A&&a):m_data(a.m_data){
PRINT("moveconstructor");
a.m_data=0;
}
A&operator=(A&&a){
PRINT("moveassigment");
if(this!=&a){
m_data=a.m_data;
a.m_data=0;
}
return*this;
}
~A(){PRINT("destructor");delete[]m_data;}
private:
char*m_data;
};
voidswap(A&a,A&b){
Atmp(move(a));
a=move(b);
b=move(tmp);
}
intmain(intargc,char**argv,char**env){
Aa("123"),b("456");
swap(a,b);
return0;
}


輸出結果為:

復制代碼 代碼如下:


constructor
constructor
moveconstructor
moveassigment
moveassigment
destructor
destructor
destructor


8、幕后花絮
C++11標準引入右值引用的提案是由HowardHinnant提出的,它的最初提案N1377在02年就提出來了,中間經歷了多次修改N1385、N1690、N1770、N1855、N1952、N2118。包括它的最終版本N2118在內,HowardHinnant的提案中都使用了右值引用直接綁定到左值的例子,并且由HowardHinnant、BjarneStroustrup和BronekKozicki三人08年10月共同署名的《ABriefIntroductiontoRvalueReferences》文章中也有右值引用直接綁定到左值的例子,但奇怪的是11年公布的最新的C++11標準文檔中卻不允許右值引用直接綁定到左值,其中的原因不得而知,但從中不難理解為什么早些編譯器版本(如g++4.4.4)對右值引用綁定到左值,不會報出編譯錯誤,而最新的編譯器卻會報錯。
另外,HowardHinnant是C++標準委員會LibraryWorkingGroup老大(chairman),libcxx和libcxxabi的維護者,蘋果公司的高級軟件工程師。

延伸 · 閱讀

精彩推薦
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 在线国产视频观看 | 亚洲精品乱码久久久久久麻豆不卡 | 中文字幕一区二区三区四区 | av网站观看 | 视频一区 中文字幕 | 久久亚洲视频 | 99精品免费 | 亚洲精品一区二区三区蜜桃久 | 中文字幕日韩一区 | 欧美日韩成人一区二区 | 一区二区三区久久久久 | 国产精品178页 | 日韩精品一区二区在线观看视频 | 国产成人一区 | 99精品国产高清一区二区麻豆 | 亚洲欧洲精品成人久久奇米网 | 中文色视频 | 欧美精品乱码久久久久久按摩 | 992人人tv香蕉国产精品 | 国产精品网站在线观看 | 久久久精品日本 | 国产成人在线一区二区 | 中文字幕一区二区三区精彩视频 | 精品视频在线免费观看 | 自拍偷拍欧美 | 国产一区 | 亚洲2020天天堂在线观看 | 一级毛片免费高清 | 好看的国产精彩视频 | 精品国产一区二区三区日日嗨 | 91免费版在线观看 | 久久性色 | 男人午夜视频在线观看 | 欧美在线视频网站 | 欧洲精品久久久 | 久热在线视频 | 青青草在线视频免费观看 | 成年免费视频 | 亚洲国产一区二区三区 | 精品国产髙清在线看国产毛片 | bxbx成人精品一区二区三区 |