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 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"

方法分类:

六、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)

判断标准:

七、字符串比较与相等

字符串比较有——大写小写、文化差异、引用 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 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# 类与面向对象

✈️💬