Ruby很强大,可是相关资料少而不详细。本文是个人学习总结,测试环境是windows xp sp3 + NetBeans6.7.1(JRuby 1.2.0),主要结论来自于互联网、"Programming Ruby"2e、对于源代码的分析和实测代码。
双引号字符串和单引号字符串
都能表示字符串对象,区别在于双引号字符串能够支持更多的转义字符。下面的代码在字符串中增加了'符号。
str=‘he'lo’
puts str
显示结果为he'lo。
单引号仅支持// => / 和 /' => '
下表是ruby中双引号字符串支持的转义字符:
分界符
所有不是字母或者数字的单字节字符都可以成为String的分界符。注意,通常他们都是成对出现的,比如<和>,!和!,{和}等。
构造字符串字面量
方法一:
最简单的使用单引号或者双引号括起来的字符串,比如"hello"。
方法二:
使用%q配合分界符,%q代表单引号
str=%q!he/lo!
方法三:
使用%Q配合分界符,%Q代表双引号
str=%Q{he/lo}
方法四:
here document构建字符串,该方法比较适合用于多行字符串的创建。由<<和边界字符串作为开头,由边界字符串作为结尾,比如下列代码:
str = <<END_OF_STRING1
We are here now,
where are you?
END_OF_STRING1
puts str
输出结果为:
We are here now,
where are you?
较为复杂的是允许多个边界字符串对出现。
str = <<END_OF_STRING1,<<END_OF_STRING2
We are here now,
where are you?
END_OF_STRING1
I will leave now,
would you like to go with me?
END_OF_STRING2
puts str
输出结果为:
We are here now,
where are you?
I will leave now,
would you like to go with me?
字面量与copy-on-write技术
在Java中,如果两个String对象a和b的值都是"abcdef",如下:
String a="abcdef";
String b="abcdef";
那
么,JVM只会创建一个常量对象"abcdef",让a和b都指向它。但是在ruby中,采用了智能指针(熟悉c++的朋友清楚)的一个高级技术
copy-on-write,一开始也是共享同一个字符常量,但是一旦之后某个对象(比如b对象)进行了修改操作,则"abcdef"将产生一个副本,b
的修改操作在这个副本上进行。
更详细的讨论请参考http://developer.51cto.com/art/200811/98630.htm。
和Java的一些其他区别
Java的String每次执行修改操作,都不会改变自身,而是创建一个新的String对象,而Ruby每次的修改操作都会修改自身。
计算长度
puts "hello".length
该句输出5,是字符个数,不要和C函数搞混,C函数经常用0结束字符串,因此长度经常为实际字符个数+1,Ruby中没有这个习惯。
查找
从左向右查找第一个
index方法有三种重载,分别是:
str.index(substring [, offset]) => fixnum or nil
str.index(fixnum [, offset]) => fixnum or nil
str.index(regexp [, offset]) => fixnum or nil
第二个参数offset是可选参数,不用的话则从索引0的字符开始查找。
puts "hello".index("el") 输出为1 ,注意这里的'el'也可以。也可以只查一个字符比,如puts "hello".index(101) 输出为1,这时候第一个参数为'e'的二进制码。
也可以使用正则表达式进行查找,比如puts "hello".index(/[az]/) 输出为nil,因为"hello"不包含a或者z。[]是正则表达式的运算符,代表里面的a和z有一个找到即可。
puts "hello".index(/lo/) 这个没有[]符号,因此是查找子字符串lo,结果为3.
我个人觉得尽量熟练使用正则表达式查找是最好的选择,既可以完成简单查找,也可以完成难度查找。不过需要付出不少努力去学习。
下面这个例子puts "hello".index('o', -1) 证明了第二个参数可以为负数,虽然这没有什么意义,因为功能和为0等价。
如果查找不到,返回nil。
逆向查找(从左向右查找最后一个还是从右向左查找第一个)
str.rindex(substring [, fixnum]) => fixnum or nil
str.rindex(fixnum [, fixnum]) => fixnum or nil
str.rindex(regexp [, fixnum]) => fixnum or nil
第一个参数和index相同,第二个参数是可选,如果不用则默认为字符串尾部。如果为0呢?则从第一个字符开始向右查找。如果为负数呢?这时候很奇怪,居然能查到。通过看C的实现代码,发现当fixnum<0时,会执行这个运算:fixnum+=substring.length,然后就能找到。逻辑上可以理解为当fixnum<0时,将从最右边开始向左移动abs(fixnum)-1个位置,并作为最后查找范围,然后开始从左至右进行查找。字符串最右边的字符的位置被-1代表。
下面两行代码结果都是nil:
puts "hlloe".rindex('e', -2)
puts "hlloe".rindex('e', 3)
下面两行代码结果都是1:
puts "hello".rindex('e', -2)
puts "hello".rindex('e', 3)
注意,以上的代码理解是我个人观察代码后的猜测,因为我还不会调试运行ruby的C代码,所以不一定正确。代码摘录如下:(代码是ruby网站公布的C代码,但是我所用的平台其实NetBeans6.7.1,因此真正代码应该是Java实现的JRuby1.2.0,这里的C代码仅供参考)
static VALUE
rb_str_rindex_m(argc, argv, str)
int argc;
VALUE *argv;
VALUE str;
{
VALUE sub;
VALUE position;
long pos;
if (rb_scan_args(argc, argv, "11", ⊂, &position) == 2) {
pos = NUM2LONG(position);
if (pos < 0) {
pos += RSTRING(str)->len;
if (pos < 0) {
if (TYPE(sub) == T_REGEXP) {
rb_backref_set(Qnil);
}
return Qnil;
}
}
if (pos > RSTRING(str)->len) pos = RSTRING(str)->len;
}
else {
pos = RSTRING(str)->len;
}
switch (TYPE(sub)) {
case T_REGEXP:
if (RREGEXP(sub)->len) {
pos = rb_reg_adjust_startpos(sub, str, pos, 1);
pos = rb_reg_search(sub, str, pos, 1);
}
if (pos >= 0) return LONG2NUM(pos);
break;
case T_STRING:
pos = rb_str_rindex(str, sub, pos);
if (pos >= 0) return LONG2NUM(pos);
break;
case T_FIXNUM:
{
int c = FIX2INT(sub);
unsigned char *p = (unsigned char*)RSTRING(str)->ptr + pos;
unsigned char *pbeg = (unsigned char*)RSTRING(str)->ptr;
if (pos == RSTRING(str)->len) {
if (pos == 0) return Qnil;
--p;
}
while (pbeg <= p) {
if (*p == c) return LONG2NUM((char*)p - RSTRING(str)->ptr);
p--;
}
return Qnil;
}
通常我们理解为从右边开始查找,但是注释却表明是从左向右查找,并返回最后一个找到的目标的位置。究竟内幕如何,只能看代码。
01161 static
long
01162
rb_str_rindex
(str, sub, pos)
01163 VALUE
str, sub;
01164 long
pos;
01165 {
01166 long
len = RSTRING
(sub)->len;
01167 char
*s, *sbeg, *t;
01168
01169
01170 if
(RSTRING
(str)->len < len) return
-1;
01171 if
(RSTRING
(str)->len - pos < len) {
01172 pos = RSTRING
(str)->len - len;
01173 }
01174 sbeg = RSTRING
(str)->ptr;
01175 s = RSTRING
(str)->ptr + pos;
01176 t = RSTRING
(sub)->ptr;
01177 if
(len) {
01178 while
(sbeg <= s) {
01179
if
(
rb_memcmp
(s, t, len) == 0) {
01180
return
s -
RSTRING
(str)->ptr;
01181 }
01182 s--;
01183 }
01184 return
-1;
01185 }
01186 else
{
01187 return
pos;
01188 }
01189 }
通过看代码,发现s--;因此,是从右向左进行匹配,找到的第一个就返回。写注释的人应该枪毙!虽然看上去意思一样,但是算法的时间复杂度大不一样。从左到右的查找总是O(n),而从右到左的最坏事件复杂度才是O(n)。
大小写不区分查找
puts "hello".upcase.index("H"),利用downcase或者upcase全部转换成小写或者大写,然后再查找。
正则表达式匹配查找
operator =~ 将返回匹配的模式开始位置,如果没有找到则返回nil。
puts "abcde789" =~ /d/
输出5.
提取子字符串
str="hello"
puts str[0,2]
第一个参数是子字符串首字母的Index,第二个是长度(不能为负数)。
结果为he。
第一个参数可以为负数,会把最右边的字符作为-1,然后向左增加-1的方式查找起始位置,比如:
str="hello"
puts str[-2,2]
输出为lo,这种情况我们在rindex方法中已经看到过了。
也可以使用正则表达式进行提取,这真的很强大。
str="hello"
puts str[/h..l/]
输出为hell。
符号.代表一个字符,两个.代表两个字符。两个/里面的内容就是正则表达式。.*代表可以有无数个字符,比如
str="hello"
puts str[/h.*o/]
输出为hello。
字符计数
String#count用来计算我们参数中给出的字符集中字符出现的总次数,比如最简单的情况:
str = "hello,world"
puts str.count "w"
“w" 参数代表的是一个字符结合,里面只有一个字符w,count方法计算出w出现在"hello,world"的次数是1,因此输出为1。
下面我们的参数里面包含了三个字符:
str = "hello,world"
puts str.count "wld"
输出为5,w出现1次,l出现3次,d出现1次,正好5次。
也可以传递多个参数,每个参数代表一个字符集合,这时候这些字符集合的交集作为count计算的条件:
str = "hello,world"
puts str.count "lo","o"
输出为2。
str = "hello,world"
puts str.count "lo","o"," "
输出为0,因为三个集合的交集为空,所以计算结果为0.
注意,如果参数^o,代表o出现的次数不计算。
删除末尾分隔符
String#chomp方法有一个字符串参数,指定了要在末尾删除的子字符串。如果不用这个参数,则会将字符串末尾的n,r和rn删除(如果有的话)。
压缩重复字符
String#squeeze方法如果不用参数,则会将字符串中的任何连续重复字符变成单一字符,如下:
str = "helllloo"
puts str.squeeze
输出:helo。
如果传递字符串参数,含义同count方法的参数一样,代表了一个字符集合,则将符合条件(1,在字符集合中出现;2,在字符串中连续出现)的子字符串压缩成的单一字符
实例代码如下:
str = "helllloo"
puts str.squeeze('l')
puts str.squeeze('a-l')
puts str.squeeze('lo')
输出为:
heloo
heloo
helo
参数也可以用a-z方式表示在某个字符集合区间内。
一个很常用的功能是利用squeeze(" ")对字符串内重复的空白字符进行压缩。
字符串删除
delete方法
可以接收多个参数,每个参数代表一个字符集合,类似count方法。如果有多个参数,取交集,然后从字符串中删除所有出现在交集中的字符。
"hello".delete "l","lo" #=> "heo"
"hello".delete "lo" #=> "he"
"hello".delete "aeiou", "^e" #=> "hell"
"hello".delete "ej-m" #=> "ho"
利用sub和gsub
参见后面的sub用法,使用''进行替换即可。
字符串拆分
String#split接收两个参数,第一个参数总是被作为间隔符来拆分字符串,并且不会出现在结果中。
第一个参数如果是正则表达式的话,如果为空,则每个字符都被拆开,返回一个字符数组。例子代码如下:
str = "hello"
puts str.split(//)
输出为:
h
e
l
l
o
如果正则表达式不为空,则根据匹配的情况进行拆分。例子代码如下:
str = "hello"
puts str.split(/h/)
结果为:
ello
拆分成了两个数组,第一个为"",第二个为ello,用h进行拆分的。
第一个参数的另一种用法很简单,只是一个字符串,用于作为间隔符进行拆分,就不举例子了。我更倾向于使用强大的正则表达式。
第二个参数是一个整数,用于对拆分的结果数组的元素个数进行限制,这个功能有多大用处,我现在到没有体会,一般情况下不用即可。
大小写转换
如前面出现的,利用downcase或者upcase方法即可。
数组操作
使用[],里面填上Index,就可以获取第Index个元素。
和数值类型的相互转换
获取单字节字符的二进制码
puts ?e
?运算符用于中文是非法的。
字符串迭代
Ruby迭代器的设计不在这里讨论,我会专门有一篇文章描述。
each_char
迭代每个字符,下面是示例代码:
require 'jcode' #NetBeans6.7.1和JRuby1.2.0需要,否则下面代码找不到方法
"hello".each_char(){ |c| print c,' ' } #()可以不写
|c| 代表字符串中的当前字符。
each
迭代每个子字符串,如果不传递seperator参数,则默认用n作为seperator。
"hellonworld".each { |c| puts c }
输出为:
hello
world
如果传递了有效的字符串作为seperator参数,那么就以这个seperator代替n进行子字符串的迭代:
"hellonworld".each('l') { |s| p s }
输出为:
"hel"
"l"
"onworl"
"d"
each_byte
用法和each_char类似,不过迭代的对象是char,因此输出的是二进制数值。
"hellonworld".each_byte { |s| print s," " }
输出:
104 101 108 108 111 10 119 111 114 108 100
each_line
用法和前面相同,只是用换行符分割子字符串进行迭代:
"hellonworld".each_line do |s|
print s
end
注意,这是另一种写法,用do/end替换了{/}对。
输出为:
hello
world
只所以输出为两行,是因为第一个子字符串是"hellon"输出后自动换行。
字符串拼接
使用operator +操作
str1="hello,"
str2="world"
str3=str1+str2
puts str3
输出为hello,world
使用operator <<操作
str1="hello,"
str2="world"
str1<
puts str1
输出为hello,world
concat方法
concat方法可以在字符串后面加上一个二进制值为[0,255]的字符,用法如下:
str1="hello,world"
str1.concat(33)#33是!的二进制值
puts str1
输出为hello,world!
concat也可以接一个object,比如另一个String对象
是否为空
String#empty? 方法 如果为空返回true,否则返回false
字符串比较
operator<=>操作
str1<=>str2
如果str1小于str2,返回-1;
如果str1等于str2,返回0;
如果str1大于str2,返回1。
官方注释写反了。
operator==操作
两个比较对象必须都为String,否则返回false;
如果都是String对象,则调用operator <=> 操作符进行比较,比较结果为0时,返回true,否则返回false
字符串替换
replace方法
和operator = 功能相同,字符串内容的完全替换,没什么作用。
sub方法
str.sub(pattern, replacement) => new_str
str.sub(pattern) {|match| block } => new_str
在str副本上将找到的第一个匹配字符(串)用replacement替换,并返回。比如:
puts "abcde789".sub(/d/, "000")
输出为:abcde00089
第二种重载形式允许执行一段代码,比如:
puts "abcde789".sub(/d/){|c| 'a'}
找到的字符用|c|表示,可以替换成a字符
输出为:abcdea89
gsub方法
和sub的区别在于所有匹配的地方都会被替换,而不只是第一个。
未完待续,后面会谈到Array#pack和String#unpack以及编码问题。
分享到:
相关推荐
pack模板字符串 sprintf格式 Marshal格式 Ruby FAQ Ruby的陷阱 Ruby/Tk FAQ Ruby的相关书籍 Ruby Documentation Project (RDP) HTML Help版和分立HTML ReFe 索引 功能分类索引 发布条件
表达方式:符号+一串字符,数字串中的下划线会被忽略,(前缀包括:0表示八进制, 0x表示十六进制, 0b表示二进制)123_456_789_123_345_789 # Bignum0xaabb # 十六进制 也可以通过在前面加上问号来得到ASCII码字符...
程序经常需要管理变量集合。例如,管理日历的程序必须有一周的天数列表。...从键盘上读取三个字符串(字符的有序序列)并“pushed”或添加到数组的末尾。 #!/usr/bin/env ruby array = Array.new 3.time
Ruby 中的每个值都是一个对象,即使是最原始的东西:字符串、数字,甚至连 true 和 false 都是对象。类本身也是一个对象,是 Class 类的一个实例。本章将向您讲解所有与 Ruby 面向对象相关的主要功能。 类用于指定...
#Grasp.js 总结 Grasp.js是一个通用的前端实用程序,... Grasp.js使用与ES6中的字符串模板用于插值的语法相同(也与Ruby使用的语法相同)。 创建和呈现模板需要三个步骤。 首先是去关心它 var template = grasp . tem
字符串,字符编码和匹配模式... 101 作为正则表达式的字符串... 101 字符编码... 105 正则模式和匹配模式... 110 常用的元字符和特性... 113 字符表示法... 115 字符组及相关结构... 118 锚点及其他“零长度...
2-5 字符串.mp4 2-4 单线程.mp4 2-3 数据结构和内部编码.mp4 2-2通用命令.mp4 2-11 zset.mp4 2-10 set.mp4 13-1 _课程总结.mp4 12-7 运维功能.mp4 12-6 用户功能.mp4 12-5 应用接入.mp4 12-4 机器部署.mp4...
2.8.5 字符串字面量 48 2.8.6 符号字面量 50 2.8.7 函数字面量 50 2.8.8 元组字面量 50 2.9 Option、Some 和None:避免使用null 52 2.10 封闭类的继承 53 2.11 用文件和名空间组织代码 54 2.12...
字符串 Valid Palindrome Implement strStr() String to Integer (atoi) Add Binary Longest Palindromic Substring Regular Expression Matching Wildcard Matching Longest Common Prefix Valid Number Integer to...
2012-06-11 21:40 60 access连接字符串.txt 2012-06-11 21:08 666 adc-test.c 2012-06-11 21:07 765,000 AS3游戏编程大学.pdf 2012-06-11 21:40 750,563 ATL开发指南源码.rar 2012-06-11 21:05 186,863 BIOS练习工具...