天天看點

寬字元與窄字元的處理

1.      《windows核心程式設計》

>Unicode是傳遞字元串的最佳手段。

2.      C标準庫中與wchar_t相關的檔案:

《windows核心程式設計》

1)      檔案”string.h”定義了wchar_t資料類型:

typedef unsignedshort wchar_t;
           

2)      标準的ANSI C函數和它們等價的UNICODE函數:

>所有的U n i c o d e 函數均以w c s 開頭,w c s 是寬字元串的英文縮寫。若要調用Un i c o d e函數,隻需用字首wc s 來取代AN S I 字元串函數的字首st r 即可。

>若要建立雙重功能,必須包含T C h a r. h 檔案,而不是包含S t r i ng . h 檔案。

“雙重功能”,即為定義的宏,在UNICODE下為哪種使用,在ANSI下又為哪種定義。

3)      檔案”tchar.h”定義了TCHAR類型

> T C h a r. h 檔案的唯一作用是幫助建立A N S I / U n i c o d e 通用源代碼檔案。它包含你應該用在源代碼中的一組宏,而不應該直接調用st r 函數或者wc s 函數。如果在編譯源代碼檔案時定義了UN I C O D E ,這些宏就會引用wc s 這組函數。如果沒有定義_U N I C O D E ,那麼這些宏将引用st r這組宏。

>例如,在T C h a r. h 中有一個宏稱為_ t c s cp y 。如果在包含該頭檔案時沒有定義_U N I C O D E ,那麼_t c s c p y 就會擴充為AN S I 的st r c p y 函數。但是如果定義了_UNICODE,_tcscpy 将擴充為Un i c o d e的wc s c p y 函數。擁有字元串參數的所有C運作期函數都在TC h a r. h 檔案中定義了一個通用宏。如果使用通用宏,而不是AN S I / U n i c o d e 的特定函數名,就能夠順利地建立可以為A N S I 或U n i c od e進行編譯的源代碼。

4)      _UNICODE和UNICODE

>_ U N I C O D E 宏用于C 運作期頭檔案,而U N I C OD E 宏則用于Win d o w s 頭檔案。當編譯源代碼子產品時,通常必須同時定義這兩個宏。

3.      windows中與wchar_t相關的檔案

《windows核心程式設計》

1)      定義了UNICODE資料類型的檔案:

i.                    檔案”wtypes.h”定義了windows所有應用的資料類型,包括字元串類型。

#TODO(continue): 既然是定義了所有應用的資料類型,那麼應該是不用手動添加的。待測。。

ii.                  檔案”winnt.h”檔案

winnt.h檔案編譯問題,解決方案:

A.     http://hagejid.blog.51cto.com/141754/294797

調準包含目錄的次序

B.     http://sz3mhx.573114.com/Blog/Html/8E16/118149.html

在include <winnt.h>前include <windows.h>

2)      windows中的UNICODE函數和ANSI函數

i.                    windows中的函數名都是大寫開頭的,windows中的函數類型都是大寫開頭的,比如整型為UINT, INT, LONG。

ii.                  windows中許多函數都是”相容”UNICODE和ANSI字元串。比如CreateWindowEx,它實際上有兩種形式:CreateWindowExA和CreateWindowExW。A字尾的表示其中的字元串參數采用的ANSI型字元串,W字尾的表示其中的字元串參數采用的是寬字元型字元串。

iii.                 作業系統字元串函數名形如:StrCat,StrCpy, StrCmp等。與C運作期字元串函數相似。使用這些函數須加上”shlwapi.h”頭檔案。

>若要使用這些函數,必須加上S h l WA p i . h 頭檔案。另外,如前所述,這些字元串函數既有AN S I 版本,也有Un i c o d e 版本,例如St r C a t A 和St r C a t W 。

iv.                 windows另外提供了一組對unicode和ANSI字元串的操作函數:(這些函數是作為宏實作的,如lstrcat是lstrcatA和lstrcatW宏定義)。使用這些函數須加上”winbase.h”頭檔案。

lstrcat, lstrcpy, lstrlen,

lstrcmp(對兩個字元串進行區分大小寫的比較),

lstrcmpi(對兩個字元串進行不區分大小寫的比較)

v.                  轉換大小寫的函數:使用這些函數須加上”winuser.h”頭檔案。

