C# 字符串
字符串是程序里用得最多的数据类型之一。C# 的 string 设计有几个特别之处:不可变(immutable)、字符串插值语法优雅、引用类型但表现像值类型。这一篇我们一次搞懂。
一、字符串基础
string 是 C# 的字符串类型(别名 System.String),用双引号包裹:
// 字符串字面量
string s1 = "Hello"; // 普通字符串
string s2 = ""; // 空字符串
// string s3 = null; // null(未指向任何对象)
// 推荐用 string.Empty 而不是 ""
string empty = string.Empty;
// 推荐判空用 string.IsNullOrEmpty
if (string.IsNullOrEmpty(s2))
Console.WriteLine("空或 null");
// 还要检查空白:IsNullOrWhiteSpace
string ws = " ";
if (string.IsNullOrWhiteSpace(ws))
Console.WriteLine("空白或 null");
// 字符串长度
Console.WriteLine("Hello".Length); // 5关键点:
- 判空用
string.IsNullOrEmpty(null 或 "")或IsNullOrWhiteSpace(再排除空白)。 - 空字符串用
string.Empty比""更清晰、更省内存。 - 长度用
Length属性(不是方法)。
二、字符串插值(现代首选)
字符串前加 $,花括号嵌入变量或表达式:
// 字符串插值:$ 前缀 + {变量}
string name = "小明";
int age = 20;
Console.WriteLine($"我叫{name},今年{age}岁。");
// 花括号里可写表达式
Console.WriteLine($"明年{age + 1}岁");
Console.WriteLine($"大写:{name.ToUpper()}");
// 格式化:冒号 + 格式字符串
double pi = 3.14159265;
Console.WriteLine($"Pi = {pi:F2}"); // Pi = 3.14
Console.WriteLine($"Pi = {pi:E}"); // 科学计数
DateTime now = DateTime.Now;
Console.WriteLine($"现在:{now:yyyy-MM-dd HH:mm:ss}");
// 多行插值
string html = $@"
<div>
<h1>{name}</h1>
<p>年龄:{age}</p>
</div>";字符串插值是 C# 6 引入的,现在已经全面取代传统的 string.Format 和 + 拼接。可读性碾压。
三、转义与逐字字符串
普通字符串用反斜杠转义;逐字字符串(@ 前缀)则不转义,适合文件路径、正则:
// 转义字符:反斜杠开头
Console.WriteLine("Hello\nWorld"); // \n 换行
Console.WriteLine("A\tB\tC"); // \t 制表符
Console.WriteLine("path: C:\\Users"); // \\ 表示一个反斜杠
Console.WriteLine("引号:\"hello\""); // \" 双引号
// 逐字字符串(@ 前缀):反斜杠不转义,常用于路径正则
string path = @"C:\Users\MyDir\file.txt";
// 不加 @ 的话反斜杠要写两遍
string regex = @"\d{4}-\d{2}-\d{2}"; // 正则更清晰
// 逐字 + 插值($@ 或 @$)
string name = "World";
string greeting = $@"Hello
{name}!";Windows 路径 C:\\Users\\... 强烈推荐用 @"...",避免反斜杠泛滥。
四、字符串的不可变性(重要!)
C# 的 string 是不可变的——创建后内容永不改变。所有"修改"操作都返回新字符串:
string s = "Hello";
string upper = s.ToUpper(); // 新字符串 "HELLO"
Console.WriteLine(s); // 还是 "Hello"(原串没变)
string replaced = s.Replace("l", "L");
Console.WriteLine(s); // 还是 "Hello"
Console.WriteLine(replaced); // "HeLLo"
// 性能陷阱:大量拼接
string sum = "";
for (int i = 0; i < 10000; i++)
sum += i; // 每次创建新字符串,O(n²) 性能差不可变的好处:线程安全、可作为字典 key、避免误改。坏处:频繁修改时性能差,要用 StringBuilder。
五、常用方法
string s = "Hello, World!";
// 信息查询
Console.WriteLine(s.Length); // 13
Console.WriteLine(s.Contains("World")); // True
Console.WriteLine(s.StartsWith("Hello"));// True
Console.WriteLine(s.EndsWith("!")); // True
Console.WriteLine(s.IndexOf(",")); // 5
// 提取/拆分
Console.WriteLine(s.Substring(7)); // "World!"
Console.WriteLine(s.Substring(0, 5)); // "Hello"
string[] parts = "a,b,c".Split(','); // ["a","b","c"]
string[] kv = "name=Tom".Split('=');
// 大小写转换
Console.WriteLine(s.ToUpper()); // "HELLO, WORLD!"
Console.WriteLine(s.ToLower()); // "hello, world!"
// 修改(返回新字符串)
Console.WriteLine(s.Replace(",", "-")); // "Hello- World!"
Console.WriteLine(" hi ".Trim()); // "hi"
Console.WriteLine(" hi ".TrimStart()); // "hi "
Console.WriteLine(s.Remove(5)); // "Hello"方法分类:
- 查询:Length、Contains、StartsWith、EndsWith、IndexOf。
- 提取:Substring、Split、ToCharArray。
- 转换:ToUpper、ToLower、Trim、PadLeft、PadRight。
- 修改:Replace、Remove、Insert(都返回新字符串)。
六、StringBuilder(频繁拼接首选)
大量拼接用 StringBuilder(可变字符串),性能从 O(n²) 提升到 O(n):
// 频繁拼接用 StringBuilder(可变,性能好)
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append("line ").Append(i).AppendLine();
string result = sb.ToString();
// StringBuilder 方法
var sb2 = new StringBuilder();
sb2.Append("Hello");
sb2.AppendLine(); // 加换行
sb2.AppendFormat("Pi = {0:F2}", 3.14);
sb2.Insert(0, "[Start] ");
sb2.Replace("Hello", "Hi");
sb2.Remove(0, 8);
// ❌ 性能陷阱:string 频繁拼接
string s = "";
for (int i = 0; i < 1000; i++)
s += i; // 每次都创建新字符串,O(n²) 复杂度
// ✅ 用 StringBuilder,O(n)判断标准:
- 3 次以内拼接:用
+或字符串插值即可。 - 循环里拼接:用 StringBuilder。
- 构建大文本(如 HTML、JSON):用 StringBuilder。
七、字符串比较与相等
字符串比较有坑——大写小写、文化差异、引用 vs 值。看仔细:
// 字符串相等:== 默认按值比较
string a = "Hello";
string b = "Hello";
Console.WriteLine(a == b); // True(内容相同)
// 字符串驻留:相同字面量共享引用
Console.WriteLine(object.ReferenceEquals(a, b)); // 通常 True
// 显式按值比较
Console.WriteLine(string.Equals(a, b));
// 大小写敏感
Console.WriteLine("Hello" == "hello"); // False
Console.WriteLine(string.Equals("Hello", "hello", StringComparison.OrdinalIgnoreCase)); // True
// 比较(字典序)
int cmp = string.Compare("apple", "banana"); // < 0
Console.WriteLine(cmp < 0); // True,apple 在前
// 推荐的 StringComparison
bool same = string.Equals(
"Hello",
"hello",
StringComparison.OrdinalIgnoreCase
);关键建议:
- 判等用
==或string.Equals。 - 不区分大小写用
StringComparison.OrdinalIgnoreCase。 - 不要用
StringComparison.CurrentCulture做权限/路径比较(文化相关会出 bug)。 - 排序用
string.Compare或 LINQ 的 OrderBy。
八、字符串与字符数组互转
string s = "Hello";
// string → char[]
char[] chars = s.ToCharArray();
// 修改 char[] 不影响原 string
chars[0] = 'X';
Console.WriteLine(s); // 还是 "Hello"
// char[] → string
string s2 = new string(chars);
string s3 = string.Join("", chars);
string s4 = chars.ToString(); // ❌ 这只是类型名,不是内容
// 翻转字符串(经典面试题)
string reversed = new string(s.Reverse().ToArray());九、字符串格式化
数字/日期的格式化常用:
// 数字格式化
Console.WriteLine(12345.ToString("N0")); // "12,345" 千分位
Console.WriteLine(3.14.ToString("F2")); // "3.14"
Console.WriteLine(0.85.ToString("P0")); // "85 %" 百分比
Console.WriteLine(255.ToString("X")); // "FF" 十六进制
// 日期格式化
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("yyyy-MM-dd"));
Console.WriteLine(now.ToString("HH:mm:ss"));
Console.WriteLine(now.ToString("yyyyMMddHHmmss"));
// 在插值里用
double pi = 3.14159;
Console.WriteLine($"Pi = {pi:F2}"); // Pi = 3.14
// 复合格式化(老写法,被插值取代)
string s = string.Format("Pi = {0:F2}", pi);十、字符串驻留与性能
C# 编译器会把相同字面量的字符串"驻留"(intern),共享一个引用:
string a = "Hello";
string b = "Hello";
Console.WriteLine(object.ReferenceEquals(a, b)); // True(同一引用)
// 但运行时拼接的字符串不自动驻留
string c = "Hel" + "lo"; // 编译时常量,可能驻留
string d = "Hel" + DateTime.Now.Day.ToString() + "lo"; // 运行时,新对象
Console.WriteLine(object.ReferenceEquals(a, d)); // 通常 False日常开发不必关心驻留细节,知道"相同字面量共享"即可。需要时可用 string.Intern 显式驻留。
十一、Span<char> 与高性能字符串处理
极致性能场景(解析 JSON、解析协议),用 ReadOnlySpan<char> 切片字符串,无分配:
string s = "Hello, World";
// Substring 创建新字符串(分配内存)
string sub1 = s.Substring(0, 5);
// AsSpan 切片,无分配
ReadOnlySpan<char> sub2 = s.AsSpan(0, 5);
Console.WriteLine(sub2.ToString()); // "Hello"
// 字符串比较也可以用 Span 高效实现
bool eq = s.AsSpan(0, 5).SequenceEqual("Hello".AsSpan());这是 C# 7+ 的高性能特性,日常业务用不到,做底层/性能优化必备。
小结
字符串是日常用得最多的类型。记住几个要点:不可变(改了返回新对象)、插值首选($"...{var}")、大量拼接用 StringBuilder、判空用 IsNullOrEmpty、不区分大小写用 OrdinalIgnoreCase。下一篇进入 C# 的核心——面向对象之类。
← 上一篇 C# 数组
下一篇 C# 类与面向对象 →