字符串

字符串类


  • https://www.kancloud.cn/imxieke/ruby-base/107302


字符串的创建

最简单的字符创建方法就是把字符的集合用 " " 或者 ' ' 括起来并直接写到程序中。

str1 = "这也是字符串"
str2 = ' 那也是字符串'

使用 " " 时还可以执行用 #{} 括起来的 Ruby 式子,并将执行结果嵌入到字符串中。这个 #{} 就称为内嵌表达式(embedded expressions)。

moji = "字符串"
str1 = "那也是#{moji}"
p str1    #=> "那也是字符串"
str2 = ' 那也是#{moji}'
p str2    #=> "那也是\#{moji}"

使用 " " 时,可以显示使用 \转义的特殊字符。

使用 \ 转义的特殊字符

特殊字符 | 意义

  • | - \t | 水平制表符(0x09) \n | 换行符(0x0a) \r | 回车(0x0d) \f | 换页(0x0c) \b | 退格(0x08) \a | 响铃(0x07) \e | 溢出(0x1b) \s | 空格(0x20) \v | 垂直制表符(0x0b) \nnn | 8 进制表示方式(n 为 0~ 7) \Xnn | 16 进制表示方式(n 为 0 ~ 9、a ~ f、A ~ F) \Cx、\C-x | Control + x\M-x Meta(Alt) + x \M-\C-x | Meta(Alt) + Control + x \x | 示 x 字符本身(x 为除以上字符外的字符) \Unnnn | Unicode 字符的 16 进制表示方式(n 为 0 ~ 9、a ~ f、A ~ F)

下面我们来看看不用 " "' ' 时应如何创建字符串。

使用 %Q 与 %q

当创建包含 " 或者 ' 的字符串时,比起使用 \"\' 进行转义,使用 %Q 或者 %q 会更简单。

使用 %Q 相当于用 " " 创建字符串,使用 %q 则相当于用 ' ' 创建字符串。

使用 Here Document

Here Document 是源自于 Unix 的 shell 的一种程序写法,使用 << 来创建字符串。创建包含换行的长字符串时用这个方法是最简单的。

<< 后面的结束标识符可以用 " " 括着的字符串或者用 ' ' 括着的字符串来定义。用 " " 括住的字符串中可以使用转义字符和内嵌表达式,而用 ' ' 括住的字符串则不会做任何特殊处理,只会原封不动地显示。另外,使用既没有 " " 也没有 ' ' 的字符串时,则会被认为是用 " " 创建的字符串。

由于 Here Document 整体就是字符串的字面量,因此可以被赋值给变量,也可以作为方法的参数。

我们一般将 EOF 或者 EOB 作为结束标识符使用。EOF 是“End of File”的简写,EOB 是“End of Block”的简写。

Here Document 的结束标识符一定要在行首。因此,在程序缩进比较深的地方使用 Here Document 的话,有时就会像下面的例子那样,出现整个缩进乱掉的情况。

若希望缩进整齐,可以像下面那样用 <<- 代替 <<。这样程序就会忽略结束标识符前的空格和制表符,结束标识符也就没有必要一定要写在行首了。

这样操作以后,printEOB 的缩进就会变得整齐,便于阅读。下面是将 Here Document 赋值给变量时的做法。

使用 sprintf 方法

就像用 8 进制或 16 进制的字符串来表示数值那样,我们也可以用 sprintf 方法输出某种格式的字符串。

使用 ``

通过用

这样的形式,我们可以得到命令的标准输出并将其转换为字符串对象。下面是获取 Linux 的 ls 命令与 cat 命令的输出内容的例子:

printf 方法与 sprintf 方法