PTSTR CharLower(PTSTRpszString);
PTSTR CharUpper(PTSTRpszString);
           

>其他的C 運作期函數,如i s a l ph a 、is l o w e r 和is u p p e r ,傳回一個值,指明某個字元是字母字元、小寫字母還是大寫字母。WindowsAPI 提供了一些函數,也能傳回這些資訊:。使用這些函數須加上”winuser.h”頭檔案。

BOOL IsCharAlpha(TCHARch);
BOOLIsCharAlphaNumerica(TCHAR ch);
BOOL IsCharUpper(TCHARch);
BOOL IsCharLower(TCHARch);
           

vi.                 printf函數:sprintf(C标準庫提供的函數)和swprintf(作業系統提供的函數即windows函數)

A.    sprintf的使用:在頭檔案”stdio.h”中

char szA[100];
sprintf(szA, “%s”,“asdf”);
sprintf(szA, ”%S”, L”ASDF”);// 在将寬字元轉換為窄字元的時候s須大寫
           

B.    swprintf的使用:在頭檔案”stdio.h”或”wchar.h”中

wchar_t szW[100];
swprintf(szW, L”%s”,L”SADF”);
swprintf(szW, L”%S”, “SADF”);// 在将窄字元轉換為寬字元的時候s須大寫
           

vii.               sprintf與sprintf_s,sprint_s_l,swprintf與swprintf_s,swprint_s_l的差別:

http://technet.microsoft.com/zh-cn/ce3zzk1k(de-de).aspx

>    sprintf_s 函數在 buffer設定格式并存儲一系列字元和值。 每 argument (如果有) 基于在 format相應的格式規範轉換和輸出。 該格式包括普通字元并具有窗體和功能和printf的 format 參數相同。 null 字元從右向左書寫的最後一個字元之後追加。如果複制出現在重疊的字元串之間,該行為不确定。

sprintf_s 和 sprintf 之間的主要差異是 sprintf_s 檢查格式字元串的格式無效字元,sprintf ,而隻檢查格式字元串或緩沖區是否 NULL 指針。如果任何檢查失敗,無效參數調用處理程式,如 參數驗證所述。如果執行允許繼續,該函數傳回 -1 并将 errno 到 EINVAL。

sprintf_s 和 sprintf 之間的另一個主要差別在于 sprintf_s 帶有指定輸出區域的大小長度參數在字元。 如果緩沖區為是以列印的文本太小緩沖區設定為空字元串,而無效參數調用處理程式。不同 snprintf, sprintf_s 確定緩沖區将 null 終止 (除非緩沖區大小為零)。

swprintf_s 是 sprintf_s的寬字元版本;為 swprintf_s 的指針參數是寬字元字元串。 編碼錯誤的檢測到 swprintf_s 的可能與在 sprintf_s。這些功能的版本與 _l 字尾的相同,隻不過它們使用區域設定參數而不是目前線程區域設定。

在 C++ 中,使用這些功能由模闆重載簡化;重載可推斷緩沖區長度 (自動不再需要指定範圍參數),并且還可以用以較新,安全重複自動替換舊,不安全的功能。有關更多資訊,請參見 安全模闆重載。

具有提供對 sprintf_s 的版本時所發生的其他控件,如果緩沖區太小。 有關更多資訊,請參見_snprintf_s,_snprintf_s_l,_snwprintf_s,_snwprintf_s_l。

3)      UNICODE與ANSI字元串之間的轉換

i.                    windows函數:

A.       int MultiByteToWideChar

int MultiByteToWideChar(
    UINT CodePage,          //code page
    DWORD dwFlags,          //character-type options
    LPCSTR lpMultiByteStr,  //address of string to map
    int cchMultiByte,       //number of bytes in string
    LPWSTR lpWideCharStr,   //address of wide-character buffer
    int cchWideChar         //size of buffer
);
           

> u C o d e P a ge 參數用于辨別一個與多位元組字元串相關的代碼頁号。d w F l a g s 參數用于設定另一個控件,它可以用重音符号之類的區分标記來影響字元。這些标志通常并不使用,在d w F l a g s參數中傳遞0 。p M u l t i B y t e S t r 參數用于設定要轉換的字元串,c c h M u l t i B y t e 參數用于指明該字元串的長度(按位元組計算)。如果為c c h M u l t i B y t e 參數傳遞- 1 ,那麼該函數用于确定源字元串的長度。

