[.NET大牛之路 012] C# 基础:字符串操作

字符串操作在任意编程语言的日常编程中都随处可见,是最基础和最重要的基础知识之一。在 C# 中, System.String 类提供了许多实用的字符串相关的操作,包括查找当前字符串中的子串、大小写转换等。这些方法在使用的时候,大家通过 IDE 的智能提示可以查看完整的列表和它们的使用说明,这里就不展开讲了。我们主要来看看那些无法通过智能提示获取的知识点。

01 逐字字符串

在 C# 的字符串中,反斜杠字符( \)是转义字符,后面跟一个特定的字符,代表一种特定的打印输出,比如 \n 表示插入新行。

但有时候为了书写和阅读方便,我们希望不要把字符串中的 \ 解释为转义符,这时可以使用 C# 的逐字字符串(Verbatim Strings)功能,即在字符串前面加上 @ 符号,表示字符将被编程器按照原义进行解释。逐字字符串主要用于按原义解释转义符和多行文本,例如:

// 逐字字符串:转义符
var filename = @"c:\temp\newfile.txt";
Console.WriteLine(filenaame);

// 逐字字符串:多行文本
var multiLine = @"This is a
multiline paragraph.";
Console.WriteLine(multiLine);

// 非逐字字符串
var escapedFilename = "c:\temp\newfile.txt";
Console.WriteLine(escapedFilename);

输出:

c:\temp\newfile.txt
This is a
multiline paragraph.
c:    emp
ewfile.txt

逐字字符串中唯一不被原样解释的字符是双引号"。由于双引号是定义字符串的关键字符,所以在逐字字符串中要表达双引号需要用双引号进行转义。

