Ruby字符串类型
Ruby字符串类型
字符串由String类提供,除了直接使用单双引号或其它字面量创建字符串,也可以使用String.new()方法来创建。
1 | a = "hello" |
Ruby中的字符串是可变对象。
字符串的连接
直接连接即可,空格会被忽略:
1 | "a""b" |
也可以使用+
号进行串联:
1 | "aaa"+"bbb" |
单双引号
这和Perl一样,和Shell也类似。单引号是强引用,双引号是弱引用。
格式化字符串内插
Ruby显然也是支持printf、sprintf的,printf是直接输出格式化后的字符串,sprintf是返回(而不是输出,比如赋值给变量)格式化后的字符串。
此外,Ruby除了表达式或变量内插,还支持%
的格式化字符串内插,等价于sprintf的功能。
1 | sprintf "pi is about %.4f",Math::PI |
正如上面的示例,需要进行格式化的字符使用%
标识,并使用%
连接字符串和待替换的值。如果要内插多个字符串,则值部分使用中括号包围(即放进数组)或放进hash。
%q和%Q和%
这和Perl里的q() qq()
是一样的,也是分别充当单引号、双引号的角色。%q()
被解析成单引号,单个%
或%Q
被解析成双引号。
%q %Q
后面的()
是引号的起始、终止定界符,定界符可以替换成其他成对或相同的符号。例如,下面是等价的:
1 | # 以下等价,内部的单引号不需要反斜线转义 |
如果使用的是成对的定界符,那么在定界符内只要合理的配对,就可以包含定界符字面符号。例如:
1 | %Q(hello(hello world)world) |
单字符
使用一个问号作为下一个字符的前缀,这个字符将成为字符字面量。例如:
1 | ?A # 代表一个大写字母A |
在Ruby1.8及之前的版本,这是一个单字符序列,会转换成ASCII码存放,在Ruby 1.9之后,单字符等价于只有一个字符的字符串。
1 | ?A == 'A' |
Ruby字符串的可变性
对于Ruby来说,字符串是可变的。所以,无法使用单个对象来引用内容相同的两个字符串,如果能引用的话,其中一个修改了就表示另一个字符串也会修改,但这已经表示同一个对象了。
所以,只要Ruby遇到一个字符串字面量,都会新创建一个字符串对象。这意味着,如果在一个循环中使用了字符串常量,那么这个常量字符串对象也会在每次循环过程中新创建,而这是可以避免的消耗性能的一种方式:尽量不要在循环内使用字符串常量。
1 | 10.times {puts "test".object_id} |
(另注:Python中字符串是不可变的,所以for i in range(10):print(id("test"))
会返回10次相同的结果)
+-可变和不可变(frozen)字符串
虽然字符串默认是可变的,但是也可以改变字符串的可变性。
1 | +str → str (mutable) |
+str
表示返回一个可变的字符串对象:
- 如果原始字符串是frozen的,则拷贝一份并返回它的可变对象
- 如果原始字符串本身就是可变的(字符串默认就是可变的),则返回自身,不拷贝字符串对象
-str
表示返回一个不可变(frozen)的字符串对象:
- 如果原始字符串是可变的,则拷贝一份
- 如果原始字符串本身不可变,则返回自身
freeze()
也表示返回不可变字符串对象,它总是在原处修改的。
所以,[+ -]str
可能会创建新对象,而freeze
则总是使得原始字符串不可变。
1 | "world" #=> "world" a= |
扩充字符串
有几种方式:
- (1)两字符串或多字符串直接相连
"abc""def"
或空格相连"abc" "def"
- (2)使用加符号
+
连接两个或多个字符串"abc"+"def"
- (3)使用
*
号重复一个字符串n次"ab" * 3
- (4)使用
<<
将一或多个字符串(能通过to_str转换成字符串的对象均可)追加在原字符串尾部"a" <<"b" <<"b"
- (5)使用
concat()
将一或多个字符串追加在原字符串尾部"a".concat("b","c")
- (6)使用
prepend()
将一或多个字符串插入在原字符串的前面"c".prepend("a","b")
其中(1)到(3)的扩充方式都会返回新的字符串对象,而(4)到(6)的方式都是直接在原处修改字符串对象的。
注意,(1)和(2)扩充方式不会自动将其它类型转换成字符串类型,需要手动调用to_s方法来转换。
1 | "A""B" #=> "AB" |
可使用<<
将多个字符串追加到某个字符串的尾部,它同样不会自动转换成字符串。这时候字符串就像是一个字符数组一样,但需要知道,Ruby字符串不是字符数组,只是实现了一些好用的操作字符串的方法:
1 | "abc" << "hello" <<"world" |
<<
可以直接追加整数,整数被当作ASCII或其它编码字符进行转换,使得追加到字符串里的是字符。
1 | "xyz" << 65 |
只是需要注意的是,使用+
或直接相连扩展字符串的方式时会自动创建一个新的字符串对象,原始字符串不变,也就是说在得到扩展的结果前拷贝了一些数据进行创建新字符串对象。而使用<<
的方式,因为修改的是字符数组,所以是原地修改的。
1 | "xyz" a= |
*
号重复字符串N次。于是,可以简单地写出等长的分割线。例如:
1 | "ab" * 3 |
Ruby字符串的索引属性
字符串可变、可索引子串、设置子串、插入子串、删除子串等等。
字符串[]搜索和赋值
通过[]
可以对字符串进行搜索和赋值,赋值时是原处修改字符串的。索引方式有多种,且支持负数索引号。
1 | # 1.根据索引,搜索或赋值单元素 |
可以说,Ruby对字符串的索引操作支持的是相当的丰富、完善。下面是一些例子:
1 | a = "hello there" |
与索引搜索、索引赋值(包括替换、插入、删除元素)、索引检查元素是否存在等操作有一些相对应的String方法。如下。
include?()
字符串中是否存在某子串。
1 | include? other_str → true or false |
它和STR["str"]
在true/false的布尔判断上是等价的。
例如:
1 | "hello".include? "lo" #=> true |
index()
搜索某子串或匹配的子串的索引位置。
1 | index(substr[, offset]) → int or nil |
offset指定从哪个索引位置处开始搜索。
例如:
1 | "hello".index('e') #=> 1 |
replace()
替换字符串为另一个字符串,它会替换掉全部内容并返回替换后的新字符串。它会原处修改字符串。
1 | replace(other_str) → str |
1 | s = "hello" #=> "hello" |
insert()
在字符串中某个位置处开始插入另一个字符串并返回插入后的字符串。它会原处修改字符串。
1 | insert(index, other_str) → str |
例如:
1 | "abcd".insert(0, 'X') #=> "Xabcd" |
字符串的迭代
因为字符串支持索引操作,所以可以直接通过索引的方式进行迭代。
1 | a="hello" |
可以将字符串split成数组,然后通过each迭代。注意,Ruby 1.9里字符串不能直接通过each迭代了,它不再max-in Enumerable中的each。
1 | a="hello" |
Ruby的字符串类中还定义了4个迭代方式:each_byte, each_char, each_line, each_codepoint
。最常用的,显然是each_char
。
1 | a = "hello" |
each_byte
、each_codepoint
迭代时,得到的是各字符的ASCII码或Unicode代码点。
1 | "我".each_byte.to_a #=> [230, 136, 145] |
按块读取数据(或按段落读取)时,很可能会用上each_line
来迭代缓冲中的一大段数据的每一行。
1 | each_line(separator=$/ [, getline_args]) {|substr| block } → str |
每次迭代,都将该行传递到代码块中。
其中:
separator
指定记录(行)分隔符,默认的是\n
、\r
或\r\n
。
getline_args
指定读取到每一行时的所作的操作,目前支持的是:chomp
选项,表示将每行尾部的换行符移除掉。这是非常方便的功能,这样在迭代每一行的时候,不需要手动在代码块中使用chomp()了。
1 | str="a line\nb line\nc line\n" |
下面是使用chomp
参数的功能。
1 | str="a line\nb line\nc line\n" |
下面是指定行分隔符的用法。
1 | str="a line\nb line\nc line\n" |
Ruby字符串的比较
Ruby中典型的几种方法:<=>、eql?、equal?、==、===
。
1 | string <=> other_string → -1, 0, +1, or nil |
用<=>
比较字符串大小时:
- 左边小于右边,则返回-1
- 左边等于右边(使用
==
做等值比较),则返回0 - 左边大于右边,则返回1
- 两者不可比较(比如一方不是字符串),则返回nil
有了<=>
之后,如果再mix-in Comparable模块,那么就额外具备了<、<=、> 、>=
和between?
方法(买一送N)。
其中,对于纯字符串对象,==、===、eql?
等价,都用于判断字符串内容是否相同。==
是判断对象的内容是否完全一致(或者说是相等);===
是智能判断,对于字符串而言,等价于==
;eql?
是根据计算对象hash值进行比较的,对于字符串的hash()方法来说,它是根据字符串长度、内容、编码三个属性来生成hash值的。
equal?
则最严格,只有双方引用的是同一对象时,才返回true。
说明下eql?
比较。下面是eql?()
在不同编码时的比较过程。
1 | "我".encode("utf-8").eql?( "我".encode("utf-16") ) |
此外,String类还实现了casecmp
和casecmp?
。前者是无视大小写的<=>
,后者是无视大小写的等值比较。编码不同或一方不是字符串时,返回nil。
1 | casecmp(other_str) → -1, 0, +1, or nil |
例如:
1 | "1234abc".casecmp("1234ABC") #=> 0 |
=~正则匹配字符串
1 | str =~ obj → integer or nil |
- 如果obj是一个正则表达式,则用此正则去匹配str,匹配成功则返回匹配到的第一个字符的索引位置,否则返回nil
- 如果obj不是正则表达式,则调用
obj.=~(str)
,即调用obj的=~
方法,然后以str作为参数
注:在Ruby中,str =~ reg 和 reg =~ str是不等价的,如果正则中包含了命名捕获时,只有后者才会设置分组变量,所以此时应该将reg放在符号=~
的左边。为了统一代码规范,一直都如此书写比较好。而在Perl中,reg的位置是在=~符号右边的,这和Ruby正好相反。
1 | "hello" =~ /(?<x>e)/ |