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

C++字符串完全指引之一 —— Win32 字符編碼


原著:Michael Dunn

翻譯:Chengjie Sun



原文出處:CodeProject:The Complete Guide to C++ Strings, Part I

引言

  毫無疑問,我們都看到過像 TCHAR, std::string, BSTR 等各種各樣的字符串類型,還有那些以 _tcs 開頭的奇怪的宏。你也許正在盯著顯示器發愁。本指引將總結引進各種字符類型的目的,展示一些簡單的用法,并告訴您在必要時,如何實現各種字符串類型之間的轉換。
  在第一部分,我們將介紹3種字符編碼類型。了解各種編碼模式的工作方式是很重要的事情。即使你已經知道一個字符串是一個字符數組,你也應該閱讀本部分。一旦你了解了這些,你將對各種字符串類型之間的關系有一個清楚地了解。
  在第二部分,我們將單獨講述string類,怎樣使用它及實現他們相互之間的轉換。

字符基礎 -- ASCII, DBCS, Unicode

  所有的 string 類都是以C-style字符串為基礎的。C-style 字符串是字符數組。所以我們先介紹字符類型。這里有3種編碼模式對應3種字符類型。第一種編碼類型是單子節字符集(single-byte character set or SBCS)。在這種編碼模式下,所有的字符都只用一個字節表示。ASCII是SBCS。一個字節表示的0用來標志SBCS字符串的結束。
  第二種編碼模式是多字節字符集(multi-byte character set or MBCS)。一個MBCS編碼包含一些一個字節長的字符,而另一些字符大于一個字節的長度。用在Windows里的MBCS包含兩種字符類型,單字節字符(single-byte characters)和雙字節字符(double-byte characters)。由于Windows里使用的多字節字符絕大部分是兩個字節長,所以MBCS常被用DBCS代替。
  在DBCS編碼模式中,一些特定的值被保留用來表明他們是雙字節字符的一部分。例如,在Shift-JIS編碼中(一個常用的日文編碼模式),0x81-0x9f之間和 0xe0-oxfc之間的值表示"這是一個雙字節字符,下一個子節是這個字符的一部分。"這樣的值被稱作"leading bytes",他們都大于0x7f。跟隨在一個leading byte子節后面的字節被稱作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一樣,DBCS字符串的結束標志也是一個單字節表示的0。
  第三種編碼模式是Unicode。Unicode是一種所有的字符都使用兩個字節編碼的編碼模式。Unicode字符有時也被稱作寬字符,因為它比單子節字符寬(使用了更多的存儲空間)。注意,Unicode不能被看作MBCS。MBCS的獨特之處在于它的字符使用不同長度的字節編碼。Unicode字符串使用兩個字節表示的0作為它的結束標志。
  單字節字符包含拉丁文字母表,accented characters及ASCII標準和DOS操作系統定義的圖形字符。雙字節字符被用來表示東亞及中東的語言。Unicode被用在COM及Windows NT操作系統內部。
  你一定已經很熟悉單字節字符。當你使用char時,你處理的是單字節字符。雙字節字符也用char類型來進行操作(這是我們將會看到的關于雙子節字符的很多奇怪的地方之一)。Unicode字符用wchar_t來表示。Unicode字符和字符串常量用前綴L來表示。例如:

wchar_t wch = L''1''; // 2 bytes, 0x0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters

字符在內存中是怎樣存儲的

  單字節字符串:每個字符占一個字節按順序依次存儲,最后以單字節表示的0結束。例如。"Bob"的存貯形式如下:

42 6F 62 00
B o b BOS

Unicode的存儲形式,L"Bob"

42 00 6F 00 62 00 00 00
B o b BOS

使用兩個字節表示的0來做結束標志。

  一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我們一會兒將看到 DBCS 字符串的微妙之處,它使得使用字符串操作函數和永字符指針遍歷一個字符串時會產生預料之外的結果。字符串" " ("nihongo")在內存中的存儲形式如下(LB和TB分別用來表示 leading byte 和 trail byte)

93 FA 96 7B 8C EA 00
LB TB LB TB LB TB EOS
EOS

值得注意的是,"ni"的值不能被解釋成WORD型值0xfa93,而應該看作兩個值93和fa以這種順序被作為"ni"的編碼。

