📄 字串處理與正規表達式
📌 為什麼字串處理這麼重要?
字串就像日常生活中的語言——幾乎所有程式都需要處理文字。不論是讀取使用者輸入、處理檔案內容、還是組合 API 回應,字串無所不在。
想像你經營一家郵局:
- 字串方法就像基本的信件處理工具(拆信、蓋章、裁剪)
- 正規表達式就像進階的地址辨識系統(自動驗證地址格式是否正確)
📝 常用字串方法
Contains、Replace、Split、Trim
// 準備一個測試字串
string message = " Hello, World! Welcome to C#. ";
// Trim:去除前後空白(像把信封邊緣多餘的紙裁掉)
string trimmed = message.Trim(); // "Hello, World! Welcome to C#."
// Contains:檢查是否包含某段文字(像在信裡找關鍵字)
bool hasWorld = trimmed.Contains("World"); // true
// Replace:取代文字(像用修正帶把錯字蓋掉再寫上新字)
string replaced = trimmed.Replace("World", "C# 開發者"); // "Hello, C# 開發者! Welcome to C#."
// Split:用指定字元切割字串(像把一長串珍珠項鍊拆成一顆顆珍珠)
string csv = "蘋果,香蕉,橘子,葡萄";
string[] fruits = csv.Split(','); // ["蘋果", "香蕉", "橘子", "葡萄"]
// 走訪切割後的每個元素
foreach (string fruit in fruits) // 逐一取出每顆水果
{
Console.WriteLine(fruit); // 印出水果名稱
}
Substring 與索引
// 準備一個字串
string text = "Hello, C# World";
// Substring:擷取子字串(像從書中撕下某幾頁)
string sub1 = text.Substring(7); // "C# World"(從索引 7 開始到結尾)
string sub2 = text.Substring(7, 2); // "C#"(從索引 7 開始取 2 個字元)
// IndexOf:找出某段文字第一次出現的位置(像在書裡找某個詞在第幾頁)
int pos = text.IndexOf("C#"); // 7
// ToUpper / ToLower:大小寫轉換(像把小寫印章換成大寫印章)
string upper = text.ToUpper(); // "HELLO, C# WORLD"
string lower = text.ToLower(); // "hello, c# world"
// StartsWith / EndsWith:檢查開頭或結尾(像看信封上的郵遞區號)
bool startsH = text.StartsWith("Hello"); // true(是否以 Hello 開頭)
bool endsW = text.EndsWith("World"); // true(是否以 World 結尾)
🔗 字串插值 vs String.Format
// 基本資料
string name = "小明"; // 姓名
int age = 25; // 年齡
double score = 95.5; // 分數
// 方法 1:字串插值 $"..." — 最推薦!(像填空題,直接把答案寫在空格裡)
string msg1 = $"我叫 {name},今年 {age} 歲,考了 {score} 分";
// 方法 2:String.Format — 傳統方式(像用編號對應答案)
string msg2 = String.Format("我叫 {0},今年 {1} 歲,考了 {2} 分", name, age, score);
// 字串插值支援格式化(像指定小數點要幾位)
string formatted = $"成績:{score:F1},日期:{DateTime.Now:yyyy-MM-dd}";
// 結果類似:"成績:95.5,日期:2024-01-15"
// 字串插值支援運算式(像計算機,直接在空格裡算)
string calc = $"{name} 明年 {age + 1} 歲"; // "小明 明年 26 歲"
⚡ StringBuilder:效能的好幫手
// ❌ 不好的做法:大量串接字串(像每次都重新抄一整封信再加一行)
string result = ""; // 空字串
for (int i = 0; i < 10000; i++) // 迴圈一萬次
{
result += $"第 {i} 行\n"; // 每次都建立新的字串物件,非常慢!
}
// ✅ 好的做法:使用 StringBuilder(像在同一張紙上持續往下寫)
using System.Text; // 引用 StringBuilder 所在的命名空間
StringBuilder sb = new StringBuilder(); // 建立 StringBuilder 實例
for (int i = 0; i < 10000; i++) // 迴圈一萬次
{
sb.AppendLine($"第 {i} 行"); // 在現有內容後面附加新行,不會建立新物件
}
string output = sb.ToString(); // 最後一次轉成字串
// StringBuilder 常用方法
StringBuilder builder = new StringBuilder("Hello"); // 初始內容
builder.Append(" World"); // 附加文字:"Hello World"
builder.Insert(5, ","); // 在索引 5 插入逗號:"Hello, World"
builder.Replace("World", "C#"); // 取代文字:"Hello, C#"
builder.Remove(5, 2); // 從索引 5 移除 2 個字元:"HelloC#"
💡 何時用 StringBuilder? 當你需要在迴圈中拼接超過 10 次以上的字串時,就該考慮使用 StringBuilder。就像蓋房子,如果只釘一兩根釘子用手動就好,但要釘一萬根就需要電動釘槍。
🔍 正規表達式 (Regex)
正規表達式就像一個超級搜尋引擎——你給它一個「模式」,它就能在文字中找出所有符合模式的內容。
基本使用
using System.Text.RegularExpressions; // 引用正規表達式命名空間
// IsMatch:檢查字串是否符合模式(像安檢門,通過就 true)
string phone = "0912-345-678"; // 要驗證的電話號碼
bool isValid = Regex.IsMatch(phone, @"^\d{4}-\d{3}-\d{3}$"); // true
// ^ 表示開頭,\d 表示數字,{4} 表示剛好 4 個,$ 表示結尾
// Match:找出第一個符合的結果(像在人群中找到第一個穿紅衣的人)
string text = "我的電話是 0912-345-678,辦公室是 02-1234-5678";
Match match = Regex.Match(text, @"\d{4}-\d{3}-\d{3}"); // 找手機號碼模式
if (match.Success) // 如果有找到
{
Console.WriteLine($"找到:{match.Value}"); // "找到:0912-345-678"
}
// Matches:找出所有符合的結果(像找出人群中所有穿紅衣的人)
MatchCollection allMatches = Regex.Matches(text, @"\d+"); // 找出所有連續數字
foreach (Match m in allMatches) // 走訪每一個符合的結果
{
Console.WriteLine(m.Value); // 印出:0912、345、678、02、1234、5678
}
Replace 與常用模式
// Regex.Replace:用模式取代文字(像自動修正所有錯別字)
string dirty = "價格是 100 元"; // 多餘的空白
string clean = Regex.Replace(dirty, @"\s+", " "); // 把多個空白變成一個
// 結果:"價格是 100 元"
// Email 驗證模式
string email = "user@example.com"; // 要驗證的 email
string emailPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
bool isEmail = Regex.IsMatch(email, emailPattern); // true
// [a-zA-Z0-9._%+-]+ 表示使用者名稱可以包含英文、數字、點等字元
// 手機號碼驗證(台灣格式)
string phonePattern = @"^09\d{2}-?\d{3}-?\d{3}$"; // 09 開頭,可選的破折號
bool isPhone1 = Regex.IsMatch("0912345678", phonePattern); // true(沒有破折號)
bool isPhone2 = Regex.IsMatch("0912-345-678", phonePattern); // true(有破折號)
📜 Verbatim 字串與 C# 11 原始字串
// 一般字串:需要用 \\ 來表示反斜線(像要寫特殊符號時需要前面加暗號)
string path1 = "C:\\Users\\Desktop\\file.txt"; // 需要跳脫每個反斜線
// Verbatim 字串 @"...":所見即所得(像照片,拍什麼就是什麼)
string path2 = @"C:\Users\Desktop\file.txt"; // 不需要跳脫,直接寫路徑
string multiLine = @"第一行
第二行
第三行"; // 可以直接換行,不需要 \n
// 在 verbatim 字串中要表示雙引號,用兩個雙引號
string quote = @"他說:""你好"",然後離開了"; // 他說:"你好",然後離開了
// C# 11 原始字串字面值 """..."""(像超強的保護套,裡面寫什麼都不用跳脫)
string json = """
{
"name": "小明",
"age": 25
}
"""; // 不需要跳脫任何雙引號
// 原始字串也支援插值
string name = "小明";
string rawInterpolated = $"""
{{
"name": "{name}",
"greeting": "你好"
}}
"""; // 用雙大括號 {{ }} 表示 JSON 的大括號
🤔 我這樣寫為什麼會錯?
❌ 錯誤 1:混淆 null、空字串和空白字串
// ❌ 錯誤寫法:用 == 檢查 null 和空字串
string input = GetUserInput(); // 取得使用者輸入
if (input == null || input == "") // 這樣寫可以,但不夠好
{
Console.WriteLine("沒有輸入"); // 印出提示
}
// ✅ 正確寫法:使用 string.IsNullOrEmpty 或 string.IsNullOrWhiteSpace
string input = GetUserInput(); // 取得使用者輸入
// 檢查 null 或空字串
if (string.IsNullOrEmpty(input)) // 同時處理 null 和 ""
{
Console.WriteLine("沒有輸入"); // 印出提示
}
// 更嚴格:還能抓到只有空白的情況(如 " ")
if (string.IsNullOrWhiteSpace(input)) // 同時處理 null、"" 和 " "
{
Console.WriteLine("沒有有效輸入"); // 印出提示
}
解釋: string.IsNullOrWhiteSpace 最安全,因為使用者可能只輸入空白鍵。就像檢查信封裡有沒有信,不只要看信封是不是空的,還要看裡面是不是只塞了白紙。
❌ 錯誤 2:字串比較時忽略文化差異
// ❌ 錯誤寫法:直接用 == 比較使用者輸入(可能因大小寫不同而失敗)
string userInput = "hello"; // 使用者輸入的文字
if (userInput == "Hello") // false!大小寫不同
{
Console.WriteLine("匹配"); // 永遠不會執行到這裡
}
// ✅ 正確寫法:指定比較方式
string userInput = "hello"; // 使用者輸入的文字
// 忽略大小寫比較
if (userInput.Equals("Hello", StringComparison.OrdinalIgnoreCase)) // true
{
Console.WriteLine("匹配"); // 會印出
}
// 或者用 ToLower/ToUpper 統一後再比(但效能較差,因為會建立新字串)
if (userInput.ToLower() == "hello") // true,但不推薦
{
Console.WriteLine("匹配"); // 會印出
}
解釋: 就像問路時,"台北車站" 和 "台北車站 " 應該是同一個地方,但電腦會認為它們不同。使用 StringComparison.OrdinalIgnoreCase 可以讓比較更有彈性,不會因為大小寫而出錯。
❌ 錯誤 3:在迴圈中用 + 拼接大量字串
// ❌ 錯誤寫法:在迴圈中用 + 拼接字串(每次都會建立新物件)
string html = ""; // 空字串
for (int i = 0; i < 1000; i++) // 迴圈一千次
{
html += $"<li>項目 {i}</li>"; // 每次 += 都會建立一個新的字串物件
}
// 這會建立 1000 個暫時的字串物件,浪費記憶體!
// ✅ 正確寫法:使用 StringBuilder
StringBuilder sb = new StringBuilder(); // 建立 StringBuilder
for (int i = 0; i < 1000; i++) // 迴圈一千次
{
sb.Append($"<li>項目 {i}</li>"); // 在同一個緩衝區附加文字
}
string html = sb.ToString(); // 最後一次性轉為字串
解釋: 字串在 C# 中是不可變的(immutable)。每次 += 都會建立一個全新的字串物件,舊的就變成垃圾等待回收。就像每次想加一頁就把整本書重新印一次——用 StringBuilder 就像在同一本筆記本上繼續寫,效率高很多。