不管創(chuàng)建什么類型的應用程序,你都需要使用字符串。無論數據如何存儲,終端用戶總要與可讀的文本打交道。因此,了解如何使用字符串是任何.net開發(fā)者創(chuàng)建豐富的應用程序所需要學習的必要知識。除了向你展示在.net框架中字符串的使用外,本文還將向你介紹正規(guī)表達式的知識。
  除了向你展示如何使用.net框架中的字符串外,本文還將向你介紹正規(guī)表達式。正規(guī)表達式是格式代碼,不僅允許你驗證一個特定字符串匹配一個給定的格式,而且你還可以使用正規(guī)表達式來從任何其它可能被認為是自由格式的文本中提取有意義的信息,例如從用戶輸入中提取第一個名字,或從一個數字輸入中提取代碼,或從一個url中提取服務器名。
  一、 使用字符串
  使用字符串是創(chuàng)建高質量應用程序的一個必要的技巧。即使你在處理數字或圖像數據,終端用戶也需要上下文反饋。本文將向你介紹.net字符串,如何格式它們,操作它們和比較它們,及其它有用的操作。
  (一) .net字符串簡介
  在.net框架和通用語言運行時刻(clr)以前,開發(fā)者總是花費大量時間處理字符串。一個字符串例程可重用庫幾乎是每一個c和c++程序員的工具箱中的一部分。編寫在不同程序語言之間交換字符串數據的代碼也是相當困難的。例如,pascal把字符串存儲為一個內存字符數組,其中,該數組的第一個元素指示字符串的長度;而c把字符串存儲為一個具有可變長度的字符內存數組,字符串的末端加上一個ascii null字符(在c中以"/0"表示)。
  在.net框架中,字符串以常量方式存儲。這意味著,當你用c#(或任何其它.net語言)創(chuàng)建一個字符串時,該字符串以一種固定大小存儲在內存以便clr運行更快些。結果是,當你實現例如連接字符串或修改一個字符串中的單個字符時,clr實際上是創(chuàng)建你的字符串的多個副本。
  c#中的字符串與其它值類型例如整數或浮點數聲明方式相同,見下面的例子:
string x = "hello world";
string y;
string z = x;
  (二) 格式化字符串
  當使用字符串時最常見的一項任務是格式化字符串。當向用戶顯示信息時,你經常顯示如日期,時間,數字值,十進制值,貨幣值,甚至象十六進制數字這樣的內容。c#字符串都能夠顯示這些類型的信息,甚至更多。另外一個強有力的特征是,當你使用該標準格式化工具時,該格式化的輸出具有地區(qū)感知特征。例如,如果你以短格式顯示一個英格蘭用戶的當前日期,那么對于一個美國用戶來說,當前日期的短格式將以不同形式顯示。
  為了創(chuàng)建一個格式化的字符串,你僅需要調用string類的format方法,并且傳遞給它一個格式字符串,如下列代碼所顯示的: 
string formatted = string.format("the value is {0}", value);
  在此,{0}占位符指示一個值應該被插入的位置。除了指定一個值應該被插入的位置外,你還可以指定該值的格式。
  其它數據類型還支持經由定制格式修飾符轉換成字符串,例如,datetime數據類型,通過使用如下方式,它能夠產生一種定制格式的輸出:
datetime.tostring("format specifiers");
  表格1列舉了用于格式化日期,時間,數字值等數據的一些最常用的格式字符串。
  表格1.定制datetime格式修飾符
| 修飾符 | 描述 | 
| d | 顯示某月中的這一天。 | 
| dd | 顯示某月中的這一天,其中,小于10的值之前加上一個0。 | 
| ddd | 顯示一個星期中某一天的三字母縮寫名。 | 
| dddd(+) | 顯示給定的datetime值中星期中的一天的完整名。 | 
| f(+) | 顯示秒值的最重要的x位數。在f中格式修飾符位數越多,該數字越重要。這是個總秒數,而不是從上個分鐘以來經過的秒數。 | 
| f(+) | 與f(+)相同,除了不顯示末尾的零外。 | 
| g | 顯示一個給定的datetime中的時代,例如,"a.d"。 | 
| h | 顯示小時,范圍為:1~12。 | 
| hh | 顯示小時,范圍為:1~12,其中,小于10的值之前加上一個0。 | 
| h | 顯示小時范圍為:0~23。 | 
| hh | 顯示小時范圍為:0~23,其中,小于10的值之前加上一個0。 | 
| m | 顯示分鐘,范圍為0~59。 | 
| mm | 顯示分鐘,范圍為0~59,其中,小于10的值之前加上一個0。 | 
| m | 顯示月份,范圍為1~12。 | 
| mm | 顯示月份,范圍為1~12,其中,小于10的值之前加上一個0。 | 
| mmm | 顯示月份的三字符縮略名。 | 
| mmmm | 顯示月份的完整名字。 | 
| s | 顯示秒數范圍為:0~59。 | 
| ss(+) | 顯示秒數范圍為:0~59,其中,小于10的值之前加上一個0。 | 
| t | 顯示給定的時間中am/pm指示器中的第一個字符。 | 
| tt(+) | 顯示給定的時間中完整的am/pm指示器。 | 
| y/yy/yyyy | 顯示給定的時間中的年份。 | 
| z/zz/zzz(+) | 顯示給定的時間中的時區(qū)偏移量。 | 
  讓我們觀察下列代碼,它展示使用字符串格式修飾符創(chuàng)建定制格式的日期和時間字符串:
datetime dt = datetime.now;
console.writeline(string.format("default format: {0}", dt.tostring()));
console.writeline(dt.tostring("dddd dd mmmm, yyyy g"));
console.writeline(string.format("custom format 1: {0:mm/dd/yy hh:mm:sstt}", dt));
console.writeline(string.format("custom format 2: {0:hh:mm:sstt g//mt zz}", dt));
  下面是前面代碼的輸出:
default format: 9/24/2005 12:59:49 pm
saturday 24 september, 2005 a.d.
custom format 1: 09/24/05 12:59:49pm
custom format 2: 12:59:49pm gmt -06
  你還可以提供針對數字值的定制格式修飾符。表格2描述了適用于數字值的定制格式修飾符。
  表格2.數字定制格式修飾符
| 修飾符 | 描述 | 
| 0 | 零占位符。 | 
| # | 數字占位符。如果給定的值中在#修飾符指示的位置有一個數字,那么該數字將以格式化輸出顯示。 | 
| . | 十進制點。 | 
| , | 千分位分隔符。 | 
| % | 百分比修飾符。被格式化的值在包括到格式化的輸出前將乘以100。 | 
| e0/e+0/e/e+0/e-0/e | 科學標志。 | 
| "xx"或"xx" | 代表格式的字符串。這些被包含在格式化的輸出中,而不翻譯其相對位置。 | 
| ; | 用于條件格式化負數,零和正值的節(jié)分隔符。 | 
  如果定義多個格式節(jié),那么你可以更精確地控制數字的格式化:
  · 兩個節(jié)-如果你有兩個格式化節(jié),則第一節(jié)應用于所有正數(包括0)值。第二節(jié)應用于負數值,當你想把負數值包括在括號中(就象中許多財務軟件包中一樣),這是十分方便的。
  · 三個節(jié)-如果你有三個格式化節(jié),則第一個節(jié)應用于所有正數(不包括0)值。第二節(jié)應用于負數值,第三節(jié)應用于零。
  下列代碼顯示如何使用定制數字格式修飾符。
double dval = 59.99;
double dneg = -569.99;
double zeroval = 0.0;
double pct = 0.23;
string formatstring = "{0:$#,###0.00;($#,###0.00);nuttin}";
console.writeline(string.format(formatstring, dval));
console.writeline(string.format(formatstring, dneg));
console.writeline(string.format(formatstring, zeroval));
console.writeline(pct.tostring("00%"));
  前面的代碼將產生如下所示的輸出結果:
$59.99
($569.99)
nuttin
23%  (三) 操作和比較字符串
  除了顯示包含各種格式化數據的字符串外,其它普通與字符串有關的任務就是字符串操作和比較。要記住的一個重要的事情是,字符串實際上是.net框架基類庫中的一個類。因為它是一個類,所以實際上,你可以調用一個字符串的方法,就象你可以調用任何其它類上的方法一樣。
  你可以在字符串常數或字符串變量調用這些方法,見下列代碼:
int x = string.length();
int y = "hello world".length();
  表格3簡短列舉了一些你可以使用于字符串的最常用的方法以便獲得該字符串的信息或操作它。
  表格3.常用的字符串實例方法
