# 13.2 字符串類型
使用字符串類來代替標準的字符串指針的好處是被普遍接受的.而wxWidgets就提供了它自己的字符串類:wxString,無論在wxWidgets 內部還是在其提供的API接口上,這個類都被很廣泛的使用.wxString類擁有你對一個字符串類期待的所有的操作,包括:動態內存管理,從其它字符串類型構建,賦值操作,單個字符訪問,字符串連接和比較,子字符串獲取,大小寫轉換,空格字符的修剪和補齊,查找和替換,類似C語言printf的操作以及類似流一樣的插入函數等等.
除了上述的這些字符串處理常用功能,wxString還支持一些額外的特性.wxString完美支持Unicode,包括ANSI字符和Unicode的互相轉換,這個特性是和wxWidgets的編譯配置無關的.使用wxString還使得你的代碼擁有直接將字符串傳遞給庫函數以及直接從庫函數返回字符串的能力.另外,wxString已經實現了90%的STL中的std::string類的函數,這意味著對STL熟悉的用戶基本上不需要重新學習wxString的使用方法.
使用wxString
在你的應用程序中使用wxString類型是非常簡單而直接的.將你程序中使用std::string或者是別的你習慣的字符串類的地方,全部用wxString代替基本就可以了.要注意的是,所有參數中使用字符串的地方,最好使用const wxString&這樣的聲明(這使得函數內部對于字符串的賦值操作由于使用了引用記數器技術而變得更快速),而所有返回值中使用的字符串則最好直接使用wxString類型,這使得在函數內部即使返回一個局部變量也是安全的.
C和C++的程序員通常都已經很熟悉字符串的各種操作了,因此wxString的詳細的API參考就不在這里羅嗦了,請參考wxWidgets的相關文檔.
你可能會注意到wxString的好多函數具有同樣的功能,比如Length,Len和length函數都返回這個字符串的長度.在這種情況下,你最好使用標準STL兼容的函數形式.這會讓你的代碼對別的程序員來說更親切,并且將使你的代碼更容易轉換為別的不使用wxWidgets庫的代碼,你甚至可以直接使用typedef將wxString重定義為std::string.另外wxWidgets某一天可能會開始使用標準的std:: string,因此這種作法也會讓你的代碼更容易保持前向兼容.(當然,wxString的函數也會保留以保證后向兼容.)
wxString,字符以及字符串常量
wxWidgets定義了一個wxChar類型,在不同的編譯選項(ANSI或Unicode)下,這個類型用來映射char類型或者 wchar_t類型.象前面提到的那樣,你不必使用單獨的char類型或者wchar_t類型,wxString內部存儲數據的時候使用的是相應的C類型.在任何時候,如果你需要對單個字符進行操作,你應該使用wxChar類型,這將使得你的代碼在ANSI版本和Unicode版本中保持一致,而不必使用大量的預定義宏.
如果wxWidgets被編譯成Unicode的版本,和標準字符串常量是不兼容的,因為標準字符串常量無論在哪種版本中都是 char*類型.如果你想在Unicode版本中直接使用字符串常量,你應該使用一個轉義宏L.wxWidgets提供了一個宏wxT(或者_T)來封裝字符串常量,這個宏在ANSI版本中被定義為什么事情也不做,而在Unicode版本中則用來封裝L宏,因此無論在哪種版本中,你都可以使用下面的方法使用字符串常量:
```
wxChar ch = wxT('*');
wxString s = wxT("Hello, world!");
wxChar* pChar = wxT("My string");
wxString s2 = pChar;
```
關于使用Unicode版本的更詳細的信息,請參考第16章:"編寫國際化應用程序".
wxString到C指針的轉換基礎
因為有時候你需要直接以C類型訪問wxString的內部數據進行底層操作,wxWidgets提供了幾種對應的訪問方法:
* mb_str函數無論在ANSI版本 還是Unicode版本都返回一個const char*類型的指針const char*, 如果是Unicode版本,則字符串首先經過轉換,轉換過程可能導致數據丟失.
* wc_str函數無論在ANSI版本還是Unicode版本都返回一個wchar_t*類型,如果是ANSI版本,則字符串首先被轉換成Unicode版本然后再返回.
* c_str則返回一個指向內部數據的指針 (ANSI版本為const char*類型, Unicode版本為const wchar_t*類型).不進行任何轉換.
你可以使用c_str函數的特性實現wxString和std::string之間的轉換,如下所示:
```
std::string str1 = wxT("hello");
wxString str2 = str1.c_str();
std::string str3 = str2.c_str();
```
使用wxString經常遇到的一個陷井是過度使用對const char*類型的隱式的類型強制轉換.我們建議你在任何需要使用這種轉換的時候,顯式使用c_str來指明這種轉換,下面的代碼演示了兩個常見的錯誤:
```
// 這段代碼將輸入的字符串轉換為大寫函數,然后將其打印在屏幕上
// 并且返回轉換以后的值 (這是一個充滿bug的代碼)
const char *SayHELLO(const wxString& input)
{
wxString output = input.Upper();
printf("Hello, %s!\n", output);
return output;
}
```
上面這四行代碼有兩個危險的缺陷,第一個是對printf函數的調用.在類似puts這樣的函數中,隱式的類型強制轉換是沒有問題的,因為puts聲明其參數為const char*類型,但是對于printf函數,它的參數采用的是可變參數類型,這意味著上述printf代碼的執行結果可能是任何一個種結果(包括正確打印出期待結果),不過最常見的一種結果是程序異常退出,因此,應該使用下面的代碼代替上面的printf語句:
```
printf(wxT("Hello, %s!\n"), output.c_str());
```
第二個錯誤在于函數的返回值.隱式類型強制轉換又被使用了以此,因為這個函數的返回值是const char*類型,這樣的代碼編譯是沒有問題的,但是它返回的將是一個局部變量的內部指針,而這個局部變量在函數返回以后就很快被釋放了,因此返回的指針將變成一個無效指針.解決的方法很簡單,應該將返回類型更改為wxString類型,下面列出了修改了以后的代碼:
```
// 這段代碼將輸入的字符串轉換為大寫函數,然后將其打印在屏幕上
// 并且返回轉換以后的值 (這是正確的代碼)
wxString SayHELLO(const wxString& input)
{
wxString output = input.Upper();
printf(wxT("Hello, %s!\n"), output.c_str());
return output;
}
```
標準C的字符串處理函數
因為大多數的應用程序都要處理字符串,因此標準C提供了一套相應的函數庫.不幸的是,它們中的一部分是有缺陷的(比如strncpy函數有時候不會添加結束符NULL),另外一部分則可能存在緩沖區溢出的危險.而另一方面,一些很有用的函數卻沒能夠進入標準的C函數庫.這些都是為什么wxWidgets要提供自己的額外的全局靜態函數的原因,wxWidgets的一些靜態函數視圖避免這些缺陷:wxIsEmpty函數增加了對字符串是否為NULL的校驗, 在這種情況下也返回True.wxStrlen函數也可以處理NULL指針,而返回0.wxStricmp函數則是一個平臺相關的大小寫敏感字符串比較函數,它在某些平臺上使用stricmp函數而在另外一些平臺上則使用strcasecmp函數.
"wx/string.h"頭文件中定義了wxSnprintf函數和wxVsnprintf,你應該使用它們代替標準的 sprintf函數以避免一些sprintf函數先天的危險.帶"n"的函數使用了snprintf函數,這個函數在可能的時候對緩沖區進行大小檢查.你還可以使用wxString::Printf而不必擔心遭受可能受到的針對printf的緩沖區溢出攻擊.
和數字的相互轉換
應用程序經常需要實現數字和字符串之間的轉換,比如將用戶的輸入轉換成數字或者將計算的結果顯示在用戶街面上.
ToLong(long* val, int base=10)函數可以將字符串轉換成一個給定進制的有符號整數.它在成功的時候返回True并將結果保存在val中,如果返回值是False,則表明字符串不是一個有效的對應的進制的數字.指定的進制必須是2到36的整數,0意味著根據字符串的前導符決定: 0x開頭的字符串被認為是16進制的, 0-則被認為是8進制的, 其它情況下認為是10進制的.
ToULong(unsigned long* val, int base=10)的工作模式和ToLong函數一致,不過它的轉換結果為無符號類型.
ToDouble(double* val)則實現字符串到浮點數的轉換.返回值為Bool類型.
Printf(const wxChar* pszFormat, ...) 和C語言的sprintf函數類似,將格式化的文本作為自己的內容.返回值為填充字符串的長度.
靜態函數Format(const wxChar* pszFormat, ...)則將格式化的字符串作為返回值.因此你可以使用下面的代碼:
```
int n = 10;
wxString s = "Some Stuff";
s += wxString::Format(wxT("%d"),n );
```
操作符"<<"可以用來在wxString中添加一個int,float或者是double類型的值.
wxStringTokenizer
wxStringTokenizer幫助你將一個字符串分割成幾個小的字符串,它被用類代替和擴展標準C函數strtok,它的使用方法是:傳遞一個字符串和一個可選的分割符(默認為空白符),然后循環調用GetNextToken函數直到HasMoreTokens返回False,如下所示:
```
wxStringTokenizer tkz(wxT("first:second:third:fourth"), wxT(":"));
while ( tkz.HasMoreTokens() )
{
wxString token = tkz.GetNextToken();
// 處理單個字符串
}
```
默認情況下,wxStringTokenizer對于全空字符串的處理和strtok的處理相同,但是和標準函數不同的是,如果分割符為非空字符,它將把空白部分也作為一個子字符串返回.這對于處理那些格式化的表格數據(每一行的列數相同但是單元格數據可能為空)是比較有好處的,比如使用 tab或者逗號作為分割符的情況.
wxStringTokenizer的行為還受最后一個參數的影響,相關的描述如下:
* wxTOKEN_DEFAULT: 如前所述的默認處理方式; 如果分割符為空白字符則等同于wxTOKEN_STRTOK,否則等同于wxTOKEN_RET_EMPTY.
* wxTOKEN_RET_EMPTY: 在這種模式,空白部分將作為一個子字符串部分被返回,例如"a::b:"如果用":"分割則返回三個子字符串a, ""和b.
* wxTOKEN_RET_EMPTY_ALL: 在這種模式下,最后的空白部分也將作為一個子字符串返回. 這樣"a::b:"使用":"分割將返回四個子字符串,其三個和wxTOKEN_RET_EMPTY返回的相同,最后一個則為一個"".
* wxTOKEN_RET_DELIMS: 在這種模式下,分割符也作為子字符串的一部分(除了最后一個子字符串,它是沒有分割符的),其它方面類似wxTOKEN_RET_EMPTY.
* wxTOKEN_STRTOK: 這種情況下,子字符串的產生結果和標準strtok函數完全相同.空白字符串將不作為一個子字符串.
wxStringTokenizer還有下面兩個有用的成員函數:
* CountTokens函數返回分割完的子字符串的數目.
* GetPosition返回某個位置的子字符串.
wxRegEx
wxRegEx類用來實現正則表達式.這個類支持的操作包括正則表達式查找和替換.其實現方式有基于系統正則表達式庫(比如現代的類 Unix系統以及Mac OSX支持的POSIX標準正則表達式庫)或者基于由Henry Spencer提供的wxWidgets內建庫.POSIX定義的正則表達式有基礎和擴展兩套版本.內建的版本支持這兩種模式而基于系統庫的版本則不支持擴展模式.
即使是對于那些支持正則表達式庫的系統,wxWidgets默認的Unicode版本也采用了內建的正則表達式版本,ANSI版本則使用系統提供的版本.記住只有內建版本的正則表達式庫才能完全支持Unicode.當編譯wxWidgets的時候,覆蓋這種默認設置是被允許的.如果在使用系統正則表達式庫的Unicode版本中,在使用對應函數的之前,表達式和要匹配的數據都將被轉換成8-bit編碼的Unicode方式.
使用wxRegEx的方法和其它所有使用正則表達式的方法沒有區別.因為正則表達式的內容較為羅嗦而且又鑒于正則表達式的只在特定情況下使用,請參考wxWidgets手冊中的相關內容了解具體的API.
- 第一章 介紹
- 1.1 為什么要使用wxWidgets?
- 1.2 wxWidgets的歷史
- 1.3 wxWidgets社區
- 1.4 wxWidgets和面向對象編程
- 1.5 wxWidgets的體系結構
- 1.6 許可協議
- 第一章小結
- 第二章 開始使用
- 2.1 一個小例子
- 2.2 應用程序類
- 2.3 Frame窗口類
- 2.4 事件處理函數
- 2.5 Frame窗口的構造函數
- 2.6 完整的例子
- 2.7 wxWidgets程序一般執行過程
- 2.8 編譯和運行程序
- 第二章小結
- 第三章 事件處理
- 3.1 事件驅動編程
- 3.2 事件表和事件處理過程
- 3.3 過濾某個事件
- 3.4 掛載事件表
- 3.5 動態事件處理方法
- 3.6 窗口標識符
- 3.7 自定義事件
- 第三章小結
- 第四章 窗口的基礎知識
- 4.1 窗口解析
- 4.2 窗口類概覽
- 4.3 基礎窗口類
- 4.4 頂層窗口
- 4.5 容器窗口
- 4.6 非靜態控件
- 4.7 靜態控件
- 4.8 菜單
- 4.9 控制條
- 第四章小結
- 第五章繪畫和打印
- 5.1 理解設備上下文
- 5.2 繪畫工具
- 5.3 設備上下文中的繪畫函數
- 5.4 使用打印框架
- 5.5 使用wxGLCanvas繪制三維圖形
- 第五章小節
- 第六章處理用戶輸入
- 6.1 鼠標輸入
- 6.2 處理鍵盤事件
- 6.3 處理游戲手柄事件
- 第六章小結
- 第七章使用布局控件進行窗口布局
- 7.1 窗口布局基礎
- 7.2 窗口布局控件
- 7.3 使用布局控件進行編程
- 7.4 更多關于布局的話題
- 第七章小結
- 第八章使用標準對話框
- 8.1信息對話框
- 8.2 文件和目錄對話框
- 8.3 選擇和選項對話框
- 8.4 輸入對話框
- 8.5 打印對話框
- 第八章小結
- 第九章創建定制的對話框
- 9.1 創建定制對話框的步驟
- 9.2 一個例子:PersonalRecordDialog
- 9.3 在小型設備上調整你的對話框
- 9.4 一些更深入的話題
- 9.5 使用wxWidgets資源文件
- 第九章小結
- 第十章使用圖像編程
- 10.1 wxWidgets中圖片相關的類
- 10.2 使用wxBitmap編程
- 10.3 使用wxIcon編程
- 10.4 使用wxCursor編程
- 10.5 使用wxImage編程
- 10.6 圖片列表和圖標集
- 10.7 自定義wxWidgets提供的小圖片
- 第十章小結
- 第十一章剪貼板和拖放操作
- 11.1 數據對象
- 11.2 使用剪貼板
- 11.3 實現拖放操作
- 第十一章小結
- 第十二章高級窗口控件
- 12.1 wxTreeCtrl
- 12.2 wxListCtrl
- 12.3 wxWizard
- 12.4 wxHtmlWindow
- 12.5 wxGrid
- 12.6 wxTaskBarIcon
- 12.7 編寫自定義的控件
- 第十二章小結
- 第十三章數據結構類
- 13.1 為什么沒有使用STL?
- 13.2 字符串類型
- 13.3 wxArray
- 13.4 wxList和wxNode
- 13.5 wxHashMap
- 13.6 存儲和使用日期和時間
- 13.7 其它常用的數據類型
- 第十三章小結
- 第十四章文件和流操作
- 14.1 文件類和函數
- 14.2 流操作相關類
- 第十四章小結
- 第十五章內存管理,調試和錯誤處理
- 15.1 內存管理基礎
- 15.2 檢測內存泄漏和其它錯誤
- 15.3 構建自防御的程序
- 15.4 錯誤報告
- 15.5 提供運行期類型信息
- 15.6 使用wxModule
- 15.7 加載動態鏈接庫
- 15.8 異常處理
- 15.9 調試提示
- 第十五章小結
- 第十六章編寫國際化程序
- 16.1 國際化介紹
- 16.2 從翻譯說起
- 16.3 字符編碼和Unicode
- 16.4 數字和日期
- 16.5 其它媒介
- 16.6 一個小例子
- 第十六章小結
- 第十七章編寫多線程程序
- 17.1 什么時候使用多線程,什么時候不要使用
- 17.2 使用wxThread
- 17.3 用于線程同步的對象
- 17.4 多線程的替代方案
- 第十七章小結
- 第十八章使用wxSocket編程
- 18.1 Socket類和功能概覽
- 18.2 Socket及其基本處理介紹
- 18.3 Socket標記
- 18.4 使用Socket流
- 18.5 替代wxSocket
- 第十八章小結
- 第十九章使用文檔/視圖框架
- 19.1 文檔/視圖基礎
- 19.2 文檔/視圖框架的其它能力
- 19.3 實現Undo/Redo的策略
- 第十九章小結
- 第二十章完善你的應用程序
- 20.1 單個實例和多個實例
- 20.2 更改事件處理機制
- 20.3 降低閃爍
- 20.4 實現聯機幫助
- 20.5 解析命令行參數
- 20.6 存儲應用程序資源
- 20.7 調用別的應用程序
- 20.8 管理應用程序設置
- 20.9 應用程序安裝
- 20.10 遵循用戶界面設計規范
- 20.11 全書小結