12-字符串(String)
string 是 C# 中最常用的引用类型之一,用于表示文本。理解字符串的不可变性和常用方法是高效处理文本的关键。
一、字符串的创建方式
多种创建方式
csharp
// 1. 直接赋值(最常用)
string str1 = "Hello World";
// 2. 字符数组创建
char[] chars = { 'H', 'e', 'l', 'l', 'o' };
string str2 = new string(chars); // "Hello"
// 3. 重复字符
string str3 = new string('A', 5); // "AAAAA"
// 4. 部分字符数组
string str4 = new string(chars, 1, 3); // "ell"(从索引1取3个)
// 5. 逐字字符串
string path = @"C:\Program Files\App"; // 无需转义反斜杠逐字字符串与转义对比
csharp
// 普通字符串需要转义符
string s1 = "C:\\Program Files\\App"; // 目录路径
string s2 = "他说:\"你好\""; // 包含引号
string s3 = "第一行\n第二行"; // 换行
// 逐字字符串(@)无需转义
string s4 = @"C:\Program Files\App"; // 路径直接写
string s5 = @"他说:""你好"""; // 引号用两个双引号
string s6 = @"第一行
第二行"; // 支持直接换行字符串插值 $""(C# 6.0+)
csharp
string name = "张三";
int age = 25;
decimal salary = 8500.50m;
// 基本插值
string msg = $"我叫{name},今年{age}岁。";
Console.WriteLine(msg); // 输出:我叫张三,今年25岁。
// 插值中的表达式
string calc = $"2 + 3 = {2 + 3}"; // 2 + 3 = 5
string cond = $"成年:{age >= 18}"; // 成年:True
// 插值+格式化
string fmt = $"月薪:{salary:C}"; // 月薪:¥8,500.50
string align = $"|{name,-10}|{age,5}|"; // |张三 | 25|
// 逐字字符串+插值
string path = $@"C:\Users\{name}\Documents";二、字符串的不可变性(核心概念)
字符串一旦创建,内容就不能被改变。 所有"修改"字符串的方法都会返回新字符串,原字符串不变。
csharp
string s = "Hello";
s.ToUpper(); // 返回 "HELLO",但 s 仍然是 "Hello"
Console.WriteLine(s); // 输出:Hello(未改变)
s = s.ToUpper(); // 必须重新赋值才能"改变"
Console.WriteLine(s); // 输出:HELLO不可变性的影响
csharp
// 每次 + 操作都会创建新字符串
string s = "";
s = s + "A"; // 创建 "A"
s = s + "B"; // 创建 "AB"
s = s + "C"; // 创建 "ABC"
// 内存中实际存在 "A"、"AB"、"ABC" 三个字符串
// "A" 和 "AB" 等待垃圾回收
// 大量拼接时性能极差(见 StringBuilder 章节)三、常用字符串方法详解
1. 大小写转换
csharp
string text = "Hello, World!";
Console.WriteLine(text.ToUpper()); // "HELLO, WORLD!"
Console.WriteLine(text.ToLower()); // "hello, world!"
// 区域敏感转换(土耳其语等特殊语言)
string turkish = text.ToUpper(new CultureInfo("tr-TR"));2. 去除空白
csharp
string dirty = " \t Hello \n ";
Console.WriteLine($"---{dirty.Trim()}---"); // ---Hello---
Console.WriteLine($"---{dirty.TrimStart()}---"); // ---Hello \n ---
Console.WriteLine($"---{dirty.TrimEnd()}---"); // --- \t Hello---
// 去除指定字符
string tags = "{{Hello}}";
Console.WriteLine(tags.Trim('{', '}')); // "Hello"3. 截取子串
csharp
string str = "HelloWorld";
Console.WriteLine(str.Substring(5)); // "World"(从索引5到结尾)
Console.WriteLine(str.Substring(0, 5)); // "Hello"(从索引0取5个)4. 查找
csharp
string text = "C# is great. C# is powerful.";
// IndexOf:从前查找(找不到返回 -1)
Console.WriteLine(text.IndexOf("C#")); // 0
Console.WriteLine(text.IndexOf("Java")); // -1
Console.WriteLine(text.IndexOf('i')); // 3
// LastIndexOf:从后查找
Console.WriteLine(text.LastIndexOf("C#")); // 15
// Contains:是否包含
Console.WriteLine(text.Contains("great")); // True
// StartsWith / EndsWith
Console.WriteLine(text.StartsWith("C#")); // True
Console.WriteLine(text.EndsWith(".")); // True
// IndexOfAny:查找字符数组中任意字符首次出现的位置
int pos = text.IndexOfAny(new char[] { '!', '?', '.' }); // 175. 替换
csharp
string text = "Hello, World! Hello, C#!";
string result = text.Replace("Hello", "Hi");
Console.WriteLine(result); // "Hi, World! Hi, C#!"
// 删除字符(替换为空字符串)
string noComma = text.Replace(",", "");
Console.WriteLine(noComma); // "Hello World! Hello C#!"6. 分割和连接
csharp
// Split:分割
string csv = "apple,banana,orange,grape";
string[] fruits = csv.Split(',');
// 结果:["apple", "banana", "orange", "grape"]
// 多个分隔符
string messy = "a|b,c:d";
string[] parts = messy.Split('|', ',', ':');
// 结果:["a", "b", "c", "d"]
// 控制空条目
string data = "a,,b,,c";
string[] withEmpty = data.Split(','); // ["a", "", "b", "", "c"]
string[] noEmpty = data.Split(',', StringSplitOptions.RemoveEmptyEntries); // ["a", "b", "c"]
// Join:连接
string joined = string.Join(" - ", fruits);
Console.WriteLine(joined); // "apple - banana - orange - grape"
// 从数组构建字符串
char[] chars = { 'H', 'e', 'l', 'l', 'o' };
string word = string.Join("", chars); // "Hello"7. 填充
csharp
string num = "5";
Console.WriteLine(num.PadLeft(3, '0')); // "005"
Console.WriteLine(num.PadRight(3, '0')); // "500"
// 对齐输出
string[] names = { "张三", "李四", "王五" };
int[] scores = { 95, 80, 72 };
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine($"{names[i].PadRight(4)}{scores[i]}");
}
// 输出:
// 张三 95
// 李四 80
// 王五 728. 插入和删除
csharp
string hello = "Hello";
string withSpace = hello.Insert(5, " "); // "Hello "
string world = hello.Insert(5, " World"); // "Hello World"
string removed = hello.Remove(2, 3); // "He"(从索引2删除3个字符)四、字符串比较(重要)
C# 提供了多种字符串比较方式,选择正确的方式影响性能和正确性。
csharp
string s1 = "hello";
string s2 = "Hello";
string s3 = "hello";
// 1. == 运算符(区分大小写)
Console.WriteLine(s1 == s2); // False
Console.WriteLine(s1 == s3); // True
// 2. Equals 方法
Console.WriteLine(s1.Equals(s2)); // False
// 3. 忽略大小写比较(推荐)
Console.WriteLine(s1.Equals(s2, StringComparison.OrdinalIgnoreCase)); // True
// 4. Compare 方法(返回 -1, 0, 1)
int result = string.Compare(s1, s2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result); // 0(相等)StringComparison 枚举
| 枚举值 | 说明 | 适用场景 |
|---|---|---|
Ordinal | 二进制比较,区分大小写 | 程序内部标识符比较 |
OrdinalIgnoreCase | 二进制比较,忽略大小写 | 最常用的不区分大小写比较 |
CurrentCulture | 使用当前区域设置 | 用户界面文本排序 |
InvariantCulture | 使用固定区域设置 | 跨文化数据交换 |
csharp
// 推荐做法:用户名比较(不区分大小写)
bool loginSuccess = inputUserName.Equals(storedUserName, StringComparison.OrdinalIgnoreCase);
// 排序时使用区域设置
string[] words = { "apple", "Banana", "cherry" };
Array.Sort(words, StringComparer.CurrentCultureIgnoreCase);五、StringBuilder(可变字符串)
当需要频繁修改字符串时(如循环拼接),使用 StringBuilder 性能远优于 string。
基本用法
csharp
using System.Text;
StringBuilder sb = new StringBuilder();
// 追加
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
Console.WriteLine(sb.ToString()); // Hello World
// 格式化追加
sb.AppendFormat(" 数字:{0}, 日期:{1:yyyy-MM-dd}", 42, DateTime.Now);
// 插入
sb.Insert(5, ","); // Hello, World
// 替换
sb.Replace("World", "C#"); // Hello, C#
// 删除
sb.Remove(5, 1); // Hello C#
// 清空
sb.Clear();
// 链式调用
sb.Append("A").Append("B").Append("C");性能对比演示
csharp
// ❌ 不推荐:字符串拼接,产生大量临时对象
string s = "";
for (int i = 0; i < 10000; i++)
{
s += i.ToString(); // 每次创建新字符串,O(n²) 复杂度
}
// ✅ 推荐:StringBuilder,只在内部缓冲区操作
StringBuilder sb = new StringBuilder(50000); // 预分配容量
for (int i = 0; i < 10000; i++)
{
sb.Append(i);
}
string result = sb.ToString();StringBuilder 重要属性
| 属性 | 说明 |
|---|---|
Capacity | 当前分配的字符容量(默认16) |
Length | 当前字符串长度 |
MaxCapacity | 最大容量(默认 int.MaxValue) |
csharp
StringBuilder sb = new StringBuilder();
Console.WriteLine(sb.Capacity); // 16(默认初始容量)
sb.Append("0123456789ABCDEF"); // 满16个字符
Console.WriteLine(sb.Capacity); // 16
sb.Append("X"); // 超出容量
Console.WriteLine(sb.Capacity); // 32(自动翻倍扩容)
// 事先知道容量可优化
StringBuilder sb2 = new StringBuilder(1000);六、空字符串与 null 处理
三种状态的判断
csharp
string s1 = ""; // Empty:空字符串
string s2 = null; // Null:未分配内存
string s3 = " "; // Whitespace:空白字符串
// 判断空字符串
Console.WriteLine(s1 == ""); // True
Console.WriteLine(s1 == string.Empty); // True(等价)
Console.WriteLine(string.IsNullOrEmpty(s1)); // True
// 判断空白(推荐,同时检查 null、空、空格)
Console.WriteLine(string.IsNullOrWhiteSpace(s3)); // True
Console.WriteLine(string.IsNullOrWhiteSpace(s2)); // True安全访问
csharp
string str = null;
// 危险:直接访问会抛出 NullReferenceException
// int len = str.Length; // 异常!
// 安全方式1:?. 空条件运算符
int? len = str?.Length; // null(不抛异常)
// 安全方式2:?? 空合并运算符
string display = str ?? "默认值";
Console.WriteLine(display); // 输出:默认值
// 组合使用
string name = str?.ToUpper() ?? "UNKNOWN";七、字符串格式化
数字格式化
csharp
double d = 1234.5678;
int n = 255;
Console.WriteLine(d.ToString("F2")); // 1234.57(保留两位小数)
Console.WriteLine(d.ToString("N0")); // 1,235(千分位,无小数)
Console.WriteLine(d.ToString("E2")); // 1.23E+003(科学计数法)
Console.WriteLine(n.ToString("X")); // FF(十六进制)
Console.WriteLine((0.25).ToString("P0")); // 25%(百分比)
Console.WriteLine(d.ToString("C2")); // ¥1,234.57(货币格式)日期格式化
csharp
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("yyyy-MM-dd")); // 2024-10-01
Console.WriteLine(now.ToString("yyyy/MM/dd HH:mm:ss")); // 2024/10/01 14:30:00
Console.WriteLine(now.ToString("yyyy年MM月dd日 dddd")); // 2024年10月01日 星期二
Console.WriteLine(now.ToString("hh:mm tt")); // 02:30 下午
Console.WriteLine(now.ToString("yyyy-MM-ddTHH:mm:ss")); // 2024-10-01T14:30:00(ISO 8601)自定义字符串格式化
csharp
string msg1 = string.Format("姓名:{0},年龄:{1}", "张三", 25);
string msg2 = $"姓名:{"张三"},年龄:{25}";
// 对齐和宽度
Console.WriteLine($"|{"左对齐",-10}|{"右对齐",10}|");
// 输出:|左对齐 | 右对齐|
// 格式化说明
Console.WriteLine($"{Math.PI:F2}"); // 3.14
Console.WriteLine($"{Math.PI,8:F2}"); // " 3.14"(总宽度8,右对齐,保留2位小数)八、String 与 StringBuilder 对比总结
| 对比项 | string | StringBuilder |
|---|---|---|
| 可变性 | 不可变(每次修改创建新对象) | 可变(在缓冲区中操作) |
| 性能(少量操作) | 好 | 略差(有对象创建开销) |
| 性能(大量操作) | 极差 | 好 |
| 线程安全 | 是(不可变天然安全) | 不保证 |
| 适用场景 | 固定文本、少量拼接 | 循环拼接、大量修改 |
选择建议
csharp
// 少量、确定次数的拼接 → string
string fullName = firstName + " " + lastName;
string msg = $"用户:{name},年龄:{age}";
// 大量、循环拼接 → StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append(GetData(i));九、综合案例
案例:文本模板引擎(简化版)
csharp
static string RenderTemplate(string template, Dictionary<string, string> variables)
{
StringBuilder result = new StringBuilder(template);
foreach (var kvp in variables)
{
result.Replace($"{{{{{kvp.Key}}}}}", kvp.Value);
}
return result.ToString();
}
// 使用
string template = "您好,{{Name}}!您的订单 {{OrderId}} 已 {{Status}}。";
var data = new Dictionary<string, string>
{
{ "Name", "张三" },
{ "OrderId", "ORD-2024-0001" },
{ "Status", "发货" }
};
string output = RenderTemplate(template, data);
Console.WriteLine(output);
// 输出:您好,张三!您的订单 ORD-2024-0001 已 发货。案例:CSV 解析器
csharp
static List<Dictionary<string, string>> ParseCSV(string csvContent)
{
var result = new List<Dictionary<string, string>>();
string[] lines = csvContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
if (lines.Length < 2) return result;
string[] headers = lines[0].Trim().Split(',');
for (int i = 1; i < lines.Length; i++)
{
string[] values = lines[i].Trim().Split(',');
var row = new Dictionary<string, string>();
for (int j = 0; j < headers.Length && j < values.Length; j++)
{
row[headers[j].Trim()] = values[j].Trim();
}
result.Add(row);
}
return result;
}
string csv = "姓名,年龄,城市\n张三,25,北京\n李四,30,上海\n王五,28,广州";
var data = ParseCSV(csv);
foreach (var row in data)
Console.WriteLine($"{row["姓名"]} - {row["年龄"]}岁 - {row["城市"]}");核心知识点总结
常用方法速查
| 分类 | 方法 | 用途 |
|---|---|---|
| 大小写 | ToUpper() / ToLower() | 转换大小写 |
| 去除空白 | Trim() / TrimStart() / TrimEnd() | 去除首尾空白 |
| 截取 | Substring(start, len) | 取子串 |
| 查找 | IndexOf() / LastIndexOf() / Contains() | 查找位置或判断包含 |
| 替换 | Replace(old, new) | 替换字符串 |
| 分割 | Split(separator) | 分割为数组 |
| 连接 | string.Join(sep, arr) | 连接数组为字符串 |
| 填充 | PadLeft() / PadRight() | 对齐填充 |
| 判空 | string.IsNullOrEmpty() / IsNullOrWhiteSpace() | 安全判空 |
注意事项
- 字符串是不可变的——每次修改都会创建新对象
- 大量拼接使用
StringBuilder——提升性能 - 比较字符串时注意大小写——使用
StringComparison.OrdinalIgnoreCase忽略大小写 - 使用
$""插值——比+拼接更简洁、可读性更好 - 使用
@""逐字字符串——避免路径和正则表达式中的转义问题 string是引用类型,但表现类似值类型——==比较的是内容而非引用