| 方法 | 描述 | 
| compareto | 把這個字符串實例與其它字符串實例比較。 | 
| contains | 返回一布爾值,指示是否當前字符串實例包含給定的子串。 | 
| copyto | 從字符串實例中把一個子串復制到一個字符數組的特定位置。 | 
| endswith | 返回一布爾值,指示是否字符串以一個給定的子串結束。 | 
| equals | 指示是否該字符串等于另一個字符串。你還可以使用'=='操作符來代替。 | 
| indexof | 返回一個子串在字符串實例中的索引。 | 
| indexofany | 返回一個字符串實例中在子串內的任何字符的第一次索引的出現。 | 
| padleft | 使用特定數目的空格或其它unicode字符來填充字符串,特別適用于字符串右對齊。 | 
| padright | 把一組特定的空格字符或其它unicode字符添加到字符串的最后,創(chuàng)建一個字符串右對齊效果。 | 
| remove | 從字符串中刪除給定數目的字符。 | 
| replace | 使用特定的代替內容來代替一個給定的字符或字符串在字符串實例中的所有出現。 | 
| split | 使用特定的字符作為分割點,把當前字符串分解成一個字符串數組。 | 
| startswith | 返回一個布爾值,指示是否該字符串實例以一個特定的字符串開始。 | 
| substring | 給定起始點和長度的情況下,返回字符串的特定部分。 | 
| tochararray | 把字符串轉換成一個字符數組。 | 
| tolower | 把字符串全部轉換成小寫字符。 | 
| toupper | 把字符串全部轉換成大寫字符。 | 
| trim | 從一個字符串的開始和結束位置,刪除一組給定字符的所有出現。 | 
| trimstart | 實現trim功能,但僅從字符串的開始位置。 | 
| trimend | 實現trim功能,但僅從字符串的結束位置。 | 
  下列代碼展示了你可以使用上面的相應函數來實現字符串查詢和操作等: 
string sourcestring = "mary had a little lamb";
string sourcestring2 = " mary had a little lamb ";
console.writeline(sourcestring.tolower());
console.writeline(string.format("the string '{0}' is {1} chars long",
sourcestring,sourcestring.length));
console.writeline(string.format("fourth word in sentence is : {0}",
sourcestring.split(' ')[3]));
console.writeline(sourcestring2.trim());
console.writeline("two strings equal? " + (sourcestring == sourcestring2.trim()));
  前面的代碼輸出如下所示結果:
mary had a little lamb
the string 'mary had 一個 little lamb' is 22 chars long.
fourth word in sentence is : little
mary had a little lamb
two strings equal? true
  (四) stringbuilder入門
  如前面所提及,字符串是常量。這意味著,當你把兩個字符串連接成一個新的字符串時,有一段時間clr在內存中有三個字符串。因此,例如,當你連接實現如下代碼所示連接時:
string a = "hello";
string b = "world";
string c = 一個 + " " + c;
  實際上,在內存共有四個字符串,包括空格。為了緩和這個字符串連接性能問題并且提供給你一個工具使連接更容易些,.net框架中提供了一個類stringbuilder。
  通過使用stringbuilder動態(tài)地創(chuàng)建可變長度的字符串,你克服了clr字符串中常量字符串這一事實;而且,這樣以來,該代碼變得更具可讀性。下列代碼展示了stringbuilder的使用:
stringbuilder sb = new stringbuilder();
sb.append("greetings!/n");
formatstring = "{0:$#,###0.00;($#,###0.00);zero}";
dval = 129.99;
sb.appendformat(formatstring, dval);
sb.append("/nthis is a big concatenated string.");
console.writeline(sb.tostring());
  前面的代碼輸出如下所示結果:
greetings!
$129.99
this is a big concatenated string.
  注意,前面代碼中的"/n"把一個換行符字符插入到字符串中。  二、 使用正規(guī)表達式
  正規(guī)表達式允許快速有效地處理文本。被處理的文本小到一個電子郵件地址,大到一個多行的輸入框內容。正規(guī)表達式的使用不僅允許你使用一個定義模式來校驗文本,而且還允許你從匹配一個給定模式的文本中提取數據。
  你可以把一個正規(guī)表達式當作是一種特別強有力的通配符。當我們看到象"sams*"這樣的表達式時,我們都會熟悉通配符,任何以單詞sams開頭的內容都是一個匹配的表達式。正規(guī)表達式能為你提供遠遠超過通配符的強有力的控制功能。 
  本節(jié)先向你簡短地介紹一下.net框架中提供的支持使用正規(guī)表達式的類。有關正規(guī)表達式的更多信息,你可以參考《正規(guī)表達式快速參考手冊》或《精通正規(guī)表達式》的第二版。這些書將提供給你需要的信息以便創(chuàng)建你自己的正規(guī)表達式,而且還提供了常用正規(guī)表達式的一組列表。至于正規(guī)表達式本身已經超出本文的討論范圍。
  (一) 校驗輸入
  正規(guī)表達式的一種最常用的場所是用于使用一些預先定義的格式(例如,強制建立的規(guī)則用于確保口令中包含特定的使其很難被拆斷的字符)校驗用戶輸入。這些規(guī)則被典型地定義為正規(guī)表達式。正規(guī)表達式也常用于校驗簡單的輸入,例如電子郵件地址和電話號碼。
  .net框架提供的操作正規(guī)表達式的一個關鍵類是regex類。這個類提供一個靜態(tài)的方法ismatch,它返回一個布爾值指示是否指定的輸入字符串匹配一個給定的正規(guī)表達式。
  在下列代碼中,使用一個普通正規(guī)表達式來測試電子郵件地址的有效性:
string emailpattern = @"^([/w-/.]+)@((/[[0-9]{1,3}/.[0-9]{1,3}/.[0-9]{1,3}/.)|[ccc]
(([/w-]+/.)+))([a-za-z]{2,4}|[0-9]{1,3})(/]?)$";
console.write("enter an e-mail address:");
string emailinput = console.readline();
bool match = regex.ismatch(emailinput, emailpattern);
if (match)
 console.writeline("e-mail address is valid.");
else
 console.writeline("supplied input is not a valid e-mail address.");
  如果你搞不清楚這個正規(guī)表達式,別擔心。電子郵件模式的基本思想是,它需要一些數字字母字符,后面跟著一個@符號,然后是一些字符組合,再后面跟著一個".",再往后至少跟著兩個字符。你可以以不同的輸入試驗前面的代碼來看一下你得到什么結果。即使你不理解該正規(guī)表達式本身,只要知道它們的存在,那么你就可以把它使用于你的應用程序中來校驗輸入。 
  (二) 從輸入中提取數據
  正規(guī)表達式的其它常見的用法是,根據表達式分析文本以及使用之來從用戶輸入中提取數據(稱作組匹配)。
  正規(guī)表達式中包括一個特征叫組。一個組允許你把一個命名標識放到該正規(guī)表達式的一個特定節(jié)中。當你調用match()來針對模式比較輸入數據時,其結果實際上把匹配分成一些組,允許你提取匹配每一個組的輸入的部分。
  例如,在前面的例子中,我們創(chuàng)建了一個username,它允許我們提取在一個電子郵件地址中位于@符號前的所有數據。然后,當執(zhí)行一個匹配時,我們能夠使用正規(guī)表達式的命名組從輸入中提取該username。
  下列代碼顯示怎樣從一個用戶在控制臺輸入的url中提取協(xié)議名字和端口號。正規(guī)表達式的偉大在于,它們使用自己的語言;因此,它們不必依賴于c、c++、c#、vb.net或任何其它語言。在下列代碼中的正規(guī)表達式來自于一個msdn例子:
string urlpattern = @"^(?<proto>/w+)://[^/]+?(?<port>:/d+)?/";
console.writeline();
console.write("enter a url for data parsing: ");
string url = console.readline();
regex urlexpression = new regex(urlpattern, regexoptions.compiled);
match urlmatch = urlexpression.match(url);
console.writeline("the protocol you entered was " + urlmatch.groups["proto"].value);
console.writeline("the port number you entered was " + urlmatch.groups["port"].value);
  當你使用不帶有一個端口號的url運行前面的代碼時,你會注意到,你沒有得到任何組值。這是因為,該輸入根本不匹配正規(guī)表達式。當不存在匹配時,你顯然無法從給定的組中提取有意義的數據。當你使用匹配該正規(guī)表達式的端口號的url運行前面的代碼時,你將得到如下列文本所示的輸出結果: 
entera url for data parsing: http://server.com:2100/home.aspx
the protocol you entered was http
the port number you entered was :2100
  三、 總結
  在本文中,你已看到現在你有了自己的字符串例程庫。借助于c#和.net框架,字符串成為該基類庫的一個本機組成部分,并且提供給你大量的工具方法用于實現字符串的比較,操作,格式化等操作。你還看到,stringbuilder類向你提供了一組易于使用的工具方法以便動態(tài)地構建字符串而不會帶來本地字符串連接的性能損失。
  最后,本文向你簡短介紹了正規(guī)表達式的威力以及regex類是如何把這些功能整合到一起的。通過閱讀本文和試驗相應的示例代碼后,你應該熟悉一些字符串和正規(guī)表達式操作以使你的應用程序更為有力。