轉換後産生的U n i c o d e 版本字元串将被寫入記憶體中的緩存,其位址由p Wi d e C h a r S t r 參數指定。必須在c c h Wi d e C h a r 參數中設定該緩存的最大值(以字元為計量機關)。如果調用M u l t i B y t e To Wi d e C h a r ,給c c h Wi d e C h a r 參數傳遞0 ,那麼該參數将不執行字元串的轉換,而是傳回為使轉換取得成功所需要的緩存的值。

n  用例:

MultiByteToWideChar(CP_ACP, 0, pMulti8yteStr, -1, 
      pWideCharStr, nLenOfWideCharStr);
           

B.       int WideCharToMultiByte

int WideCharToMultiByte(
  UINT CodePage,         // code page
  DWORD dwFlags,         // performance and mapping flags
  LPCWSTR lpWideCharStr, // address of wide-character string
  int cchWideChar,       // number of characters in string
  LPSTR lpMultiByteStr,  // address of buffer for new string
  int cchMultiByte,      // size of buffer
  LPCSTR lpDefaultChar,  // address of default for unmappable 
                         // characters
  LPBOOL lpUsedDefaultChar   // address of flag set when default 
                             // char. used
);
           

>該函數與M u l t i B i t e To Wi d e C h a r 函數相似。同樣,u C o d e P a g e 參數用于辨別與新轉換的字元串相關的代碼頁。d w F l a g s 則設定用于轉換的其他控件。這些标志能夠作用于帶有區分符号的字元和系統不能轉換的字元。通常不需要為字元串的轉換而擁有這種程度的控制手段,你将為d w F l a g s 參數傳遞0 。

p Wi d e C h a r S tr 參數用于設定要轉換的字元串的記憶體位址,c c h Wi d e C h a r 參數用于指明該字元串的長度(用字元數來計量)。如果你為c c h Wi d e C h a r 參數傳遞- 1 ,那麼該函數用于确定源字元串的長度。

轉換産生的多位元組版本的字元串被寫入由p M u l t i B y t e S t r 參數指明的緩存。必須在c c h M u l t i B y t e參數中設定該緩存的最大值(用位元組來計量)。如果傳遞0 作為Wi d e C h a r To M ul t i B y t e 函數的c c h M u l t i B y te 參數,那麼該函數将傳回目标緩存需要的大小值。通常可以使用将多位元組字元串轉換成寬位元組字元串時介紹的一系列類似的事件,将寬位元組字元串轉換成多位元組字元串。

你會發現,Wi d e C h a r To M u l t i B y t e 函數接受的參數比M u l t i B y t e To Wi d e C h a r 函數要多2 個,即p D e f a u l t C h ar 和p f U s e d D e f a u l t C h a r 。隻有當Wi d e C h a r To M u l t i B y t e 函數遇到一個寬位元組字元,而該字元在u C o d e P a g e 參數辨別的代碼頁中并沒有它的表示法時,Wi d e C h a r To M u l t i B y t e 函數才使用這兩個參數。如果寬位元組字元不能被轉換,該函數便使用p D e f a u l t C h a r 參數指向的字元。如果該參數是N U L L (這是大多數情況下的參數值),那麼該函數使用系統的預設字元。該預設字元通常是個問号。這對于檔案名來說是危險的,因為問号是個通配符。

p f U s e d D e f a ul t C h a r 參數指向一個布爾變量,如果寬字元串中至少有一個字元不能轉換成等價多位元組字元,那麼函數就将該變量置為T R U E 。如果所有字元均被成功地轉換,那麼該函數就将該變量置為FA L S E 。當函數傳回以便檢查寬位元組字元串是否被成功地轉換後,可以測試該變量。同樣,通常為該測試傳遞N U L L 。

n  用例:

WideCharToMultiByte(CP_ACP, 0, pWideCharStr, -1, 
         pMultiByteStr, strlen(pMultiByteStr), NULL, NULL);
           

#Q: strlen(pMultiByteStr)能取到此數組的大小嗎?

4.      先從UNICODE和ANSI的基礎開始講起

1)      一個ANSI字元占一個位元組共8位,一個UNICODE字元占兩個位元組共16位;ANSI字元串以’\0’結束,0x00。