虽然 printf 方法与 sprintf 方法都不是 String 类的方法,但在处理字符串时会经常用到它们。下面我们就来看看它们的用法。

  • 关于 printf 方法

    printf 方法可以按照某种格式输出字符串。例如在输出数值时,有时我们会需要在数值前补零,或者限定小数点显示的位数等,在这些情况下,用 printf 方法都能非常轻松地实现。

    执行结果如下。

    printf 方法的第 1 个参数表示字符串的输出格式。而从第 2 个参数开始,往后的参数都会被依次嵌入到格式中 % 所对应的位置。

    在本例中,第 2 行的 printf 方法被指定为了 %d,这表示输出的字符是整数。

    %d 之间还能插入字符。第 3 行的 printf 方法里插入了 4,这表示按照 4 位整数的格式输出。我们发现,执行结果中出现了 123 这种开头有 1 个空格的情况,这是因为要把 3 位整数以 4 位的格式输出,因此就多输出了 1 个空格。

    在第 4 行中,%d 中间插入了 04,这表示若输出的整数位数不足,整数的开头就会做补零处理。

    第 5 行中指定了 +,这表示输出的结果一定会包含 + 或者 -

    上面是关于数值格式的指定方法,同样,我们也可以指定字符串格式。

    执行结果如下:

    本例的第 2 行程序中指定了 %s,这表示将参数解析为字符串。参数 n 的值为 Ruby,因此输出的字符串为 Hello,Ruby!

    在第 3 行中,%s 之间插入了数字 8,这表示将 Ruby 输出为 8 位字符串。

    在第 4 行中,插入的内容为 -8,这表示按靠左对齐的方式输出 8 位字符串。

  • 关于 sprintf 方法

    printf 方法会把内容输出到控制台,而 sprintf 方法则是把同样的输出内容转换为字符串对象。开头的 s 指的就是 String

获取字符串的长度

我们用 length 方法和 size 方法获取字符串的长度。两者都返回相同的结果,大家根据自己的习惯选用即可。

若是中文字符串,则返回字符数。

如果想获取的不是字符数,而是字节数,可以用 bytesize 方法。

想知道字符串的长度是否为 0 时,可以使用 empty? 方法,该方法常被用于在循环等处理中判断字符串是否为空。

字符串的索引

获取字符串中指定位置的字符,例如获取“开头第 3 位的字符”时,与数组一样,我们也需要用到索引。

字符串的连接

连接字符串有以下两种方法

  • 将两个字符串合并为新的字符串

  • 扩展原有的字符串

    + 创建新的字符串。

    为原有字符串连接其他字符串时,可以使用 << 或者 concat 方法。

    使用 + 也能连接原有字符串。

    + 连接原有字符串的结果会被再次赋值给变量 hello,这与使用 << 的结果是一样的。但用 + 连接后的字符串对象是新创建的,并没有改变原有对象,因此即使有其他变量与 hello 同时指向原来的对象,那些变量的值也不会改变。而另一方面,由于使用 <<concat 方法时会改变原有的对象,因此就会对指向同一对象的其他变量产生影响。虽然一般情况下使用 <<concat 方法会比较有效率,但是我们也应该根据实际情况来选择适当的字符串连接方法。

字符串的比较

我们可以使用 == 或者 != 来判断字符串是否相同。例如在表达式 str1 == str2 中,str1str2 为相同字符串时则返回 true,为不同字符串时则返回 false!=== 表示正相反的意思。

虽然判断字符串是否相同时使用 == 或者 != 会很方便,但判断是否为相似的字符串时,使用正则表达式则会简单得多。

字符串的大小比较

字符串也有大小关系,但字符串的大小关系并不是由字符串的长度决定的。

字符串的大小由字符编码的顺序决定。英文字母按照“ABC”的顺序排列,日语的平假名与片假名按照“あいうえお”的顺序排列,在排列英语或日语的字符串时,就可以使用该顺序。不过,Ruby 中日语的排序规则与字典中的顺序是不同的。例如对“かけ”、“かこ”、“がけ”这 3 个单词排序时,字典中的顺序是“かけ”、“がけ”、“かこ”,而在 Ruby 中,从小到大依次是“かけ”、“かこ”、“がけ”。

中文字符也同样是由字符编码的顺序决定的。例如,在 UTF-8 字符编码表中,“一”的编码为 U+4E00,“丁”的编码为 U+4E01,两个字符的比较结果如下。

字符编码

计算机中的字符都是用数值来管理的,这样的数值也称为编码。

字符与数值的对应关系如下表所示。

字符 | 数值

  • | - A | 65 B | 66 C | 67