varstr = @"""I don't think so"", he said.";
Console.WriteLine(str);
// 输出:"I don't think so", he said.

在逐字字符串中也可以结合 $ 符号实现字符串内插值。

Console.WriteLine($@"Testing \n 1 2 {5 - 2}");
// 输出:Testing \n 1 2 3

02 数字格式化转换

将数字转换为字符串时,可以对其进行格式化。典型的的格式化方法为:

string.Format("{index[:format]}", number)

可使用“0”和“#”占位符进行补位。“0”表示位数不够位数就补充“0”,小数部分如果位数多了则会四舍五入;“#”表示占位,用于辅助“0”进行补位。

标准格式化用法:

// “0”描述:占位符,如果可能,填充位
string.Format("{0:000000}", 1234); // 结果:001234

// “#”描述:占位符,如果可能,填充位
string.Format("{0:######}", 1234);  // 结果:1234
string.Format("{0:#0####}", 1234);  // 结果:01234
string.Format("{0:0#0####}", 1234); // 结果:0001234

// "."描述:小数点
string.Format("{0:000.000}", 1234);       // 结果:1234.000
string.Format("{0:000.000}", 4321.12543); // 结果:4321.125

// ","描述:千分表示
string.Format("{0:0,0}", 1234567);   //结果:1,234,567

// "%"描述:格式化为百分数
string.Format("{0:0%}", 1234);        // 结果:123400%
string.Format("{0:#%}", 1234.125);   // 结果:123413%
string.Format("{0:0.00%}", 1234);     // 结果: 123400.00%
string.Format("{0:#.00%}", 1234.125); // 结果:123412.50%

也可以使用内置快捷字母格式化方式:

// E-科学计数法表示
(25000).ToString("E"); // 结果:2.500000E+004

// C-货币表示,带有逗号分隔符,默认小数点后保留两位,四舍五入
(2.5).ToString("C"); // 结果:¥2.50

// D[length]-十进制数
(25).ToString("D5"); // 结果:00025

// F[precision]-浮点数,保留小数位数(四舍五入)
(25).ToString("F2"); // 结果:25.00

// G[digits]-常规,保留指定位数的有效数字,四舍五入
(2.52).ToString("G2"); // 结果:2.5

// N-带有逗号分隔符,默认小数点后保留两位,四舍五入
(2500000).ToString("N"); // 结果:2,500,000.00

// X-十六进制,非整型将产生格式异常
(255).ToString("X"); // 结果:FF

同样, ToString() 也可以自定义补零格式化:

(15).ToString("000");              // 结果:015
(15).ToString("value is 0");       // 结果:value is 15
(10.456).ToString("0.00");         // 结果:10.46
(10.456).ToString("00");           // 结果:10
(10.456).ToString("value is 0.0"); // 结果:value is 10.5

若要把数字转换为二进制、八进制、十六进制输出,可以使用 Convert.ToString() 方法:

int number = 15;
Convert.ToString(number, 2);  // 结果:1111
Convert.ToString(number, 8);  // 结果:17
Convert.ToString(number, 16); // 结果:f

另外,你还可以自定义格式化器:

public class CustomFormat : IFormatProvider, ICustomFormatter
{
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if (!this.Equals(formatProvider))
        {
            return null;
        }
        if (format == "Reverse")
        {
            return string.Join("", arg.ToString().Reverse());
        }
        return arg.ToString();
    }

    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? this : null;
    }
}

使用自定义格式化器:

String.Format(newCustomFormat(), "-> {0:Reverse} <-", "Hello World");
// 输出:-> dlroW olleH <-

03 字符串拼接

C# 提供了多种字符串拼接的方法。例如,将数组中的字符串使用某字符分隔拼接成一个字符串:

var parts = new[] { "Foo", "Bar", "Fizz", "Buzz"};
var joined = string.Join(", ", parts);
// joined = "Foo, Bar, Fizz, Buzz"

下面列举四种字符串拼接方式,它们都可以达到相同的拼接效果:

string first = "Hello";
string second = "World";
string foo = first + " " + second;
string foo = string.Concat(first, " ", second);
string foo = string.Format("{0} {1}", first, lastname);
string foo = $"{first} {second}";

04 字符串内插法

字符串内插法就是把字符串字面量和变量混在一起,这样既方便编写又方便阅读。简单用法如下:

var name = "World";
var str =$"Hello, {name}!";
// str = "Hello, World!"

字符串内插法还支持格式化。比如日期格式化:

var date = DateTime.Now();
var str = $"Today is {date:yyyy-MM-dd}!";

和补齐格式化(Padding):

var number = 42;

// 向左补齐
var str = $"The answer to life, the universe and everything is {number, 5}.";
// str = "The answer to life, the universe and everything is ___42." ('_'表示空格)

// 向右补齐
var str = $"The answer to life, the universe and everything is ${number, -5}.";
// str = "The answer to life, the universe and everything is 42___."

还有结合内置快捷字母格式化:

var amount = 2.5;
var str = $"It costs {amount:C}";
// str = "¥2.50"

var number = 42;
var str = $"The answer to life, the universe and everything is {number, 5:f1}.";
// str = "The answer to life, the universe and everything is ___42.1"

05 字符串的不可变性质

在你给字符串对象分配了它的初始值之后,字符数据就不能被改变。表面上看,似乎不是这样的,因为你总是可以给字符串变量重新赋值,而且 System.String 类定义很多修改字符串数据的方法,如修改字符串的大小写。但实际上这些方法都是以修改过的格式返回给你一个新的字符串对象。

这一点我们可以通过一个小测试来验证一下,示例如下:

static void Foo()
{
    // 初始字符串数据
    string s1 = "This is my string.";
    Console.WriteLine("s1 = {0}", s1);
    // 将 s1 转换为大写?
    string upperString = s1.ToUpper();
    Console.WriteLine("upperString = {0}", upperString);
    // 看看 s1 变了没
    Console.WriteLine("s1 = {0}", s1);
}

输出结果:

s1 = This is my string.
upperString = THIS IS MY STRING.
s1 = This is my string.

可以看到,虽然在 s1 上调用了 ToUpper() 方法,但 s1 的值还是初始值。为了说明这一点,简化一下代码:

static void Foo()
{
    string s2 = "My string value";
    s2 = "New string value";
}

来看看它背后的 IL 代码:

.method private hidebysig static Foo() cil managed
{
  .maxstack  1
  .locals init (string V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "My string value"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "New string value"
  IL_000c:  stloc.0
  IL_000d:  ldloc.0
  IL_0013:  nop
  IL_0014:  ret
}

注意这里的 ldstr 操作码,它表示加载字符串(把字符串的引用加载到堆栈上)。可以看到, ldstr 操作码在托管堆上加载了一个新的字符串对象,而原来的字符串数据不被引用,将被垃圾回收器回收。

本文来自http://cnblogs.com/willick,经授权后发布,本文观点不代表Chaoqiang's Blog立场,转载请联系原作者。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

近期个人博客正在迁移中,原博客请移步此处,抱歉!