#Q: UNICODE字元串以什麼結束??

#A: UNICODE字元串以L”\0”結束,0x0000。

5.      UNICODE和ANSI字元的相關定義及應用在各種運作庫中的展現如下:

1)      在C标準庫中

i.                    UNICODE在C标準庫下編譯的宏定義為_UNICODE

ii.                  寬字元的資料類型為wchar_t,窄字元的資料類型為char。資料類型的定義在頭檔案string.h中,對wchar_t的定義為:

typedef unsigned short wchar_t;
           

iii.                 在頭檔案string.h中,定義了分别對寬字元和窄字元的操作函數,如wcscpy和strcpy等。對寬字元的操作函數和對窄字元的操作函數的對照見:

http://blog.csdn.net/typecool/article/details/5877458

寬字元與窄字元的處理

iv.                 而在頭檔案tchar.h中,定義了tchar宏,該檔案子產品功能實作了”雙重功能”。

即例如_tcscpy方法宏,當編譯環境定義了UNICODE宏,則此方法宏為wcscpy,不然則為strcpy。等等。。。。

v.                  #Q: 對于對寬字元和窄字元的處理函數帶字尾_s,如wcscpy_s,strcpy_s,此類方法與wcscpy和strcpy等的差別在哪裡?

#A: http://technet.microsoft.com/zh-cn/subscriptions/td1esda9(v=vs.80).aspx

wcscpy_s方法定義如下:

errno_t wcscpy_s(
  wchar_t *strDestination,
  size_t sizeInWords,
  const wchar_t *strSource
);
           

wcscpy方法定義如下:

wchar_t* wcscpy_s(
  wchar_t *strDestination,
  const wchar_t *strSource
);
           

wcscpy_s方法多了一項參數size_tsizeInWords,即要拷貝的字元的個數。這樣就可避免strDestination空間配置設定不足,如此使用更安全!

2)      在windows中

i.                    UNICODE在windows下編譯的宏定義為UNICODE

ii.                  windows下定義的資料類型基本上都采用的是全大寫的,比如整型INT,無符号整型UINT,wchar_t定義為WCHAR,char定義為CHAR。

iii.                 #Q: 頭檔案wtypes.h windef.h winnt.h這三個檔案該怎麼用???有好多類型都定義串了,這三個檔案都是定義在同一級目錄下面。

#A: http://baike.baidu.com/view/1586331.htm

>WINDEF.H 基本資料類型定義。

WINNT.H 支援Unicode的類型定義。

WINBASE.H Kernel(核心)函數。

WINUSER.H 使用者界面函數。

WINGDI.H 圖形裝置接口函數。

>這些頭檔案定義了Windows的所有資料型态、函數調用、資料結構和常數識别字,它們是Windows檔案中的一個重要部分。

#Q: 頭檔案windows.h的用法?

#A: http://baike.baidu.com/view/1586331.htm

>WINDOWS.H是一個最重要的頭檔案,它包含了其他Windows頭檔案,這些頭檔案的某些也包含了其他頭檔案。

#Q: 頭檔案windows.h可否直接加載???

#A: 可以。

是以,使用windows最簡單最友善的辦法就是直接加載頭檔案windows.h即可:#include <Windows.h>

MFC/Windows基本資料類型

http://blog.programfan.com/article.asp?id=33809

iv.                 windows中處理UNICODE和ANSI的函數有

A.     定義的宏函數,與C标準庫形似的有:

StrCat,StrCpy等等。

a)      在頭檔案shlwapi.h中如下定義:

#ifdef UNICODE
#define StrCat                  StrCatW
#define StrCmp                  StrCmpW
#define StrCmpI                 StrCmpIW
#define StrCpy                  StrCpyW
#define StrCpyN                 StrCpyNW
#define StrCatBuff              StrCatBuffW
#else
#define StrCat                  lstrcatA
#define StrCmp                  lstrcmpA
#define StrCmpI                 lstrcmpiA
#define StrCpy                  lstrcpyA
#define StrCpyN                 lstrcpynA
#define StrCatBuff              StrCatBuffA
#endif
           

b)      在頭檔案winbase.h中定義了以下宏函數:

lstrcat,lstrcpy, lstrlen,
lstrcmp(對兩個字元串進行區分大小寫的比較),
lstrcmpi(對兩個字元串進行不區分大小寫的比較)
           