我们把上表那样字符与数值一一对应的关系称为字符编码。但字符编码并不是一个正确的专业术语,因此我们需要小心使用。

ASCII 编码是计算机的基础。ASCII 编码是指把英文字母、数值、其他符号、以及换行、制表符这样的特殊字符集合起来,为它们分配 1 到 127 之间的数值,并使之占用 1 个字节空间(1 个字节可以表示 0 到 255)。另外,在欧美,一种名为 ISO-8859-1 的编码曾经被广泛使用,该编码包含了欧洲常用的基本字符(拼写符号、原音变音等),并为它们分配 128 到 255之间的数值。也就是说,大部分的字符都是占用 1 个字节的空间。

使用日语时不可能不使用平假名、片假名或者汉字,但只用一个字节来表示这些字符是不可能的。因此,为了表示这些字符,用两个字节表示一个字符的技术就诞生了。

不过,非常可惜的是日语的字符编码并不只有 1 种,而是大概可以分为以下 4 种编码方式,并且相同编码方式得到的字符也不一定相等。

编码方式 | 主要使用的地方

  • | - Shift_JIS | Windows 文本 UTF-8 | Unix 文本、HTML 等 EUC-JP | Unix 文本 ISO-2022-JP | 电子邮件等

为字符分配与之对应的数值,这样的分配方式就称为字符编码方式(character encoding scheme)。在日本,常用的编码方式有 Shift_JIS、EUC-JP、ISO-2022-JP 这 3 种。它们是日语标准字符编码 JIS X0208 的基础。字符编码的名称一般都直接使用编码方式的名称。例如我们会说“这个文本的字符编码是 EUC,在 Windows 中打开的时候要小心”。

编码方式不同的情况下,即使是相同的字符,对其分配的数值也会不一样。编码方式的不同,就是导致俗称为“乱码”的问题的原因之一

字符 | Shift_JIS | EUC-JP | ISO-2022-JP | UTF-8

  • | - | - | - | - あ | 82A0 | A4A2 | 2422 | E38182

在上表中,あ字符被分配的数值是用 16 进制表示的。据此可以看出,不同的编码方式所对应的数值是不一样的。像这样表示字符的值,我们称之为码位(code point)。在 Ruby 中可以用 String.ord 方法获取字符的码位。

字符串的分割

用特定字符分割字符串时可以使用 split 方法。例如,用冒号(:)分割字符串的程序就可以像下面那样写:

这样一来,分割后的各项字符串就会以数组的形式赋值给 column

换行符的使用方法

each_line 等方法从标准输入读取字符串时,末尾肯定有换行符。然而,在实际处理字符串时,换行符有时候会很碍事。这种情况下,我们就需要删除多余的换行符

效果 | 删除最后一个字符 | 删除换行符

  • | - | - 非破坏性的 | chop | chomp 破坏性的 | chop! | chomp!

chop 方法与 chop! 方法会删除字符串行末的任何字符,chomp 方法与 chomp! 方法则只在行末为换行符时才将其删除。

each_line 方法循环读取新的行时,一般会使用具有破坏性的 chomp! 方法直接删除换行符。

上面是 chomp! 的典型用法。另外,不同的运行环境下,换行符也不同.

字符串的检索与置换

字符串处理一般都离不开检索与置换。Ruby 可以很轻松地处理字符串。

字符串的检索

我们可以用 index 方法或者 rindex 方法,来检查指定的字符串是否存在在某字符串中。

index 方法会从左到右检查字符串中是否存在参数指定的字符串,而 rindex 方法则是按照从右到左的顺序来检查(rindex 的“r”表示的就是 right(右)的意思)。

找到字符串时,index 方法和 rindex 方法会返回字符串首个字符的索引值,没找到时则返回 nil

另外,如果只是想知道字符串中是否有参数指定的字符串,用 include? 方法会更好。

除了直接检索字符串外,Ruby 还可以使用正则表达式来检索。

关于换行符

所谓换行符就是指进行换行的符号。计算机里使用的字符都被分配了 1 个与之对应的数值,同理,换行符也有 1 个与之对应的数值。不过麻烦的是,不同的 OS 对换行符的处理也不同。