使用字符串處理函數

  我們都已經見過C語言中的字符串函數,strcpy(), sprintf(), atoll()等。這些字符串只應該用來處理單字節字符字符串。標準庫也提供了僅適用于Unicode類型字符串的函數,比如wcscpy(), swprintf(), wtol()等。
  微軟還在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函數都有對應名字的DBCS版本_mbs***()。如果你料到可能會遇到DBCS字符串(如果你的軟件會被安裝在使用DBCS編碼的國家,如中國,日本等,你就可能會),你應該使用_mbs***()函數,因為他們也可以處理SBCS字符串。(一個DBCS字符串也可能含有單字節字符,這就是為什么_mbs***()函數也能處理SBCS字符串的原因)
  讓我們來看一個典型的字符串來闡明為什么需要不同版本的字符串處理函數。我們還是使用前面的Unicode字符串 L"Bob":

42 00 6F 00 62 00 00 00
B o b BOS

  因為x86CPU是little-endian,值0x0042在內存中的存儲形式是42 00。你能看出如果這個字符串被傳給strlen()函數會出現什么問題嗎?它將先看到第一個字節42,然后是00,而00是字符串結束的標志,于是strlen()將會返回1。如果把"Bob"傳給wcslen(),將會得出更壞的結果。wcslen()將會先看到0x6f42,然后是0x0062,然后一直讀到你的緩沖區的末尾,直到發現00 00結束標志或者引起了GPF。
  到目前為止,我們已經討論了str***()和wcs***()的用法及它們之間的區別。Str***()和_mbs**()之間的有區別區別呢?明白他們之間的區別,對于采用正確的方法來遍歷DBCS字符串是很重要的。下面,我們將先介紹字符串的遍歷,然后回到str***()與_mbs***()之間的區別這個問題上來。