c)      在頭檔案winuser.h中:

PTSTRCharLower(PTSTR pszString);
PTSTRCharUpper(PTSTR pszString);
           

>其他的C 運作期函數,如i s a l ph a 、is l o w e r 和is u p p e r ,傳回一個值,指明某個字元是字母字元、小寫字母還是大寫字母。WindowsAPI 提供了一些函數,也能傳回這些資訊:

BOOLIsCharAlpha(TCHAR ch);
BOOLIsCharAlphaNumerica(TCHAR ch);
BOOLIsCharUpper(TCHAR ch);
BOOLIsCharLower(TCHAR ch);
           

windows中對UNICODE和ANSI的處理函數都采用了宏定義,即這樣裡面用到的參數就是類型就不确定是UNICODE還是ANSI,完全是根據編譯環境是否定義了UNICODE/_UNICODE來定。是以參數類型最好采用TCHAR類型,或者是在調用windows方法時進行宏判斷:

#ifdef UNICODE
     WINFUNC(WCHAR)
#else
     WINFUNC(CHAR)
#end
           

3)      在C++标準庫中

這裡隻講std::string和std::wstring。

std::string

http://www.cplusplus.com/reference/string/string/

std::wstring

http://www.cplusplus.com/reference/string/wstring/

4)      在MFC中

在MFC中隊UNICODE和ANSI的封裝使用就是CString了,CString也為一個宏類,即雙重類。

>一個CString對象由可變長度的一隊字元組成。CString使用類似于Basic的文法提供函數和操作符。連接配接和比較操作符以及簡化的記憶體管理使CString對象比普通字元串數組容易使用。

CString是基于TCHAR資料類型的對象。如果在你的程式中定義了符号_UNICODE,則TCHAR被定義為類型wchar_t,即16位字元類型;否則,TCHAR被定義為char,即8位字元類型。在UNICODE方式下,CString對象由16位字元組成。非UNICODE方式下,CString對象由8位字元組成。

當不使用_UNICODE時,CString是多位元組字元集(MBCS,也被認為是雙位元組字元集,DBCS)。注意,對于MBCS字元串,CString仍然基于8位字元來計算,傳回,以及處理字元串,并且你的應用程式必須自己解釋MBCS的開始和結束位元組。

6.      講完了在各個運作環境下對UNICODE和ANSI字元串的操作,現在涉及到的也就是重點中的重點,UNICODE和ANSI字元串之間的轉換操作!

1)      wcstombs和wcstombs_s,mbstowcs和mbstowcs_s

定義在頭檔案stdlib.h中

2)      sprintf和sprintf_s, sprintf_l_s,swprintf和swprintf_s, swprintf_l_s

定義在頭檔案stdio.h中

3)      MultiByteToWideChar,WideCharToMultiByte

定義在頭檔案winnls.h中

7.      C标準庫、windows、MFC的差別:

n  C标準庫:資料類型、方法均為小寫的,未封裝

<tchar.h> <string.h>

_tcs**   wcs**   str**

n  windows:資料類型大寫,方法首字母大寫,未封裝

<winbase.h> <shlwapi.h> <windows.h>

Str**    lstr**

n  MFC:資料類型和方法均封裝起來了,類以C開頭

<afxwin.h>

MFC是對windows的封裝。

CString

8.      C标準庫中寬字元與窄字元之間的轉換

windows核心程式設計中文版.chm

2.9.1 Windows字元串函數

2.9.4 在Unicode和ANSI之間轉換

1)        sprintf && sprintf_s

sprintf定義:

/**
 * @param [out] _Dest 輸出的目的位址
 * @param [in] _Format 輸出格式
 * @param [in] ... 參數清單
 * @retval 傳回輸出字元的個數
 */
int __cdecl sprintf(char* _Dest,const char* _Format, ...);
           

sprintf_s定義:

/**
 * @param [out] _DstBuf 輸出的目的位址
 * @param [in] _SizeInBytes 限制按照輸出格式_Format轉換後的字元個數,同時也限制為_DstBuf配置設定的緩存空間
 * @param [in] _Format 輸出格式
 * @param [in] ... 參數清單
 * @retval 傳回輸出字元的個數
 */
int __cdecl sprintf_s(char* _DstBuf,size_t _SizeInBytes, const char* _Format, ...);
           

