| 赞 | 0 |
| VIP | |
| 好人卡 | |
| 积分 | 1 |
| 经验 | |
| 最后登录 | 2025-2-24 |
| 在线时间 | 552 小时 |
- 梦石
- 0
- 星屑
- 122
- 在线时间
- 552 小时
- 注册时间
- 2012-8-18
- 回帖
- 1392
|
发表于 2014-7-6 12:08:22
|
显示全部楼层
本帖最后由 无脑之人 于 2014-7-15 00:35 编辑
操作符(Operator)
可能会有一些人觉得ruby的操作符是很简单并且也很方便的东西,其实呢,这里面是有很多坑点的哦!
【当然其实不用作死的写法基本上不会被坑】
[fold=第一章 基本的操作符与操作符优先级]
Ruby里面主要有以下几个操作符:【按照优先级由高到低的顺序】
【注:unary代表一元操作符,binary代表二元操作符,这里面没写的都默认binary,除了?:是唯一的三元操作符(ternary operator)】
【一元操作符代表结合一个操作数,举例来说就像是-a中的-只结合了a一个操作数,而a-b中的-结合了a,b两个操作数,他就是二元操作符,我们用unary-和binary-来区分这两个操作符——他们是截然不同的】
- ::(作用域操作符)
- [] []=(下标操作符)
- unary+ !(否定) ~(按位取反操作符)
- **(幂操作符)
- unary-
- * / %(求模操作符,取余数)
- + -
- << >>(移位操作符)
- &(按位与操作符)
- |(按位或操作符) ^(按位异或操作符)
- > >= < <=(比较操作符)
- <=> == === != =~ !~
- &&(逻辑与操作符)
- ||(逻辑或操作符)
- .. ...(范围操作符)
- ?:(三目操作符)
- =(+=, -= ... )
- not
- and or
-
复制代码 【说真的unary*和unary&的优先级我一直没找着,貌似很低】
运算符优先级的意思是,对于一个对象,它结合运算符的方式有两种,比如:那么按照运算符优先级的顺序来结合,如果是同优先级,要看他们是左结合优先还是右结合优先,这一点不同运算符都是不同的,比如+ - * /是左结合,而**则是右结合
如果你想知道操作符真正的顺序,就请用实践来探寻他
然后我们说一下这里面不是很好理解的几个运算符:
1. ::(作用域操作符)
这里的作用域操作符一般是指用来从命名空间(namespace)里面取出常量(constant),命名空间通俗一点讲就是用Class和Module来实现的,下面的代码就创建了一个命名空间:
- class Namespace1
- end
- module Namespace2
- end
-
复制代码 那么常量是什么呢,常量就是大写字母开头的那些看起来像变量的东西,其中也包括Class和Module哟!
- class Namespace1
- Constant1 = 10
- class Constant2
- end
- module Constant3
- class Constant233
- Constant9 = 9
- end
- end
- end
- p Namespace1::Constant1 #=>10
- Namespace1::Constant2.new
- p Namespace1::Constant3::Constant233::Constant9 #=> 9
-
复制代码 我相信读者看到这里应该已经明白了吧=w=【实际上是不知道怎么说下去了】
2.位运算的操作符
包括~ & | ^ << >>,本来打算讲一下,结果觉得工程太大,所以还是留给讲位运算的人吧XD
3.逻辑与,逻辑或,三目操作符
首先来说三目操作符,?:相当于一个if构成的表达式:
- a ? b : c
- #等同于
- if a
- b
- else
- c
- end
-
复制代码 是不是很好理解呢?三目操作符用好了可以减少代码量,但是用得不好也许会让整个代码难以理解
三目操作符是短路运算,a一定会计算,如果返回b就不会计算c,就这样
https://rpg.blue/thread-335582-1-1.html 像这个帖子的LZ就是个2B青年
【C/C++党注意】ruby中的三目操作符仅能用做求值,不可以放在等号左边
然后是逻辑与和逻辑或,其实说他们是短路运算怎么怎么样什么的都很麻烦,一句代码概括就是了:
- a && b #=> a ? b : a
- a || b #=> a ? a : b
-
复制代码 这样一来,它短路运算和返回值的规则就可以鲜明的表现出来了,不是么?我相信读者都看懂了吧=w=
【这次是每个人都注意】对于&&和||来说,虽然有优先级的顺序,但是优先级顺序(甚至是括号)代表结合顺序不代表求值顺序:
- a,b,c = 0,0,0
- 1 || (a+=1) #a+=1根本不会计算
- (a+=1) || (b+=1) && (c+=1) #b和c毫无影响
-
复制代码 这一点不仅对于ruby,对于C/C++等大多数语言都是适用的
4.下标操作符
参见上面的数组
5.<=>操作符
这个操作符的目的呢,大概是用来用一个函数表示所有的比较的,在重载部分咱会详细讲到,请大家不要着急XD
6.===操作符
它一般代表“包括”的意思,可以用来判断在不在Range里面,用来判断是不是这个类的对象什么的,如果没有这些特殊定义,那么等同于==
参见Range的相关内容
case语句使用===进行判断7.=~/!~操作符
参见上面的正则与字符串
8. .. ...操作符
参见Range的相关内容
9.=(+=, -= ...) 赋值操作符和复合赋值操作符
这是一个重头戏233
首先,赋值操作符都有着这样的规定,他的返回值是等号的右边【注意:与C/C++以及其他乱七八糟的可以操作符重载的基本上都不一样】
即使是复合赋值操作符以及xxx=这样的writer也是一样——不要妄图给writer方法提供返回值,他什么作用也没有
- a = b = c = 1 #=>1
- a += 99 #=> 100 【怎么回事呢?FlagA】
- class W
- def www=(x)
- return 1
- end
- def www
- return 999
- end
- end
- c = W.new #=> W.new这个对象
- c.www = 10 #=>10 而不是 1
- c.www += 100 #=>1099 【诶?FlagB】
复制代码 上面的代码生动形象的说明了这些问题,但是为什么这两个Flag是不对的呢?
首先我们从复合赋值操作符的规则说起,所有的a @= b【@代表操作符】都等同于 a = a @ b
如 a+=1 等同于 a = a + 1
但是有两个例外:- a &&= 1 #=> a && (a=1)
- a ||= 9 #=> a || (a=9)
复制代码 所以我们可以用||=来给变量赋初值
那么,说好的返回值是等号右边呢?Bingo!复合赋值操作符的返回值是展开形式的等号右边:- a += 99 #=> 100
- a = a + 99 #=> 199
- c.www += 100 #=>1099
- c.www= c.www + 100 #=>1099
复制代码 这就是Ruby中赋值操作符的坑点所在啊!
然后咱举出一些操作符容易忽视的问题:
1.幂操作符是右结合
2.算数取正和取负的优先级是不同的- +a**2 #=> (+a)**2
- -a**2 #=> -(a**2)
复制代码 3.逻辑运算符具有短路运算的性质,但按位运算符是没有的!
4.不要妄图使用a==b==c,他不仅没有意义,还是一个可以被ruby检查出来的错误【CompileError】
5.一般来讲,!开头的操作符都是其原来的操作符否定后的结果,如!=是==的否定,!~是=~的否定
6.三目操作符也是右结合的【卧槽我之前都没发现他还能出现别的结合情况】:- a ? b : c ? d : e #等同于 a ? b : (c ? d : e)
复制代码 7.rescue的优先级在三目操作符和赋值操作符之间
8.and or not是&& || !的另一种写法,不过他们的优先级很低
9.if/unless/while/until修饰符在and not or优先级的后面
[/fold]
[fold=第二章 操作符重载(Operator Overload)]
【不管怎么看这一章的名字都好厉害的样子【泥垢
其实这个高端的标题想说的就是,我们可以让我们的类,也具有使用操作符的能力哦
首先,让我们回忆一下,之前某位大触讲的数组里面的使用下标操作符:
- class WArray
- def initialize
- @data = []
- end
- def [](index)
- @data[index]
- end
- def []=(index, result)
- @data[index] = result
- end
- end
- a = WArray.new
- a[10] = 9
- p a[10] #=> 9
复制代码 嘛,实际上对于大多数操作符来说,重载的方法都是一样的,那就是
- #@代表操作符,args代表参数
- def @(args)
- #...
- end
复制代码 然后我们或许需要讨论一下参数的问题——我们应该需要几个参数呢?
那么,实际上有这样的一个规则:
对于一元操作符,他作为方法被重载不需要参数,对于二元操作符,他的重载需要一个参数,对于三元操作符——2333,三目操作符重载不了
那么哪些可以重载,哪些不可以捏~?
对于下列这些,是可以重载的:
- [] []= unary+ !(这个操作符的重载,需要Ruby1.9,VA是可以的啦w) ~ ** unary- * / % + - << >> & | ^
- < <= > >= == =~ != !~(这两个操作符的重载同样需要Ruby1.9不过他们不推荐进行重载,!=与==的语义不保持一致的话往往会出现混乱) <=> ===
- unary* unary& (这两个是什么东西捏?我们下一章就谈)
复制代码 然后是几个需要注意的点:
1.对于unary+和unary-难道我们要?当然不是!Ruby对一元+一元-的重载进行了特殊规定,使用+@和-@:2.对于下标操作符来说,可能我们会认为他只能接受一个下标【在C/C++里面就是这样】,但是Ruby中,他们的下标个数是不定的哦~
- class WArray
- def [](a,b,c,d,e,f,g,h)
- p a,b,c,d,e,f,g,h
- end
- def []=(a,b,c,d,e,f,g)
- p a,b,c,d,e,f,g
- end
- end
- a = WArray.new
- a[1,2,3,4,5,6,7,8] #=> 1,2,3,4,5,6,7,8
- a[1,2,3,4,5,6] = 7 #=> 1,2,3,4,5,6,7 对于下标输入,等号后面的值一定作为最后一个参数
- class MArray
- def [](*m)
- p m
- end
- def []=(*m, s)
- p m,s
- end
- end
- a = MArray.new
- a[1,2,3,4,5] #=> [1,2,3,4,5]
- a[1,5,6] #=> [1,5,6]
- a[9,9,9,9] = 100 #=> [9,9,9,9], 100
- 对于Ruby1.9以前的比如说XP党,由于不支持*m,s这种语法,我们可以妥协一下:
- class XPArray
- def []=(*m)
- s = m.pop
- p m,s
- end
- end
- a = XPArray.new
- a[1,2,3,4,5,6] = 7 #=>[1,2,3,4,5,6], 7
复制代码 3.对于unary*和unary&
Yohoho~这是下一章的内容啦,所以敬请期待咯~☆
[/fold]
[fold=第三章 神奇的unary*与unary&]
Hi~大家好~☆很可能这是我最后一篇教程啦,暑假里面出了很多事情,所以还希望读者大人不要丢鸡蛋……啊呀
这次讲的是两个神奇的一元操作符——*和&
现讲&好了,工程比较小:
unary&的用途是与块紧密相关的,比如说在传递参数的时候,我们可以用&block来接受参数,但这与这个操作符有什么关系呢?
首先我们来看下面的代码:
- def mmm(&block)
- block.call(100)
- end
- mmm {|val| p val} #=> 100
-
复制代码 从上面的代码可以看出,在这里&是将递交给方法的块进行了捕获。
紧接着我们再看这样的代码:
- def mmm(&block)
- block.call(1,2)
- end
- def plus(a,b)
- a + b
- end
- pro = proc {|m,n| m + n}
- mmm &:plus #=> 3
- mmm &pro #=> 3
-
复制代码 从这段代码可以看出,我们似乎能用&将一个方法(用Symbol表示)或者一个过程(Proc)转化为块
我相信我这么说肯定不是很好理解,下面才是重头戏:
【声明:以下均为层主自己脑补的,如果有失误还请指正】
在Ruby中,对于这种无论是叫函数,还是叫过程,还是叫块,他们都是这样的两种形式——块和对象
块,是在方法中挂上的,{-}或者do-end那种东西,他们不是对象,不能拿正常的ruby操作去操作他们
而我们可以将块转化为对象,这样他就符合Ruby中对象的各种要求,也就可以拿来进行操作了
因此,我们需要一个媒介,将块和对象之间转化——也就是&
我们对一个对象调用&,将会得到一个块,我们在方法的参数列表中用&,就可以把块捕获成对象
当然Ruby为我们内置了proc和lambda方法,可以方便的将块转化为对象
Ruby的中的方法对象,同样也是这种对象的一员
那么,我们为什么可以&:plus呢?难道Symbol也是这种对象么?不
Symbol,他重载了unary&,在这个重载操作符方法内,他将自己转化为了相应的方法对象,这样&就可以将它转化
我们也可以用同样的方法,重载unary&操作符(他的重载方法是to_proc):
- class A
- def to_proc
- proc {|i,j| i + j}
- end
- end
- a = A.new
- def mmm
- yield 1,2
- end
- mmm &a
-
复制代码 好了,unary&到此结束【我是不是一不小心领了block参数那一节 @taroxd
紧接着是unary*,他的用途更加广泛而灵活
*是伴随着数组而出现的,我们同样可以在参数列表中看见他也可以在并行赋值【好像还没讲,我如果有机会会开了这个坑】中看见他- a,b,*c = 1,2,3,4,5,6
- p c #=> [3,4,5,6]
复制代码 那么,她是怎样的呢?【高能脑补注意!】
实际上,他和unary&一样,也具有两种形式——展开式和数组
展开式就是像a,b,c,d这样用逗号分隔的表达式
- a = [1,2,3,4]
- p(*a) #=> 1 2 3 4
- p(1,2,3,4) #=> 1 2 3 4
- #别忘了p是一个方法调用
- *m = 1,2,3,4
- a,b,c,d = 1,2,3,4
- p(*m) #=> 1 2 3 4
- p m #=> [1,2,3,4]
- p a,b,c,d #=> 1 2 3 4
- n = []
- n[0],n[1],n[2],n[3] = 1,2,3,4
- p n #=> [1,2,3,4]
- def aaa(*m)
- p m
- end
- aaa(1,2,3,4) #=> [1,2,3,4]
- def ccc
- yield 1,2,3,4
- end
- ccc {|*c| p c } #=> [1,2,3,4]
-
复制代码 *同样也是展开式与数组之间的媒介,相信读者大人看懂了吧?【是因为快要去学校了懒得解释了
对于不是数组的对象,默认是返回本身我们可以重载它,用to_a:
- class M
- def to_a
- [1,2,3,4]
- end
- end
- m = M.new
- p(*m) #=> 1 2 3 4
-
复制代码 不过,对于Ruby的某一些版本【记不起来是哪些了】,禁止了数组展开与其他展开式连接:
- m = 1,2,3,4
- p(*m) #=> 1 2 3 4
- p(5,6,7,8) #=> 5 6 7 8
- p(*m,5,6,7,8) #=>1 2 3 4 5 6 7 8 #某些版本不允许
-
复制代码 好了就到这里了,还剩一章,关于操作符的小实践
[/fold]
以下内容不看也罢
[fold=最终章 操作符的应用]
以上讲了操作符的各种使用方法,现在我们就来将理论付诸于实际
1.用于将自制的数字类模仿原Numeric的使用
在这里作为示范,我们写一个虚数类【不知道虚数是啥的可以查查wiki,不过更建议先好好学习,嗯我又开地图炮了】
[pre lang="ruby" line="1"]module NOB
class Complex
def initialize(a=0,b=0) #此处不理解见咱的参数教程
@real = a
@virtual = b
end
attr_accessor :real, :virtual #此处不理解详见taroxd触的属性教程
def conjugate #共轭复数
Complex.new(@real, -@virtual)
end
def size #模
Math.hypot(@real, @virtual)
end
def square
@real ** 2 + @virtual ** 2
end
def to_s
((@real != 0) ? @real.to_s : "") + ((@real != 0 && @virtual != 0) ? "+" : "") +
((@virtual != 0) ? @virtual.to_s + "i" : "")
end
alias inspect to_s #这句和上一个无视就好
end
end[/pre]
最基本的骨架就是这样了喵【我会在注释中说明为什么不继承Numeric类】
这个时候,他作为一个像Numeric的类,应该具有基本的操作符支持——最起码复数也要可以运算呐OAO
我们先来想一想都要怎么运算
圈1:我们要有加减法!既然我们要兼容各种各样的数值类型【整数,浮点数,以及刚做的复数】,那么我们需要对传来的参数的类型进行判断
[pre lang="ruby" line="1"]module NOB
class Complex
def initialize(a=0,b=0)
@real = a
@virtual = b
end
attr_accessor :real, :virtual
def conjugate
Complex.new(@real, -@virtual)
end
def size #模
Math.hypot(@real, @virtual)
end
def square
@real ** 2 + @virtual ** 2
end
def to_s
((@real != 0) ? @real.to_s : "") + ((@real != 0 && @virtual != 0) ? "+" : "") +
((@virtual != 0) ? @virtual.to_s + "i" : "")
end
alias inspect to_s
def +(other)
case other
when Numeric
Complex.new(@real + other, @virtual)
when Complex
Complex.new(@real + other.real, @virtual + other.virtual)
else
raise TypeError,"Invalid argument (#{} to Numeric,Complex)" #这句无视就好
end
end
def -(other)
self + (-other) #总是满足这样的效果
end
end
end[/pre]
圈2:你看上面的代码能运行吗?不能!因为什么?我们需要unary+和unary-啊!自己取负当然是需要支持的咯
[pre lang="ruby" line="1"]module NOB
class Complex
def initialize(a=0,b=0) #此处不理解见咱的参数教程
@real = a
@virtual = b
end
attr_accessor :real, :virtual #此处不理解详见taroxd触的属性教程
def conjugate #共轭复数
Complex.new(@real, -@virtual)
end
def size #模
Math.hypot(@real, @virtual)
end
def square
@real ** 2 + @virtual ** 2
end
def to_s
((@real != 0) ? @real.to_s : "") + ((@real != 0 && @virtual != 0) ? "+" : "") +
((@virtual != 0) ? @virtual.to_s + "i" : "")
end
alias inspect to_s
def +(other)
case other
when Numeric
Complex.new(@real + other, @virtual)
when Complex
Complex.new(@real + other.real, @virtual + other.virtual)
else
raise TypeError,"Invalid argument (#{} to Numeric,Complex)"
end
end
def -(other)
self + (-other) #总是满足这样的效果
end
def +@
self
#这里也可以选择 Complex.new(@real, @virtual),这样更加符合语义
#不过我懒得这样做
end
def -@
Complex.new(-@real, -@virtual)
end
end
end[/pre]
圈3:我们该增加乘除法了!为了避免整数除法为我们带来的坑点,我们统一进行浮点除法
[pre lang="ruby" line="1"]module NOB
class Complex
def initialize(a=0,b=0) #此处不理解见咱的参数教程
@real = a
@virtual = b
end
attr_accessor :real, :virtual #此处不理解详见taroxd触的属性教程
def conjugate #共轭复数
Complex.new(@real, -@virtual)
end
def size #模
Math.hypot(@real, @virtual)
end
def square
@real ** 2 + @virtual ** 2
end
def to_s
((@real != 0) ? @real.to_s : "") + ((@real != 0 && @virtual != 0) ? "+" : "") +
((@virtual != 0) ? @virtual.to_s + "i" : "")
end
alias inspect to_s
def +(other)
case other
when Numeric
Complex.new(@real + other, @virtual)
when Complex
Complex.new(@real + other.real, @virtual + other.virtual)
else
raise TypeError,"Invalid argument (#{} to Numeric,Complex)"
end
end
def -(other)
self + (-other) #总是满足这样的效果
end
def +@
self
#这里也可以选择 Complex.new(@real, @virtual),这样更加符合语义
#不过我懒得这样做
end
def -@
Complex.new(-@real, -@virtual)
end
def *(other)
case other
when Numeric
Complex.new(@real * other, @virtual * other)
when Complex
Complex.new(@real * other.real - @virtual * other.virtual,
@real * other.virtual + @virtual * other.real)
else
raise TypeError,"Invalid argument (#{} to Numeric,Complex)"
end
end
def /(other)
self * other.conjugate / other.square
#分母实数化,我也懒得用什么高端算法了……【其实是不会用吧
end
end
end[/pre]
圈4:这个概括所有需要填的了……卧槽累死我了……不管了就当有这些操作符
圈5:我们需要影响整数类和浮点类的+-*/以便于支持Complex【以Float的+为例,真是累残了不想打了……
[pre lang="ruby" line="1"]class Float
alias :plus :+
def +(other)
case other
when NOB::Complex
other + self
else
plus(other)
end
end
end[/pre]
总之呢,完成这样一个数学上的类,对操作符要熟练掌握哦
2.对于容器和(伪)输入输出流
其实是模仿C++,小朋友们不要学我:
假设我们写了一个红黑树【真是丧心病狂
[pre lang="ruby" line="1"]class RBT
def initialize
#红黑树你去死吧我艹你大爷%¥%&%@#¥%¥#%*&%……
end
def xxx
#各种实现
end
def push(item)
#添加一个元素
end
end[/pre]
然后这个时候我们可以模仿Array类的<<方法:
[pre lang="ruby" line="1"]class RBT
def initialize
#红黑树你去死吧我艹你大爷%¥%&%@#¥%¥#%*&%……
end
def xxx
#各种实现
end
def push(item)
#添加一个元素
end
alias :<< :push
end[/pre]
这样我们就可以[pre lang="ruby" line="1"]a = RBT.new
a << 1
a << 999[/pre]
然后是伪输入输出流
[pre lang="ruby" line="1"]class RubyOut
class HelperProc
def initialize(&block)
@BLock = block
end
def call(rubyout)
@block.call(rubyout)
end
end
HEX = HelperProc.new {|out| %w{我懒得做了...} }
def initialize
#我们需要做什么吗?好像不需要
end
def <<(item)
print(item)
end
end[/pre]
我们同样可以模拟一下这个cout嗯……卧槽谁打我
3.新操作符的hack
听说C++有++运算符,Ruby没有
但是呢,今天我们就来看看,Ruby的++运算符!
【当然不要抱太大希望,这个伪++运算符真的是太水了
我们只能hack前置++运算符,使用的是unary+哦~
不过很抱歉不可能应用于Numeric了——他们修改不了自己
我们做一个Numeric的wrapper好了:
[pre lang="ruby" line="1"]class NumWrapper
def initialize(num = 0)
@num = num
@Flag = 0
end
def +@()
if @flag == 1
@num += 1
@flag = 0
else
@flag = 1
end
self
end
def -@()
if @flag == 2
@num -= 1
@flag = 0
else
@flag = 2
end
self
end
def method_missing(sym, *args)
if @flag == 2
@flag = 0
(-@num).send(sym,*args)
else
@flag = 0
@num.send(sym,*args)
end
end
def to_i
a = @flag == 2 ? -@num : @num
@flag = 0
a
end
def to_s
to_i.to_s
end
alias :inspect :to_s
#还有很多需要做的……哎呀懒得做了啦
end[/pre]
即便是这样一个小小的hack,还是非常麻烦,并且还有很多应该地方因为没有拆包无法代替Numeric
[/fold]
结语:操作符是一个有趣的东西,善于利用会让你的代码更加可读更加有效,使用不当则会令你永生难忘【笑
希望读者合理利用操作符,让自己的代码发光出彩 |
评分
-
查看全部评分
|