正確的遍歷和索引字符串

  因為我們中大多數人都是用著SBCS字符串成長的,所以我們在遍歷字符串時,常常使用指針的++-和-操作。我們也使用數組下標的表示形式來操作字符串中的字符。這兩種方式是用于SBCS和Unicode字符串,因為它們中的字符有著相同的寬度,編譯器能正確的返回我們需要的字符。
  然而,當碰到DBCS字符串時,我們必須拋棄這些習慣。這里有使用指針遍歷DBCS字符串時的兩條規則。違背了這兩條規則,你的程序就會存在DBCS有關的bugs。

  • 1.在前向遍歷時,不要使用++操作,除非你每次都檢查lead byte;
  • 2.永遠不要使用-操作進行后向遍歷。
  •   我們先來闡述規則2,因為找到一個違背它的真實的實例代碼是很容易的。假設你有一個程序在你自己的目錄里保存了一個設置文件,你把安裝目錄保存在注冊表中。在運行時,你從注冊表中讀取安裝目錄,然后合成配置文件名,接著讀取該文件。假設,你的安裝目錄是C:\Program Files\MyCoolApp,那么你合成的文件名應該是C:\Program Files\MyCoolApp\config.bin。當你進行測試時,你發現程序運行正常。
      現在,想象你合成文件名的代碼可能是這樣的:

    bool GetConfigFileName ( char* pszName, size_t nBuffSize )
    {
        char szConfigFilename[MAX_PATH];
     
        // Read install dir from registry... we''ll assume it succeeds.
     
        // Add on a backslash if it wasn''t present in the registry value.
        // First, get a pointer to the terminating zero.
        char* pLastChar = strchr ( szConfigFilename, '''' );
     
        // Now move it back one character.
        pLastChar--;  
     
        if ( *pLastChar != ''\\'' )
            strcat ( szConfigFilename, "\\" );
     
        // Add on the name of the config file.
        strcat ( szConfigFilename, "config.bin" );
     
        // If the caller''s buffer is big enough, return the filename.
        if ( strlen ( szConfigFilename ) >= nBuffSize )
            return false;
        else
            {
            strcpy ( pszName, szConfigFilename );
            return true;
            }
    }      
      這是一段很健壯的代碼,然而在遇到 DBCS 字符時它將會出錯。讓我們來看看為什么。假設一個日本用戶使用了你的程序,把它安裝在 C:\。下面是這個名字在內存中的存儲形式:
     
    43 3A 5C 83 88 83 45 83 52 83 5C 00
          LB TB LB TB LB TB LB TB  
    C : \ EOS

      當使用 GetConfigFileName() 檢查尾部的''\\''時,它尋找安裝目錄名中最后的非0字節,看它是等于''\\''的,所以沒有重新增加一個''\\''。結果是代碼返回了錯誤的文件名。
      哪里出錯了呢?看看上面兩個被用藍色高量顯示的字節。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代碼錯誤的讀取了一個 trail byte,把它當作了一個字符。
      正確的后向遍歷方法是使用能夠識別DBCS字符的函數,使指針移動正確的字節數。下面是正確的代碼。(指針移動的地方用紅色標明)

    bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
    {
        char szConfigFilename[MAX_PATH];
     
        // Read install dir from registry... we''ll assume it succeeds.
     
        // Add on a backslash if it wasn''t present in the registry value.
        // First, get a pointer to the terminating zero.
        char* pLastChar = _mbschr ( szConfigFilename, '''' );
     
        // Now move it back one double-byte character.
        pLastChar = CharPrev ( szConfigFilename, pLastChar );
     
        if ( *pLastChar != ''\\'' )
            _mbscat ( szConfigFilename, "\\" );
     
        // Add on the name of the config file.
        _mbscat ( szConfigFilename, "config.bin" );
    
         // If the caller''s buffer is big enough, return the filename.
        if ( _mbslen ( szInstallDir ) >= nBuffSize )
            return false;
        else
            {
            _mbscpy ( pszName, szConfigFilename );
            return true;
            }
    }
    
      上面的函數使用CharPrev() API使pLastChar向后移動一個字符,這個字符可能是兩個字節長。在這個版本里,if條件正常工作,因為lead byte永遠不會等于0x5c。
      讓我們來想象一個違背規則1的場合。例如,你可能要檢測一個用戶輸入的文件名是否多次出現了'':''。如果,你使用++操作來遍歷字符串,而不是使用CharNext(),你可能會發出不正確的錯誤警告如果恰巧有一個trail byte它的值的等于'':''的值。
    與規則2相關的關于字符串索引的規則:
    2a. 永遠不要使用減法去得到一個字符串的索引。

    違背這條規則的代碼和違背規則2的代碼很相似。例如,

    char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

    這和向后移動一個指針是同樣的效果。

    回到關于str***()和_mbs***()的區別

      現在,我們應該很清楚為什么_mbs***()函數是必需的。Str***()函數根本不考慮DBCS字符,而_mbs***()考慮。如果,你調用strrchr("C:\\ ", ''\\''),返回結果可能是錯誤的,然而_mbsrchr()將會認出最后的雙字節字符,返回一個指向真的''\\''的指針。
      關于字符串函數的最后一點:str***()和_mbs***()函數認為字符串的長度都是以char來計算的。所以,如果一個字符串包含3個雙字節字符,_mbslen()將會返回6。Unicode函數返回的長度是按wchar_t來計算的。例如,wcslen(L"Bob")返回3。

    Win32 API中的MBCS和Unicode

    兩組 APIs:
      盡管你也許從來沒有注意過,Win32中的每個與字符串相關的API和message都有兩個版本。一個版本接受MBCS字符串,另一個接受Unicode字符串。例如,根本沒有SetWindowText()這個API,相反,有SetWindowTextA()和SetWindowTextW()。后綴A表明這是MBCS函數,后綴W表示這是Unicode版本的函數。
      當你 build 一個 Windows 程序,你可以選擇是用 MBCS 或者 Unicode APIs。如果,你曾經用過VC向導并且沒有改過預處理的設置,那表明你用的是MBCS版本。那么,既然沒有 SetWindowText() API,我們為什么可以使用它呢?winuser.h頭文件包含了一些宏,例如:

    BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
    BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
     
    #ifdef UNICODE
    #define SetWindowText  SetWindowTextW
    #else
    #define SetWindowText  SetWindowTextA
    #endif      
    當使用MBCS APIs來build程序時,UNICODE沒有被定義,所以預處理器看到:
    #define SetWindowText SetWindowTextA

      這個宏定義把所有對SetWindowText的調用都轉換成真正的API函數SetWindowTextA。(當然,你可以直接調用SetWindowTextA() 或者 SetWindowTextW(),雖然你不必那么做。)
      所以,如果你想把默認使用的API函數變成Unicode版的,你可以在預處理器設置中,把_MBCS從預定義的宏列表中刪除,然后添加UNICODE和_UNICODE。(你需要兩個都定義,因為不同的頭文件可能使用不同的宏。) 然而,如果你用char來定義你的字符串,你將會陷入一個尷尬的境地。考慮下面的代碼:

    HWND hwnd = GetSomeWindowHandle();
    char szNewText[] = "we love Bob!";
    SetWindowText ( hwnd, szNewText );

    在預處理器把SetWindowText用SetWindowTextW來替換后,代碼變成:

    HWND hwnd = GetSomeWindowHandle();
    char szNewText[] = "we love Bob!";
    SetWindowTextW ( hwnd, szNewText );

      看到問題了嗎?我們把單字節字符串傳給了一個以Unicode字符串做參數的函數。解決這個問題的第一個方案是使用 #ifdef 來包含字符串變量的定義:

    HWND hwnd = GetSomeWindowHandle();
    #ifdef UNICODE
    wchar_t szNewText[] = L"we love Bob!";
    #else
    char szNewText[] = "we love Bob!";
    #endif
    SetWindowText ( hwnd, szNewText );

    你可能已經感受到了這樣做將會使你多么的頭疼。完美的解決方案是使用TCHAR.

    使用TCHAR

      TCHAR是一種字符串類型,它讓你在以MBCS和UNNICODE來build程序時可以使用同樣的代碼,不需要使用繁瑣的宏定義來包含你的代碼。TCHAR的定義如下:

    #ifdef UNICODE
    typedef wchar_t TCHAR;
    #else
    typedef char TCHAR;
    #endif

    所以用MBCS來build時,TCHAR是char,使用UNICODE時,TCHAR是wchar_t。還有一個宏來處理定義Unicode字符串常量時所需的L前綴。

    #ifdef UNICODE
    #define _T(x) L##x
    #else
    #define _T(x) x
    #endif

      ##是一個預處理操作符,它可以把兩個參數連在一起。如果你的代碼中需要字符串常量,在它前面加上_T宏。如果你使用Unicode來build,它會在字符串常量前加上L前綴。

    TCHAR szNewText[] = _T("we love Bob!");

      像是用宏來隱藏SetWindowTextA/W的細節一樣,還有很多可以供你使用的宏來實現str***()和_mbs***()等字符串函數。例如,你可以使用_tcsrchr宏來替換strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根據你預定義的宏是_MBCS還是UNICODE來擴展成正確的函數,就像SetWindowText所作的一樣。
      不僅str***()函數有TCHAR宏。其他的函數如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."標題下有完整的宏列表。

    字符串和TCHAR typedefs

      由于Win32 API文檔的函數列表使用函數的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR來定義的。(除了XP中引入的只適用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他們。

    type Meaning in MBCS builds Meaning in Unicode builds
    WCHAR wchar_t wchar_t
    LPSTR zero-terminated string of char (char*) zero-terminated string of char (char*)
    LPCSTR constant zero-terminated string of char (const char*) constant zero-terminated string of char (const char*)
    LPWSTR zero-terminated Unicode string (wchar_t*) zero-terminated Unicode string (wchar_t*)
    LPCWSTR constant zero-terminated Unicode string (const wchar_t*) constant zero-terminated Unicode string (const wchar_t*)
    TCHAR char wchar_t
    LPTSTR zero-terminated string of TCHAR (TCHAR*) zero-terminated string of TCHAR (TCHAR*)
    LPCTSTR constant zero-terminated string of TCHAR (const TCHAR*) constant zero-terminated string of TCHAR (const TCHAR*)

    何時使用 TCHAR 和 Unicode

      到現在,你可能會問,我們為什么要使用Unicode。我已經用了很多年的char。下列3種情況下,使用Unicode將會使你受益:

  • 1.你的程序只運行在Windows NT系統中。
  • 2. 你的程序需要處理超過MAX_PATH個字符長的文件名。
  • 3. 你的程序需要使用XP中引入的只有Unicode版本的API.
  •   Windows 9x 中大多數的 API 沒有實現 Unicode 版本。所以,如果你的程序要在windows 9x中運行,你必須使用MBCS APIs。然而,由于NT系統內部都使用Unicode,所以使用Unicode APIs將會加快你的程序的運行速度。每次,你傳遞一個字符串調用MBCS API,操作系統會把這個字符串轉換成Unicode字符串,然后調用對應的Unicode API。如果一個字符串被返回,操作系統還要把它轉變回去。盡管這個轉換過程被高度優化了,但它對速度造成的損失是無法避免的。
      只要你使用Unicode API,NT系統允許使用非常長的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一個優點是你的程序會自動處理用戶輸入的各種語言。所以一個用戶可以輸入英文,中文或者日文,而你不需要額外編寫代碼去處理它們。
      最后,隨著windows 9x產品的淡出,微軟似乎正在拋棄MBCS APIs。例如,包含兩個字符串參數的SetWindowTheme() API只有Unicode版本的。使用Unicode來build你的程序將會簡化字符串的處理,你不必在MBCS和Unicdoe之間相互轉換。
      即使你現在不使用Unicode來build你的程序,你也應該使用TCHAR及其相關的宏。這樣做不僅可以的代碼可以很好地處理DBCS,而且如果將來你想用Unicode來build你的程序,你只需要改變一下預處理器中的設置就可以實現了。

    作者簡介
      Michael Dunn:居住在陽光城市洛杉磯。他是如此的喜歡這里的天氣以致于想一生都住在這里。他在4年級時開始編程,那時用的電腦是Apple //e。1995年,在 UCLA 獲得數學學士學位,隨后在Symantec 公司做 QA 工程師,在 Norton AntiVirus 組工作。他自學了 Windows 和 MFC 編程。1999-2000年,他設計并實現了 Norton AntiVirus 的新界面。 
      Michael 現在在 Napster(一個提供在線訂閱音樂服務的公司)做開發工作,他還開發了UltraBar,一個IE工具欄插件,它可以使網絡搜索更加容易,給了 googlebar 以沉重打擊;他還開發了 CodeProject SearchBar;與人共同創建了 Zabersoft 公司,該公司在洛杉磯和丹麥的 Odense 都設有辦事處。
      他喜歡玩游戲。愛玩的游戲有 pinball, bike riding,偶爾還玩 PS, Dreamcasth 和 MAME 游戲。他因忘了自己曾經學過的語言:法語、漢語、日語而感到悲哀。

    相關文章
    Posted on 2005-10-21 14:27 艾凡赫 閱讀(700) 評論(0)  編輯 收藏 引用 所屬分類: 數據類型
    青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美色网一区二区| 一本久久知道综合久久| 亚洲大片在线| 国产精品婷婷午夜在线观看| 久久综合久久美利坚合众国| 欧美一级午夜免费电影| 亚洲淫性视频| 中文精品视频| 在线亚洲自拍| 亚洲欧美日韩爽爽影院| 亚洲午夜精品一区二区| 亚洲视频一区二区免费在线观看| 亚洲精品欧美激情| 欧美成人在线免费观看| 亚洲一区二区在线播放| 99精品久久| 国产精品99久久久久久www| 在线免费日韩片| 亚洲黄色小视频| 日韩午夜免费视频| 国产精品99久久久久久www| 午夜精品福利一区二区蜜股av| 亚洲欧美在线免费观看| 欧美亚洲综合另类| 久久全球大尺度高清视频| 在线精品视频一区二区| 欧美福利在线观看| 欧美激情网友自拍| 亚洲精品在线视频观看| 一区二区三区欧美激情| 亚洲制服av| 久久久久久久久久久久久久一区| 免费欧美在线视频| 国产精品日韩欧美一区二区| 欧美日韩国产精品专区| 国产一区二区三区最好精华液| 亚洲第一在线综合网站| 亚洲一区二区三区乱码aⅴ蜜桃女| 久久国产精品久久久久久| 免费亚洲视频| 日韩一区二区福利| 先锋影音一区二区三区| 亚洲淫性视频| 欧美精品成人91久久久久久久| 国产精品国内视频| 国产精品美女久久久久久2018| 国模精品一区二区三区| 一区免费观看| 亚洲免费一在线| 欧美黄色一区| 亚洲欧美成人一区二区三区| 欧美一区二区三区四区高清| 老司机成人网| 久久久精品999| 欧美激情视频在线播放| 国产综合一区二区| 亚洲毛片视频| 欧美亚洲免费电影| 久久久久九九视频| 亚洲二区在线观看| 亚洲欧美在线看| 欧美精品18| 国产真实乱偷精品视频免| 亚洲日本电影在线| 久久精品国产77777蜜臀| 亚洲精品国产欧美| 亚洲影音先锋| 久久精品亚洲一区| 国产一区二区福利| 亚洲欧美日韩国产中文在线| 亚洲精品一区二区三区99| 一本到高清视频免费精品| 久久精品久久综合| 国产自产v一区二区三区c| 欧美伊人久久久久久久久影院| 亚洲国产精品精华液2区45| 久久视频在线免费观看| 激情一区二区三区| 久久成人精品| 欧美一区二区黄| 国产亚洲精品久久久久婷婷瑜伽| 亚洲一区黄色| 亚洲精品国产精品国产自| 久久在线免费观看| 亚洲国产日韩欧美在线99| 久久人人九九| 亚洲成人资源网| 另类酷文…触手系列精品集v1小说| 亚洲欧美日韩国产一区| 国产精品视频xxx| 欧美一二区视频| 午夜一区二区三区不卡视频| 亚洲国产精品久久久久婷婷老年| 亚洲尤物精选| 一区二区三区.www| 久久另类ts人妖一区二区| 在线欧美日韩国产| 欧美影院一区| 性欧美超级视频| 国产精品久久久久久久久动漫| 99视频有精品| 香蕉免费一区二区三区在线观看| 99综合在线| 国产精品资源| 久久aⅴ国产欧美74aaa| 亚洲欧美精品| 精品不卡一区| 久久亚洲不卡| 久久婷婷国产综合国色天香| 亚洲国产高清一区| 亚洲人成在线影院| 久久精品视频网| 一区视频在线播放| 久久久国产一区二区三区| 久久久久国产精品人| 日韩天堂在线观看| 在线一区欧美| 黑人巨大精品欧美一区二区| 亚洲日本一区二区三区| 欧美国产日本在线| 亚洲视频在线一区观看| 亚洲精品乱码久久久久久久久| 国产精品视频福利| 欧美福利视频一区| 国产女主播一区二区三区| 欧美电影资源| 国产伦精品一区| 91久久精品国产91久久性色tv| 国产精品v亚洲精品v日韩精品 | 亚洲素人在线| 性欧美大战久久久久久久久| 亚洲国产成人在线播放| 亚洲一区二区三区涩| 亚洲欧洲精品一区二区三区不卡| 亚洲小说欧美另类社区| 亚洲欧洲在线看| 久久国产精品久久久久久电车| 一本不卡影院| 蜜臀va亚洲va欧美va天堂 | 亚洲最新在线视频| 一区二区日韩| 亚洲高清色综合| 午夜老司机精品| 亚洲色无码播放| 免费欧美日韩| 久久中文欧美| 国产模特精品视频久久久久| 91久久黄色| 亚洲精品免费一二三区| 久久亚洲春色中文字幕| 欧美一区二区精美| 国产精品豆花视频| 亚洲免费观看高清在线观看| 91久久夜色精品国产九色| 久久精品中文字幕一区二区三区| 校园春色国产精品| 欧美va亚洲va香蕉在线| 欧美aⅴ99久久黑人专区| 国产精品美女久久久浪潮软件| 久久精品99国产精品日本| 国产精品亚洲一区二区三区在线| 久久久国产一区二区三区| 国产亚洲毛片在线| 久久岛国电影| 久久香蕉国产线看观看av| 激情欧美国产欧美| 久久―日本道色综合久久| 免费成人在线视频网站| 激情六月婷婷综合| 免费不卡在线视频| 亚洲欧洲在线播放| 一区二区精品| 欧美系列亚洲系列| 一区二区三区四区五区视频| 亚洲图片你懂的| 欧美福利一区| 亚洲伦理精品| 国产欧美一区视频| 欧美激情一区在线观看| 亚洲欧美国产日韩天堂区| 欧美激情一区三区| 久久精品视频va| 一区二区欧美日韩| 影音先锋亚洲视频| 国产精品人人做人人爽| 老司机精品福利视频| 亚洲欧美在线播放| 99综合精品| 亚洲高清免费视频| 久久久久久一区二区三区| 亚洲视频在线观看免费| 亚洲国产专区校园欧美| 国产日韩欧美精品综合| 欧美日韩卡一卡二| 欧美电影在线观看| 久久免费精品视频| 久久精品夜夜夜夜久久| 欧美有码视频| 亚洲欧美日韩一区在线观看| 日韩亚洲欧美一区二区三区|