函數sprintf和sprintf_s的差別:sprintf_s函數多了一個對目的位址配置設定的空間和轉換字元的個數的限制。例:

ASSERT((sizeof(tranStr)/sizeof(char))<= _SizeInBytes);
ASSERT((sizeof(_DstBuf)/sizeof(char))>= _SizeInBytes);
           

首先判斷的是按照輸出格式轉換的字元串tranStr的個數是否小于等于SizeInBytes,若否,則彈出斷言:Bufferto small。可見,在根據_Format進行字元轉換時所配置設定的臨時存儲空間是由_SizeInBytes來指定的。然後對輸出的目的位址_DstBuf的空間大小進行判斷,判斷其大小是否大于等于_SizeInButes,若否,則可能會出現誤用非法空間的可能。不過sprintf_s都為其做了安全處理。是以總的來說,用sprintf_s比用sprintf更安全。

sprintf在對寬窄字元之間的轉換的用法在于:

轉換窄字元的用法:

char_Dest[100];
sprintf_s(_Dest, 100, "%s","ANSII STR");
           

其中轉換格式必須是小寫s;

轉換寬字元的用法:

sprintf_s(_Dest, 100, "%S",L"UNICODE STR");
           

其中轉換格式必須是大寫S;對比例子:

sprintf_s(_Dest, 100, "%s_%S", "ANSII STR",L"UNICODE STR");
           

同樣,swprintf和swprintf_s的用法:

wchar_t _Dest[100];
           swprintf_s(_Dest,100, L”%s_%S”, L”UNICODE STR”, “ANSII STR”);
           

2)        swprintf && swprintf_s

3)        wcstombs && wcstombs_s

wcstombs的定義:

/**
 * @param [in] _MaxCount 限制取出_Source中字元的個數
 * @retval 傳回轉換的字元的個數,包括對結束符的計算
 */
size_t __cdecl wcstombs(char* _Dest,const wchar_t* _Source, size_t _MaxCount);
           

wcstombs_s的定義:

/**
 * @param [out] _PtNumOfCharConverted 傳回轉換的字元的個數,包括對結束符的計算
 * @param [out] _Dst 轉換字元存放的目的位址
 * @param [in] _DstSizeInBytes 限制取出_Src中字元的個數,同時也限制為_Dst配置設定的緩存空間
 * @param [in] _Src 要轉換的字元
 * @param [in] _MaxCountInBytes 限制取出_Src中字元的個數
 * @retval 傳回錯誤号,為0則表示轉換成功
 */
errno_t__cdecl wcstombs_s(size_t* _PtNumOfCharConverted, char* _Dst, size_t_DstSizeInBytes,  const wchar_t* _Src,size_t _MaxCountInBytes);
           

函數wcstombs_s中參數_DstSizeInBytes起到的作用:對目的位址_Dst配置設定的緩存空間的限制,若sizeof(_Dst)/sizeof(char) < _DstSizeInBytes,則會提示異常;同時也限制從_Src取出的個數,至于确定取_Src中字元的個數,先是選擇_DstSizeInBytes和_MaxCountInBytes中較小的值minNum,然後如果minNum小于wcslen(_Src),則從中取出minNum個字元,再判斷_DstSizeInBytes < (minNum + 1),為true則出斷言。

是以一般的形式為這樣:

wcstombs_s(_PtNumOfCharConverted,_Dst, wcslen(_Src) + 1, _Src, _TRUNCATE);
           mbstowcs_s(_PtNumOfCharConverted,_Dst, strlen(_Src) + 1, _Src, _TRUNCATE);
           

wcstombs_s、wcstombs和mbstowcs_s、mbstowcs都是調用的WideCharToMultiByte和MultiByteToWideChar。這裡是否可以看成是對WideCharToMultiByte和MultiByteToWideChar的封裝使用?

4)        mbstowcs && wcstombs_s

9.      windows中寬字元與窄字元之間的轉換

WideCharToMultiByte

MultiByteToWideChar

10.  MFC中寬字元與窄字元之間的轉換

MFC中跟字元串相關的有:

CString, CStringList, CStringArray。

http://blog.csdn.net/pizi0475/article/details/5346708

目前這些知識暫時夠用,已達到解惑的程度,暫結貼!若以後需要繼續深入在開貼。