下面是常用的 OS 的换行符。这里,LF(LineFeed)的字符为 ,CR(Carriage Return)的字符为 。

OS 种类 | 换行符

  • | - Unix 系列 | LF Windows 系列 | CR + LF Mac OS 9 以前 | CR

Ruby 中的标准换行符为 LF,一般在 IO#each_line 等方法中使用。也就是说,Ruby 在处理 Max OS 9 以前版本的文本时,可能会出现不能正确换行的情况。

我们可以通过参数指定 each_line 方法所使用的换行符,默认为 each_line("\n")

字符串的置换

有时我们可能会需要用其他字符串来替换目标字符串中的某一部分。我们把这样的替换过程称为置换。用 sub 方法与 gsub 方法即可实现字符串的置换。

字符串与数组的共同方法

字符串中有很多与数组共同的方法。

当然,继承了 Object 类的实例的方法,在字符串(String 类的实例)以及数组(Array 类的实例)中也都能使用。除此以外,下面的方法也都能使用。

  • 与索引操作相关的方法

  • Enumerable 模块相关的方法

  • 与连接、反转(reverse)相关的方法

与索引操作相关的方法

字符串也能像数组那样操作索引。数组适用的索引操作方法,也同样适用于字符串。

  • s[n] = str

  • s[n..m] = str

  • s[n, len] = str

    用 str 置换字符串 s 的一部分。这里 n、m、len 都是以字符为单位的。

  • s.slice!(n)

  • s.slice!(n..m)

  • s.slice!(n, len)

    删除字符串 s 的一部分,并返回删除的部分。

返回 Enumerator 对象的方法

在处理字符串的方法中,有以行为单位进行循环处理的 each_line 方法、以字节为单位进行循环处理的 each_byte 方法、以及以字符为单位进行循环处理的 each_char 方法。调用这些方法时若不带块,则会直接返回 Enumerator 对象,因此,通过使用这些方法,我们就可以像下面的例子那样使用 Enumerable 模块的方法了。

Enumerator 类

虽然 Enumerable 模块定义了很多方便的方法,但是作为模块中其他方法的基础,将遍历元素的方法限定为 each 方法,这一点有些不太灵活。

String 对象有 each_byteeach_lineeach_char 等用于循环的方法,如果这些方法都能使用 each_with_indexcollectEnumerable 模块的方法的话,那就方便多了。而 Enumerator 类就是为了解决这个问题而诞生的。

Enumerator 类能以 each 方法以外的方法为基础,执行 Enumerable 模块定义的方法。使用 Enumerator 类后,我们就可以用 String.each_line 方法替代 each 方法,从而来执行 Enumerable 模块的方法了。

另外,不带块的情况下,大部分 Ruby 原生的迭代器在调用时都会返回 Enumerator 对象。因此,我们就可以对 each_lineeach_byte 等方法的返回结果继续使用 map 等方法。

与连接、反转(reverse)相关的方法

除了与 Enumerable 模块、索引等相关的方法外,字符串中还有一些与数组共同的方法。

  • s.concat(s2)

  • s+s2

    与数组一样,字符串也能使用 concat 方法和 + 连接字符串。

  • s.delete(str)

  • s.delete!(str)

    从字符串 s 中删除字符串 str。

  • s.reverse

  • s.reverse!

    反转字符串 s。

其他方法

  • s.strip

  • s.strip!

这是删除字符串 s 开头和末尾的空白字符的方法。在不需要字符串开头和末尾的空白时,用这个方法非常方便。

  • s.upcase

  • s.upcase!

  • s.downcase

  • s.downcase!

  • s.swapcase

  • s.swapcase!

  • s.capitalize

  • s.capitalize!

    所谓 case 在这里就是指英文字母的大、小写字母的意思。~case 方法就是转换字母大小写的方法。

    upcase 方法会将小写字母转换为大写,大写字母保持不变。

    downcase 方法则刚好相反,将大写字母转换小写。

    swapcase 方法会将大写字母转换为小写,将小写字母转换为大写。

    capitalize 方法会将首字母转换为大写,将其余的字母转换为小写。

  • s.tr

  • s.tr!

    源自于 Unix 的 tr 命令的方法,用于置换字符。

    该方法与 gsub 方法有点相似,不同点在于 tr 方法可以像 s.tr("a-z", "A-Z") 这样一次置换多个字符。

    相反,tr 方法不能使用正则表达式,也不能指定两个字符以上的字符串。

