Skip to content

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[] { '!', '?', '.' });  // 17

5. 替换

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
// 王五  72

8. 插入和删除

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 对比总结

对比项stringStringBuilder
可变性不可变(每次修改创建新对象)可变(在缓冲区中操作)
性能(少量操作)略差(有对象创建开销)
性能(大量操作)极差
线程安全是(不可变天然安全)不保证
适用场景固定文本、少量拼接循环拼接、大量修改

选择建议

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()安全判空

注意事项

  1. 字符串是不可变的——每次修改都会创建新对象
  2. 大量拼接使用 StringBuilder——提升性能
  3. 比较字符串时注意大小写——使用 StringComparison.OrdinalIgnoreCase 忽略大小写
  4. 使用 $"" 插值——比 + 拼接更简洁、可读性更好
  5. 使用 @"" 逐字字符串——避免路径和正则表达式中的转义问题
  6. string 是引用类型,但表现类似值类型——== 比较的是内容而非引用

Released under the MIT License.