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

            longshanks

              C++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              14 Posts :: 0 Stories :: 214 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(10)

            我參與的團隊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            C++的營養

            莫華楓
                動物都會攝取食物,吸收其中的營養,用于自身生長和活動。然而,并非食物中所有的物質都能為動物所吸收。那些無法消化的物質,通過消化道的另一頭(某些動 物消化道只有一頭)排出體外。不過,一種動物無法消化的排泄物,是另一種動物(生物)的食物,后者可以從中攝取所需的營養。
                一門編程語言,對于程序員而言,如同食物那樣,包含著所需的養分。當然也包含著無法消化的東西。不同的是,隨著程序員不斷成長,會逐步消化過去無法消化的那些東西。
                C++可以看作一種成分復雜的食物,對于多數程序員而言,是無法完全消化的。正因為如此,很多程序員認為C++太難以消化,不應該去吃它。但是,C++的 營養不可謂不豐富,就此舍棄,而不加利用,則是莫大的罪過。好在食物可以通過加工,變得易于吸收,比如說發酵。鑒于程序員們的消化能力的差異,也為了讓C ++的營養能夠造福他人,我就暫且扮演一回酵母菌,把C++的某些營養單獨提取出來,并加以分解,讓那些消化能力不太強的程序員也能享受它的美味。:)
                (為了讓這些營養便于消化,我將會用C#做一些案例。選擇C#的原因很簡單,因為我熟悉。:))

            RAII

                RAII,好古怪的營養啊!它的全稱應該是“Resource Acquire Is Initial”。這是C++創始人Bjarne Stroustrup發明的詞匯,比較令人費解。說起來,RAII的含義倒也不算復雜。用白話說就是:在類的構造函數中分配資源,在析構函數中釋放資源。 這樣,當一個對象創建的時候,構造函數會自動地被調用;而當這個對象被釋放的時候,析構函數也會被自動調用。于是乎,一個對象的生命期結束后將會不再占用 資源,資源的使用是安全可靠的。
                下面便是在C++中實現RAII的典型代碼:
                    class file
                    {
                    
            public:
                        file(
            string const& name) {
                               m_fileHandle
            =open_file(name.cstr());
                        }
                        
            ~file() {
                               close_file(m_fileHandle);
                        }
                        ...
                    
            private:
                        handle m_fileHandle;
                    }
                很典型的“在構造函數里獲取,在析構函數里釋放”。如果我寫下代碼:       
                    void fun1() {
                        file myfile(
            "my.txt");
                        ... 
            //操作文件
                    }
                //此處銷毀對象,調用析構函數,釋放資源
                當函數結束時,局部對象myfile的生命周期也結束了,析構函數便會被調用,資源會得到釋放。而且,如果函數中的代碼拋出異常,那么析構函數也會被調用,資源同樣會得到釋放。所以,在RAII下,不僅僅資源安全,也是異常安全的。
                但是,在如下的代碼中,資源不是安全的,盡管我們實現了RAII:
                     void fun2() {
                         file pfile
            =new file("my.txt");
                            ... 
            //操作文件
                     }
                因為我們在堆上創建了一個對象(通過new),但是卻沒有釋放它。我們必須運用delete操作符顯式地加以釋放:
                    void fun3() {
                         file pfile
            =new file("my.txt");
                            ... 
            //操作文件
                            delete pfile;
                    }
                否則,非但對象中的資源得不到釋放,連對象本身的內存也得不到回收。(將來,C++的標準中將會引入GC(垃圾收集),但正如下面分析的那樣,GC依然無法確保資源的安全)。
                現在,在fun3(),資源是安全的,但卻不是異常安全的。因為一旦函數中拋出異常,那么delete pfile;這句代碼將沒有機會被執行。C++領域的諸位大牛們告誡我們:如果想要在沒有GC的情況下確保資源安全和異常安全,那么請使用智能指針:
                    void fun4() {
                          shared_ptr
            <file> spfile(new file("my.txt"));
                          ... 
            //操作文件
                    }
              //此處,spfile結束生命周期的時候,會釋放(delete)對象
                那么,智能指針又是怎么做到的呢?下面的代碼告訴你其中的把戲(關于智能指針的更進一步的內容,請參考std::auto_ptr,boost或tr1的智能指針):
                    template<typename T>
                    
            class smart_ptr
                    
            {
                    
            public:
                        smart_ptr(T
            * p):m_ptr(p) {}
                        
            ~smart_ptr() { delete m_ptr; }
                        ...
                    
            private:
                        T
            * m_ptr;
                    }
                沒錯,還是RAII。也就是說,智能指針通過RAII來確保內存資源的安全,也間接地使得對象上的RAII得到實施。不過,這里的RAII并不是十分嚴 格:對象(所占的內存也是資源)的創建(資源獲取)是在構造函數之外進行的。廣義上,我們也把它劃歸RAII范疇。但是,Matthew Wilson在《Imperfect C++》一書中,將其獨立出來,稱其為RRID(Resource Release Is Destruction)。RRID的實施需要在類的開發者和使用者之間建立契約,采用相同的方法獲取和釋放資源。比如,如果在shared_ptr構造 時使用malloc(),便會出現問題,因為shared_ptr是通過delete釋放對象的。
                對于內置了GC的語言,資源管理相對簡單。不過,事情并非總是這樣。下面的C#代碼摘自MSDN Library的C#編程指南,我略微改造了一下:
                    static void CodeWithoutCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                        file 
            = fileInfo.OpenWrite();
                        file.WriteByte(
            0xF);
                    }
                那么資源會不會泄漏呢?這取決于對象的實現。如果通過OpenWrite()獲得的FileStream對象,在析構函數中執行了文件的釋放操作,那么資 源最終不會泄露。因為GC最終在執行GC操作的時候,會調用Finalize()函數(C#類的析構函數會隱式地轉換成Finalize()函數的重 載)。這是由于C#使用了引用語義(嚴格地講,是對引用類型使用引用語義),一個對象實際上不是對象本身,而是對象的引用。如同C++中的那樣,引用在離 開作用域時,是不會釋放對象的。否則,便無法將一個對象直接傳遞到函數之外。在這種情況下,如果沒有顯式地調用Close()之類的操作,資源將不會得到 立刻釋放。但是像文件、鎖、數據庫鏈接之類屬于重要或稀缺的資源,如果等到GC執行回收,會造成資源不足。更有甚者,會造成代碼執行上的問題。我曾經遇到 過這樣一件事:我執行了一個sql操作,獲得一個結果集,然后執行下一個sql,結果無法執行。這是因為我使用的SQL Server 2000不允許在一個數據連接上同時打開兩個結果集(很多數據庫引擎都是這樣)。第一個結果集用完后沒有立刻釋放,而GC操作則尚未啟動,于是便造成在一 個未關閉結果集的數據連接上無法執行新的sql的問題。
                所以,只要涉及了內存以外的資源,應當盡快釋放。(當然,如果內存能夠盡快釋放,就更好了)。對于上述CodeWithoutCleanup()函數,應當在最后調用file對象上的Close()函數,以便釋放文件:
                    static void CodeWithoutCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                        file 
            = fileInfo.OpenWrite();
                        file.WriteByte(
            0xF);
                        file.Close();
                    }
                現在,這個函數是嚴格資源安全的,但卻不是嚴格異常安全的。如果在文件的操作中拋出異常,Close()成員將得不到調用。此時,文件也將無法及時關閉,直到GC完成。為此,需要對異常作出處理:
                    static void CodeWithCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = null;
                        
            try
                        
            {
                            fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                            file 
            = fileInfo.OpenWrite();
                            file.WriteByte(
            0xF);
                        }

                        
            catch(System.Exception e)
                        
            {
                            System.Console.WriteLine(e.Message);
                        }

                        
            finally
                        
            {
                            
            if (file != null)
                            
            {
                                file.Close();
                            }

                        }

                   }
                try-catch-finally是處理這種情況的標準語句。但是,相比前面的C++代碼fun1()和fun4()繁瑣很多。這都是沒有RAII的后果啊。下面,我們就來看看,如何在C#整出RAII來。
                一個有效的RAII應當包含兩個部分:構造/析構函數的資源獲取/釋放和確定性的析構函數調用。前者在C#中不成問題,C#有構造函數和析構函數。不過, C#的構造函數和析構函數是不能用于RAII的,原因一會兒會看到。正確的做法是讓一個類實現IDisposable接口,在IDisposable:: Dispose()函數中釋放資源:
                    class RAIIFile : IDisposable
                    
            {
                    
            public RAIIFile(string fn) {
                            System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo(fn);
                            file 
            = fileInfo.OpenWrite();
                        }


                    
            public void Dispose() {
                              file.Close();
                          }


                    
            private System.IO.FileStream file = null;
                    }
                下一步,需要確保文件在退出作用域,或發生異常時被確定性地釋放。這項工作需要通過C#的using語句實現:
                    static void CodeWithRAII()
                    
            {
                        
            using(RAIIFile file=new RAIIFile("C:\file.txt"))
                        
            {
                            ... 
            //操作文件
                        }
             //文件釋放
                    }
                一旦離開using的作用域,file.Dispose()將被調用,文件便會得到釋放,即便拋出異常,亦是如此。相比CodeWithCleanup ()中那坨雜亂繁復的代碼,CodeWithRAII()簡直可以算作賞心悅目。更重要的是,代碼的簡潔和規則將會大幅減少出錯可能性。值得注意的是 using語句只能作用于實現IDisposable接口的類,即便實現了析構函數也不行。所以對于需要得到RAII的類,必須實現 IDisposable。通常,凡是涉及到資源的類,都應該實現這個接口,便于日后使用。實際上,.net庫中的很多與非內存資源有關的類,都實現了 IDisposable,都可以利用using直接實現RAII。
                但是,還有一個問題是using無法解決的,就是如何維持類的成員函數的RAII。我們希望一個類的成員對象在該類實例創建的時候獲取資源,而在其銷毀的時候釋放資源:
                    class X
                    
            {
                    
            public:
                        X():m_file(
            "c:\file.txt"{}
                    
            private:
                        File m_file;    
            //在X的實例析構時調用File::~File(),釋放資源。
                    }
                但是在C#中無法實現。由于uing中實例化的對象在離開using域的時候便釋放了,無法在構造函數中使用:
                    class X
                    
            {
                        
            public X() {
                            
            using(m_file=new RAIIFile("C:\file.txt"))
                            
            {
                            }
            //此處m_file便釋放了,此后m_file便指向無效資源
                        }

                        pravite RAIIFile m_file;
                    }
                對于成員對象的RAII只能通過在析構函數或Dispose()中手工地釋放。我還沒有想出更好的辦法來。
                至此,RAII的來龍去脈已經說清楚了,在C#里也能從中汲取到充足的養分。但是,這還不是RAII的全部營養,RAII還有更多的擴展用途。在 《Imperfect C++》一書中,Matthew Wilson展示了RAII的一種非常重要的應用。為了不落個鸚鵡學舌的名聲,這里我給出一個真實遇到的案例,非常簡單:我寫的程序需要響應一個Grid 控件的CellTextChange事件,執行一些運算。在響應這個事件(執行運算)的過程中,不能再響應同一個事件,直到處理結束。為此,我設置了一個 標志,用來控制事件響應:
                    class MyForm
                    
            {
                    
            public:
                        MyForm():is_cacul(
            false{}
                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            is_cacul
            =true;
                            ... 
            //執行計算任務
                            is_cacul=false;
                        }

                    
            private:
                        
            bool is_cacul;
                    }
            ;
                但是,這里的代碼不是異常安全的。如果在執行計算的過程中拋出異常,那么is_cacul標志將永遠是true。此后,即便是正常的 CellTextChange也無法得到正確地響應。同前面遇到的資源問題一樣,傳統上我們不得不求助于try-catch語句。但是如果我們運用 RAII,則可以使得代碼簡化到不能簡化,安全到不能再安全。我首先做了一個類:
                    class BoolScope
                    
            {
                    
            public:
                        BoolScope(
            bool& val, bool newVal)
                            :m_val(val), m_old(val) 
            {
                            m_val
            =newVal;
                        }

                        
            ~BoolScope() {
                            m_val
            =m_old;
                        }


                    
            private:
                        
            bool& m_val;
                        
            bool m_old;
                    }
            ;
                這個類的作用是所謂“域守衛(scoping)”,構造函數接受兩個參數:第一個是一個bool對象的引用,在構造函數中保存在m_val成員里;第二個 是新的值,將被賦予傳入的那個bool對象。而該對象的原有值,則保存在m_old成員中。析構函數則將m_old的值返還給m_val,也就是那個 bool對象。有了這個類之后,便可以很優雅地獲得異常安全:
                    class MyForm
                    
            {
                    
            public:
                        MyForm():is_cacul(
            false{}
                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            BoolScope bs_(is_cacul, 
            true);
                            ... 
            //執行計算任務
                        }

                    
            private:
                        
            bool is_cacul;
                    }
            ;
                好啦,任務完成。在bs_創建的時候,is_cacul的值被替換成true,它的舊值保存在bs_對象中。當OnCellTextChange()返回 時,bs_對象會被自動析構,析構函數會自動把保存起來的原值重新賦給is_cacul。一切又都回到原先的樣子。同樣,如果異常拋出,is_cacul 的值也會得到恢復。
                這個BoolScope可以在將來繼續使用,分攤下來的開發成本幾乎是0。更進一步,可以開發一個通用的Scope模板,用于所有類型,就像《Imperfect C++》里的那樣。
                下面,讓我們把戰場轉移到C#,看看C#是如何實現域守衛的。考慮到C#(.net)的對象模型的特點,我們先實現引用類型的域守衛,然后再來看看如何對付值類型。其原因,一會兒會看到。
                我曾經需要向一個grid中填入數據,但是填入的過程中,控件不斷的刷新,造成閃爍,也影響性能,除非把控件上的AutoDraw屬性設為false。為此,我做了一個域守衛類,在填寫操作之前關上AutoDraw,完成或異常拋出時再打開:
                    class DrawScope : IDisposable
                    
            {
                        
            public DrawScope(Grid g, bool val) {
                            m_grid
            =g;
                            m_old
            =g->AutoDraw;
                            m_grid
            ->AutoDraw=val;
                        }

                        
            public void Dispose() {
                                g
            ->AutoDraw=m_old;
                           }

                        
            private Grid m_grid;
                        
            private bool m_old;
                    }
            ;
                于是,我便可以如下優雅地處理AutoDraw屬性設置問題:
                    static void LoadData(Grid g) {
                        
            using(DrawScope ds=new DrawScope(g, false))
                        
            {
                            ... 
            //執行數據裝載
                        }

                    }
                現在,我們回過頭,來實現值類型的域守衛。案例還是采用前面的CellTextChange事件。當我試圖著手對那個is_cacul執行域守衛時,遇到了不小的麻煩。起初,我寫下了這樣的代碼:
                    class BoolScope
                    
            {
                        
            private ??? m_val; //此處用什么類型?
                        private bool m_old;
                    }
            ;
                m_val應當是一個指向一個對象的引用,C#是沒有C++那些指針和引用的。在C#中,引用類型定義的對象實際上是一個指向對象的引用;而值類型定義的 對象實際上是一個對象,或者說“棧對象”,但卻沒有一種指向值類型的引用。(關于這種對象模型的優劣,后面的“題外話”小節有一些探討)。我嘗試著采用兩 種辦法,一種不成功,而另一種成功了。
                C#(.net)有一種box機制,可以將一個值對象打包,放到堆中創建。這樣,或許可以把一個值對象編程引用對象,構成C#可以引用的東西:
                    class BoolScope : IDisposable
                    
            {
                        
            public BoolScope(object val, bool newVal) {
                                m_val
            =val;                 //#1
                                m_old=(bool)val;
                                (
            bool)m_val=newVal;    //#2
                        }

                        
            public void Dispose() {
                                (
            bool)m_val=m_old;    //#3
                           }

                        
            private object m_val;
                        
            private bool m_old;
                    }
                使用時,應當采用如下形式:
                    class MyForm
                    
            {
                        
            public MyForm() {
                            is_cacul
            =new bool(false); //boxing
                        }

                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            
            using(BoolScope bs=new BoolScope(is_cacul, true))
                            
            {
                                ... 
            //執行計算任務
                            }

                        }

                        
            private object is_cacul;
                    }
            ;
                很可惜,此路不通。因為在代碼#1的地方,并未執行引用語義,而執行了值語義。也就是說,沒有把val(它是個引用)的值賦給m_val(也是個引用), 而是為m_val做了個副本。以至于在代碼#2和#3處無法將newVal和m_old賦予val(也就是is_cacul)。或許C#的設計者有無數理 由說明這種設計的合理性,但是在這里,卻扼殺了一個非常有用的idom。而且,缺少對值對象的引用手段,大大限制了語言的靈活性和擴展性。
                第二種方法就非常直白了,也絕對不應當出問題,就是使用包裝類:
                    class BoolVal
                    
            {
                        
            public BoolVal(bool v)
                        
            {
                            m_val
            =v;
                        }

                        
            public bool getVal() {
                            
            return m_val;
                        }

                        
            public void setVal(bool v) {
                            m_val
            =v;
                        }

                        
            private bool m_val;
                    }

                    
            class BoolScope : IDisposable
                    
            {
                        
            public IntScope(BoolVal iv, bool v)
                        
            {
                            m_old 
            = iv.getVal();
                            m_Val 
            = iv;
                            m_Val.setVal(v);
                        }

                        
            public virtual void Dispose()
                        
            {
                            m_Val.setVal(m_old);
                        }

                        
            private BoolVal m_Val;
                        
            private bool m_old;
                    }
                這里,我做了一個包裝類BoolVal,是個引用類。然后以此為基礎,編寫了一個BoolScope類。然后,便可以正常使用域守衛:
                    class MyForm
                    
            {
                        
            public MyForm() {
                            m_val.setVal(
            false); //boxing
                        }

                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            
            using(BoolScope bs=new BoolScope(m_val, true))
                            
            {
                                ... 
            //執行計算任務
                            }

                        }

                        
            private BoolVal m_val;
                    }
            ;
                好了,一切都很不錯。盡管C#的對象模型給我們平添了不少麻煩,使得我多寫了不少代碼,但是使用域守衛類仍然是一本萬利的事情。作為GP fans,我當然也嘗試著在C#里做一些泛型,以免去反復開發包裝類和域守衛類的苦惱。這些東西,就留給大家做練習吧。:)
                在某些場合下,我們可能會對一些對象做一些操作,完事后在恢復這個對象的原始狀態,這也是域守衛類的用武之地。只是守衛一個結構復雜的類,不是一件輕松的 工作。最直接的做法是取出所有的成員數據,在結束后再重新復制回去。這當然是繁復的工作,而且效率不高。但是,我們將在下一篇看到,如果運用swap手 法,結合復制構造函數,可以很方便地實現這種域守衛。這我們以后再說。
                域守衛作為RAII的一個擴展應用,非常簡單,但卻極具實用性。如果我們對“資源”這個概念加以推廣,把一些值、狀態等等內容都納入資源的范疇,那么域守衛類的使用是順理成章的事。

            題外話:C#的對象模型

                C#的設計理念是簡化語言的學習和使用。但是,就前面案例中出現的問題而言,在特定的情況下,特別是需要靈活和擴展的時候,C#往往表現的差強人意。C# 的對象模型實際上是以堆對象和引用語義為核心的。不過,考慮到維持堆對象的巨大開銷和性能損失,應用在一些簡單的類型上,比如int、float等等,實 在得不嘗失。為此,C#將這些簡單類型直接作為值處理,當然也允許用戶定義自己的值類型。值類型擁有值語義。而值類型的本質是棧對象,引用類型則是堆對 象。
                這樣看起來應該是個不錯的折中,但是實際上卻造成了不大不小的麻煩。前面的案例已經明確地表現了這種對象模型引發的麻煩。由于C#拋棄值和引用的差異(為 了簡化語言的學習和使用),那么對于一個引用對象,我們無法用值語義訪問它;而對于一個值對象,我們無法用引用語義訪問。對于前者,不會引發本質性的問 題,因為我們可以使用成員函數來實現值語義。但是對于后者,則是無法逾越的障礙,就像在BoolScope案例中表現的那樣。在這種情況下,我們不得不用 引用類包裝值類型,使得值類型喪失了原有的性能和資源優勢。
                更有甚者,C#的對象模型有時會造成語義上的沖突。由于值類型使用值語義,而引用類型使用引用語義。那么同樣是對象定義,便有可能使用不同的語義:
                    int i, j=10;  //值類型
                    i=j;            //值語義,兩個對象復制內容
                    i=5;           //i==5, j==10
                    StringBuilder s1, s2 = new StringBuilder("s2");   //引用類型
                    s1 = s2;        //引用語義,s1和s2指向同一個對象
                    s1.Append(" is s1");    //s1==s2=="s1 is s2"
                同一個形式具有不同語義,往往會造成意想不到的問題。比如,在軟件開發的最初時刻,我們認為某個類型是值類型就足夠了,還可以獲得性能上的好處。但是,隨 著項目進入后期階段,發現最初的設計有問題,值類型限制了該類型的某些特性(如不能擁有析構函數,不能引用等等),那么需要把它改成引用類型。于是便引發 一大堆麻煩,需要檢查所有使用該類型的代碼,然后把賦值操作改成復制操作。這肯定不是討人喜歡的工作。為此,在實際開發中,很少自定義值類型,以免將來自縛手腳。于是,值類型除了語言內置類型和.net庫預定義的類型外,成了一件擺設。
                相比之下,傳統語言,如Ada、C、C++、Pascal等,區分引用和值的做法盡管需要初學者花更多的精力理解其中的差別,但在使用中則更加妥善和安全。畢竟學習是暫時的,使用則是永遠的。
            posted on 2008-02-16 08:19 longshanks 閱讀(2054) 評論(2)  編輯 收藏 引用

            Feedback

            # re: C++的營養 2008-02-16 11:47 abettor
            用對照的方法學習確實是一個很不錯的方法。  回復  更多評論
              

            # re: C++的營養 2008-02-22 16:45 i
            file pfile=new file("my.txt");
            應該改為
            file *pfile=new file("my.txt");  回復  更多評論
              

            国产精品久久久久久| 91精品日韩人妻无码久久不卡| 婷婷久久综合| 久久久久久久波多野结衣高潮| 久久精品无码午夜福利理论片| 久久香蕉综合色一综合色88| 亚洲Av无码国产情品久久| 色诱久久久久综合网ywww| 国产精品免费久久| 77777亚洲午夜久久多喷| 91久久精品国产91性色也| 2021国产精品午夜久久| 九九久久99综合一区二区| 久久亚洲日韩看片无码| 久久精品免费观看| 亚洲精品高清国产一线久久| 久久久久亚洲AV无码专区网站| 久久久一本精品99久久精品88| 中文字幕无码久久精品青草| 久久福利青草精品资源站免费| 亚洲乱码中文字幕久久孕妇黑人| 久久久久人妻精品一区| 亚洲人成无码久久电影网站| 亚洲国产精品久久久久网站 | 久久99精品久久久久婷婷| 久久国产三级无码一区二区| 国产aⅴ激情无码久久| 亚洲精品乱码久久久久久蜜桃| 久久er热视频在这里精品| 久久A级毛片免费观看| 久久综合久久自在自线精品自| 亚洲精品国产第一综合99久久| 久久成人国产精品一区二区| 国产91久久综合| 久久精品一区二区| 99久久久国产精品免费无卡顿| 一本色道久久99一综合| 久久久久久曰本AV免费免费| 久久久精品人妻一区二区三区蜜桃| 久久国产热这里只有精品| 久久久这里有精品中文字幕|