日语字符编码的转换

字符编码转换有两种方法,分别是使用 encode 方法和使用 nkf 库的方法。

encode 方法

encode 方法是 Ruby 中基本的字符编码转换的方法。将字符编码由 EUC-JP 转换为 UTF-8,程序可以像下面这样写:

另外,Ruby 中还定义了具有破坏性的 encode! 方法。

encode 方法支持的字符编码,可通过 Encoding.name_list 方法获得。

nkf 库

使用 encode 方法可以进行字符编码的转换,但却不能进行半角假名与全角假名之间的转换。全半角假名的转换我们需要使用 nkf 库。

nkf 库由 NKF 模块提供。NKF 模块是 Unix 的 nkf(Network Kanji code conversion Filter)过滤命令在 Ruby 中的实现。

NKF 模块用类似于命令行选项的字符串指定字符编码等。

nkf 的主要参数

选项 | 意义

  • | - -d | 从换行符中删除 CR -c | 往换行符中添加 CR -x | 不把半角假名转换为全角假名 -m0 | 抑制 MIME 处理 -h1 | 把片假名转换为平假名 -h2 | 把平假名转换为片假名 -h3 | 互换平假名与片假名 -Z0 | 把 JIS X 0208 的数字转换为 ASCII -Z1 | 加上 -Z0,把全角空格转换为半角空格 -Z2 | 加上 -Z0,把全角空格转换为两个半角空格 -e | 输出的字符编码为 EUC-JP -s | 输出的字符编码为 Shift-JIS -j | 输出的字符编码为 ISO-2022-JP -w | 输出的字符编码为 UTF-8(无 BOM) -w8 | 输出的字符编码为 UTF-8(有 BOM) -w80 | 输出的字符编码为 UTF-8(无 BOM) -w16 | 输出的字符编码为 UTF-16(Big Endian/ 无 BOM) -w16B | 输出的字符编码为 UTF-16(Big Endian/ 有 BOM) -w16B0 | 输出的字符编码为 UTF-16(Big Endian/ 无 BOM) -w16L | 输出的字符编码为 UTF-16(Little Endian/ 有 BOM) -w16L0 | 输出的字符编码为 UTF-16(Little Endian/ 无 BOM) -E | 输入字符编码为 EUC-JP-S 输入字符编码为 Shift-JIS-J 输入字符编码为 ISO-2022-JP -W | 输入的字符编码为 UTF-8(无 BOM) -W8 | 输入的字符编码为 UTF-8(有 BOM) -W80 | 输入的字符编码为 UTF-8(无 BOM) -W16 | 输入的字符编码为 UTF-16(Big Endian/ 无 BOM) -W16B | 输入的字符编码为 UTF-16(Big Endian/ 有 BOM) -W16B0 | 输入的字符编码为 UTF-16(Big Endian/ 无 BOM) -W16L | 输入的字符编码为 UTF-16(Little Endian/ 有 BOM) -W16L0 | 输入的字符编码为 UTF-16(Little Endian/ 无 BOM)

为了避免半角假名转换为全角假名,或者因电子邮件的特殊字符处理而产生问题,如果只是单纯对字符进行编码转换的话,一般使用选项 -x-m0(可以合并书写 -xm0)就足够了。

下面是把 EUC-JP 字符串转换为 UTF-8 的例子:

不指定输入字符编码时,nkf 库会自动判断其编码,基本上都可以按如下方式书写:

NKF 模块在 Ruby 字符串还未支持 encoding 功能以前就已经开始被使用了。大家可能会感觉选项的指定方法等与现在 Ruby 的风格有点格格不入,这是因为 nkf 库把其他命令的功能硬搬了过来的缘故。如果不涉及一些太特殊的处理,一般使用 encode 就足够了。