跳至内容

字符串

一个 字符串 代表一个不可变的 UTF-8 字符序列。

字符串通常使用双引号 (") 括起来的 UTF-8 字符创建。

"hello world"

转义

反斜杠在字符串内表示特殊字符,可以是命名转义序列或 Unicode 码点的数字表示。

可用的转义序列

"\""                  # double quote
"\\"                  # backslash
"\#"                  # hash character (to escape interpolation)
"\a"                  # alert
"\b"                  # backspace
"\e"                  # escape
"\f"                  # form feed
"\n"                  # newline
"\r"                  # carriage return
"\t"                  # tab
"\v"                  # vertical tab
"\377"                # octal ASCII character
"\xFF"                # hexadecimal ASCII character
"\uFFFF"              # hexadecimal unicode character
"\u{0}".."\u{10FFFF}" # hexadecimal unicode character

反斜杠后面跟着的任何其他字符都被解释为字符本身。

反斜杠后面跟着最多三个数字(范围从 0 到 7)表示以八进制表示的码点

"\101" # => "A"
"\123" # => "S"
"\12"  # => "\n"
"\1"   # string with one character with code point 1

反斜杠后面跟着一个 u 表示一个 Unicode 码点。它后面可以紧跟着正好四个十六进制字符,表示 Unicode 字节 (\u0000\uFFFF),或者紧跟着一到六个十六进制字符,并用花括号括起来 (\u{0}\u{10FFFF})。

"\u0041"    # => "A"
"\u{41}"    # => "A"
"\u{1F52E}" # => "🔮"

一个花括号可以包含多个 Unicode 字符,每个字符之间用空格隔开。

"\u{48 45 4C 4C 4F}" # => "HELLO"

插值

带插值的字符串字面量允许在字符串中嵌入表达式,这些表达式将在运行时扩展。

a = 1
b = 2
"sum: #{a} + #{b} = #{a + b}" # => "sum: 1 + 2 = 3"

插值也适用于 String#%

任何表达式都可以放在插值部分,但为了可读性,最好保持表达式简短。

可以通过用反斜杠转义井号 (#) 或使用非插值字符串字面量(如 %q())来禁用插值。

"\#{a + b}"  # => "#{a + b}"
%q(#{a + b}) # => "#{a + b}"

插值是通过 String::Builder 实现的,并在每个用 #{...} 括起来的表达式上调用 Object#to_s(IO)。表达式 "sum: #{a} + #{b} = #{a + b}" 等价于

String.build do |io|
  io << "sum: "
  io << a
  io << " + "
  io << b
  io << " = "
  io << a + b
end

百分号字符串字面量

除了双引号字符串,Crystal 还支持由百分号 (%) 和一对分隔符指示的字符串字面量。有效的分隔符是圆括号 ()、方括号 []、花括号 {}、尖括号 <> 和管道 ||。除了管道,所有分隔符都可以嵌套,这意味着字符串内的开始分隔符会转义下一个结束分隔符。

这些在编写包含双引号的字符串时很方便,在双引号字符串中,这些双引号需要转义。

%(hello ("world")) # => "hello (\"world\")"
%[hello ["world"]] # => "hello [\"world\"]"
%{hello {"world"}} # => "hello {\"world\"}"
%<hello <"world">> # => "hello <\"world\">"
%|hello "world"|   # => "hello \"world\""

%q 表示的字面量不应用插值或转义,而 %Q 的含义与 % 相同。

name = "world"
%q(hello \n #{name}) # => "hello \\n \#{name}"
%Q(hello \n #{name}) # => "hello \n world"

百分号字符串数组字面量

除了单个字符串字面量之外,还有一个百分号字面量用于创建 字符串数组。它由 %w 和一对分隔符指示。有效的分隔符与 百分号字符串字面量 相同。

%w(foo bar baz)  # => ["foo", "bar", "baz"]
%w(foo\nbar baz) # => ["foo\\nbar", "baz"]
%w(foo(bar) baz) # => ["foo(bar)", "baz"]

请注意,由 %w 表示的字面量不应用插值或转义,除了空格。由于字符串由单个空格字符 () 隔开,因此必须转义才能将其用作字符串的一部分。

%w(foo\ bar baz) # => ["foo bar", "baz"]

多行字符串

任何字符串字面量都可以跨越多行

"hello
      world" # => "hello\n      world"

请注意,在上面的示例中,尾随空格和前导空格以及换行符最终会出现在结果字符串中。为了避免这种情况,字符串可以用反斜杠连接多个字面量来拆分成多行

"hello " \
"world, " \
"no newlines" # same as "hello world, no newlines"

或者,可以在字符串字面量内插入反斜杠后跟换行符

"hello \
     world, \
     no newlines" # same as "hello world, no newlines"

在这种情况下,前导空格不包括在结果字符串中。

Here文档

Here 文档heredoc 在编写跨越多行的字符串时很有用。Here 文档由 <<- 后跟一个 heredoc 标识符表示,该标识符是一个以字母开头的字母数字序列(并且可以包含下划线)。Here 文档从下一行开始,以下一行结束,该行仅包含 heredoc 标识符,前面可以有空格。

<<-XML
<parent>
  <child />
</parent>
XML

从 heredoc 内容中移除前导空格,数量与 heredoc 标识符之前最后一行中的空格数相同。

<<-STRING # => "Hello\n  world"
  Hello
    world
  STRING

<<-STRING # => "  Hello\n    world"
    Hello
      world
  STRING

在 heredoc 标识符之后,以及在同一行中,任何后续内容都将继续 heredoc 之前出现的原始表达式。就像 heredoc 标识符的末尾是字符串的末尾一样。但是,字符串内容出现在后续行中,直到结束的 heredoc 标识符,该标识符必须位于它自己的行上。

<<-STRING.upcase # => "HELLO"
hello
STRING

def upcase(string)
  string.upcase
end

upcase(<<-STRING) # => "HELLO WORLD"
  Hello World
  STRING

如果多个 heredoc 在同一行开始,它们的正文将按顺序读取

print(<<-FIRST, <<-SECOND) # prints "HelloWorld"
  Hello
  FIRST
  World
  SECOND

Here 文档通常允许插值和转义。

为了表示不带插值或转义的 heredoc,开始的 heredoc 标识符用单引号括起来

<<-'HERE' # => "hello \\n \#{world}"
  hello \n #{world}
  HERE