1月10日 ruby基础教程,查漏补缺; 2月22日 Exception补充

https://ruby-doc.org/core-2.5.0/Exception.html

1月20日练习完1,2章。 

 第一章 初探

‘’单引号不执行转义符。

\t 制表符。\n 换行符。

p mehtod ,类似于puts,但转义符不起效果,另外会对数字和string以不同形式输出。

2.3.1 :011 > p 100
100
 => 100
2.3.1 :012 > p "100"
"100"
 => "100"

数学运算符,如果是sin,cos,平方根。需要在函数前加Math. 

例子:Math.sqrt(100)  =>10.0 

9.2653e-05表示(小数) * 10的(整数)次幂 


 变量名 = 对象 这个过程称为将对象赋值给变量,变量相当于给对象贴上的标签。

例子:area = 100. area表示area这个变量引用的对象。 


注释:

单行 #

=begin..=end  


 

迭代器:iterator, 就是iterate循环的容器or.

times方法,each方法都是迭代器。 



第二章  Object

array,hash, regular expression.

 

hash:的三种写法 

 

  1. hash = {:name => "Tom", :address => "xxx"} 这是符号的写法,可以省略为{name:"Tom",...}
  2. hash = {"name" => "Tom", ...}  这是string的写法
  3. hash = {1 => "xxx", 2 => "yyy"}  这是数值的写法。

 

 

re的匹配正则表达式与字符串的写法: 

/pattern/ =~ "string" 

/pattern/i =~ "string"   #忽略大小写


2.3.1 :033 > names = ["小林","王明","陈"]

 

 => ["小林", "王明", "陈"]
2.3.1 :034 > names.each do |name|
2.3.1 :035 >     if /陈/ =~ name
2.3.1 :036?>     puts name
2.3.1 :037?>     end
2.3.1 :038?>   end
 => ["小林", "王明", "陈"]

 

 


 

 第三章创建命令

 

  name = ARGV[0]  #类似于gets ,共同点是得到的都是string。

 

  代码:print File.read(ARGV[0])  

  #输入ruby read_text_oneline.rb 文件名(带后缀) 

 

each_line 方法: 用于对文件进行逐行处理,或逐个处理数组元素。用在大文件时,减少占用内存的空间。

 例子:

pattern = Regexp.new(ARGV[0])    #Regular expression正则表达式
filename = ARGV[1]
file = File.open(filename)
file.each_line do |line|
  if pattern =~ line    #查找 =~
    print line
  end
end
file.close

 


引用library库的方法:require 

调用其他文件内的自定义方法def..end ,或者Ruby提供的标准库

require 希望使用的库名

require_relative 希望使用的库名   #根据执行中的程序目录(文件夹)来进行。

案例:use_grep.rb 和 grep.rb 

 


 

 


第四章 object,variable,constant

 

  类          对象     解释

Numeric   数值     整数,浮点,矩阵,复数,公式

String      字符串

Array

Hash

Regexp    正则表达式对象

File          文件      对文件进行读写操作的对象

Symbol    符号   

Range      范围对象

expection 异常对象 

Data        时间对象 


 变量:

local variable   英文小写 或 _开头 

global variable  $开头,不常用

instance variable  @开头, 实例变量

class variable       @@开头  类变量


Constant 常量 :大写字母开头

如果改变constant的值,Ruby会发出⚠️, 但还是会改变该值。


保留字:不能使用作为变量或常量的单词,Ruby已经占用了。会报告❌。 

 begin, end, alias ,and ,BEGIN ,END, break  case ,class ,def ,defined? ,do ,else ,elsif ,ensure, false ,for, if, in, module, next, nil, not, or, redo, rescue, retry, return, self, super, then, true, undef, unless, until, when, while, yield. 


多重赋值: 

> a, b, c = 1,2,3
 => [1, 2, 3]
> a
 => 1
> a, b,*c = 1,2,3,4,5     # 变量前加*,表示ruby将未分配的值封装为数组赋值给该变量。
 => [1, 2, 3, 4, 5]
> c
 => [3, 4, 5]
> b
 => 2
> p [a, b, c]
[1, 2, [3, 4, 5]]
 => [1, 2, [3, 4, 5]]

 多重赋值的方便之处: 交换变量的值,无需再通过临时变量temp.直接一行搞定!

a,  b = 1, 2

a,  b = b, a

p [a,b]  #=> [2, 1] 

 



第五章 条件判断

 if,  unless, case.

 unless 条件为假 then  处理  end

 unless 条件

  处理1

 else

处理2

 end 

 

case:比较对象只有一个的时候,根据对象值的不同,执行不同处理结果 。when可以指定多个值。

case 比较对象

  when 值1.. then

处理1

  when 值2.. then

处理2

  when 值3.. then

处理3

end


case value

  when a

..

  when b

..

  else

..

end 

 等同于, ===是更广泛的相等。可以用于正则表达式,判断右边的对象是否属于左边的类,等等。

if a === value

..

elsif b === value

..

else

..

end 


对象的同一性:

所以对象都有ID和value. 对象的ID可以用object_id方法来获得。 

判断两个对数是否有为同一个对象,用equal?方法 


 


 

第六章  循环

 1.循环的目的,做什么?

 2.停止循环的条件是什么?

 

循环语句: whlie, for, until

循环方法: times, each, loop

 for 就是 each的语法糖,都是从对象中抽取元素时使用。


循环的次数.times do |i|

      希望循环的处理

end 

 


for 变量 in 对象 do

    希望循环的处理

end 

或者 

for variable in 开始的数..结束的数 do

    希望循环的处理

end 

 实际上,数值范围,也是对象范围。例子:

names = ["awk", "perl", "python", "ruby"]
for name in names do
  puts name
end

 


while语法 

 sum = 0

 i = 1 

while i < 5 do

     sum = sum + i

     i += 1 

end 

和for语句的程序有2个差别:

第一:条件的指定方式不一样,while用了比较运算符号

第二,i的累加方式不一样,for是自动累加.


 Until语法

也有♻️,和while相反,否定条件下,进行循环。 

sum = 0
i = 1
until sum >= 50 do
  sum += i
  i += 1
  puts sum
end

 在Ruby中,for语句是用each方法来实现的。因此,可以使用each方法的对象,同样也可以指定为for语句的循环对象。

names = ["awk", "perl", "python", "ruby"]
for name in names do
  puts name
end
#等同
names.each do |name|
  puts name
end
#等同
names.each {|name| puts name}

 

 


 

Loop方法 

不限制循环次数 

loop do

  print "ruby"

  break

end 


♻️控制 

break:跳出循环。(终止整个循环程序)

next: 下一个循环(遇到next忽略本次循环的后面的语句,直接开始循环程序的下一次循环)

redo: 在相同条件下重复刚才的处理 


#这是strip.rb 

file = File.open(ARGV[0])

file.each_line do |line|
  next if /^\s*$/ =~ line   #空行
  next if /^#/ =~ line       #以 #开头的行
  puts line
end
file.close

 

#这是hello.rb 

#Hello, World
puts "Hello World"
#日元
puts "日元"
#人民币
puts "人民币"


#当输入 ruby strip.rb hello.rb 后返回:

puts "Hello World"
puts "日元"
puts "人民币"

 

 

总结:任何种类的循环都可以用while实现,但为了便于读写和理解ruby提供了其他语句和方法。 




 

第七章 Method 

 7.1 object.method(parmeter1,par2,...)

object又被称为receiver,在面向对象的世界,调用方法被称为“向object发送messages”,调用的结果就是“Object received messages.”。简单来说,方法的调用就是把几个参数连同消息一起发送给Object的过程。 

 

7.12带块的方法调用

Object.Method(参数,...) do |变量1,变量2...|

    block 内容

end 

 

7.13运算符形式的方法调用

obj =~ arg1

!obj

-obj

obj[arg1]

obj[arg1] = arg2   #给对象赋值

 

7.2M e h t o d 分类 

  • Instance method
  • Class method
  • Function method
7.21 instance method

> p "10, 20, 33".split(",") #=> ["10", " 20", " 33"]

> p  ["a",12,"b"].index(12)          #=> 1  Return the index of the first object in []

 

 

7.22  Class method

Array.new

File.open("some_file")   #创建新的文件对象

Time.now                     #创建新的Time对象 

 

7.23 函数式方法function method

没有接受者的方法。

print "hello"

sleep(10) 

 

7.3 define method 

def hello(name="默认参数")     #方法名(参数1, 参数2...)

  puts "hello, #{name}"    #希望执行的处理

end

hello(要传入的参数) 

 

 return 返回方法的值,省略的话,Method最后一个表达式的结果就会成为方法的返回值。

 Ruby有很多返回值为nil的方法,如print 

 

7.32 定义带块的方法 

 

def myloop
  while true
    yield #yield 生产:执行块
  end
end

 

num = 1
myloop do
  break if num > 10      #类似于loop方法,遇到break终止循环
  puts "num is #{num}"
  num = num * 2
end

 

7.33 参数个数不确定的方法 

def foo(*args)

  args

end 

p foo(1,2,3)     #=> [1,2,3] 

 

7.34 关键字参数

 将参数名和值成对的传给method内部使用

>def area2(x:0,y:0,z:0)
>   xy = x * y
>   yz = y * z
>   zx = z * x
>   (xy+yz+zx)*2
>end                         
> p area2(x:2, y:2, z:3)
=> 32

可以用hash传递参数。

arg1 = {x:2, y:3, z:4}

p area2(arg1)    #=> 52 

 


 


 

 第八章, class and model

 

model模块是Ruby独有的概念。

类表示对象的种类。是对象的雏形或设计图。

ary = Array.new  #或者用 ary = []

p ary      #=> []

str = "Hello World"

p ary.class    #=> Array

p str.class #=> String  class方法返回变量的类型。 

p ary.instance_of?(Array)  #=> true  instanc_of?() 方法判断对象是否是括号内的类的实例。等同is_a?

 

8.12 inheritance 

通过扩展已经定义的类来创建新类,继承。subclass, superclass.

1.追加新功能

2.重新定义原有功能

3.扩展原有功能

BaseObject是Ruby中所有类的superclass父类。 

 

8.2 create class 

 

class HelloWorld   # class语句
 def initialize(myname = "Ruby") # initialize方法,生成新的对象时候用到
 @name = myname   # 初始化实例变量
 end
 def hello   # 实例方法
 puts "Hello, world. I am #{@name}."
 end
end
bob = HelloWorld.new("Bob")
alice = HelloWorld.new("Alice")
ruby = HelloWorld.new
bob.hello
注意⚠️ :实例化对象的代码,不能在类内部写!

实例变量@name,只要他的实例对象存在,它就存在。而local variable 只在方法被调用时候存在。

 

8.24 attr_accessor  Method 存取器

在Ruby中,从对象外部不能直接访问实例变量或对实例变量赋值,需要通过Method来访问object的内部。 


class HelloWorld
...
 attr_accessor :name #等同于下面两个method
 def name # 获取@name
 @name
 end
 def name=(value) # 修改@name
 @name = value
 end
...
end
bob = HelloWorld.new("Bob")
p bob.name  #读实例变量name
bob.name = "Robert"  #改写实例变量name 
p bob.name

 

 

8.25 self 

在实例method中,可以用self这个特殊变量来引用方法的接受者object.

class HelloWorld
attr_accessor :name
...
def greet
puts "Hi, I am #{self.name}." # 这个是调用对象自身的name方法
end
def hello

puts "Hello, world. I am #{@name}" #输出实例变量@name

end

end

 

 

8.26 class method

一共有4种写法 ,定义类方法


 

以定义实例方法的形式来定义类方法: (单例类定义)

class << HelloWorld

  def hello

    puts "xxx"

  end 

end 

HelloWorld.hello #=> "xxx"


 在类中嵌套: 使用class << self ~ end

class HelloWorld

  class << self

      def hello

   puts "xxx"

      end 

  end 

end 

HelloWorld.hello #=> "xxx"

 


 

 用 def class_name.mehtod_name  ~ end

def HelloWorld.hello

    puts "xxx"

end 

HelloWorld.hello #=> "xxx"


使用self

def self.hello

    puts "xxx"

end 

HelloWorld.hello #=> "xxx"


 

8.27常量constant

在类中定义的常量,通过 class_name::constant_name来冲外部访问。 

class HelloWorld

  Version = "1.0"

  ...

end

p HelloWorld::Version    #=> "1.0" 


 

8.28 class variable 

 

同常量一样所以object实例可以共享,不过可以修改 变量的值。

和instance variable一样,从外部访问需要用到 accessor method (存取器或访问器),但:

attr_accessor不支持@@variable,所以需要自定义存取方法。 

 

class HelloCount
@@count = 0 # 调用hello方法的次数
def HelloCount.count # 读取调用次数的类方法,另一种写法self.count
@@count
end
def initialize(myname="Ruby")
@name = myname
end
def hello
@@count += 1 # 累加调用次数
puts "Hello, world. I am #{@name}.\n"
end
end
bob = HelloCount.new("Bob")
alice = HelloCount.new("Alice")
ruby = HelloCount.new
p HelloCount.count #=> 0
bob.hello
alice.hello
ruby.hello
p HelloCount.count #=> 3


 

 8.29 限制方法的调用

public:以实例方法的形式向外部公开该方法。

protected:不能从实例外部访问,但在同一类中可以将该方法作为实例方法调用。

private : 无法从实例外部访问。(最严格)

 

class AccTest
def pub
puts "pub is a public method."
end
public :pub # 把pub方法设定为public(可省略)
def priv
puts "priv is a private method."
end
private :priv # 把priv方法设定为private
end
acc = AccTest.new
acc.pub
acc.priv
结果:
pub is a public method

acc_test.rb:15:in `<main>': private method `priv' called for #<AccTest:0x007fc6a4807218> (NoMethodError)


class Point
attr_accessor :x, :y # 定义存取器
protected :x=, :y= # 把x=与y=设定为protected
  #如果设为private :x=, :y= 则方法swap(other)也不管用了。
def initialize(x=0.0, y=0.0)
@x, @y = x, y
end
def swap(other) # 置换x、y值的方法
tmp_x, tmp_y = @x, @y
@x, @y = other.x, other.y
other.x, other.y = tmp_x, tmp_y # 在同一个类中可以被调用
return self
end
end
p0 = Point.new
p1 = Point.new(1.0, 2.0)
p [ p0.x, p0.y ] #=> [0.0, 0.0]
p [ p1.x, p1.y ] #=> [1.0, 2.0]
p0.swap(p1)
p [ p0.x, p0.y ] #=> [1.0, 2.0]
p [ p1.x, p1.y ] #=> [0.0, 0.0]
p0.x = 10.0 #=> 错误(NoMethodError)

 


 

8.3扩展class

 

8.31在原有class的基础上 添加新的方法

8.32 继承类

class subclass < superclass 

  定义

end 

class RingArray < Array # 指定父类
def [](i) # 重定义运算符[]
idx = i % size # 计算新索引值
super(idx) # 调用父类中同名的方法
end
end
wday = RingArray["日", "月", "火", "水", "木", "金", "土"]
#定义数组可以省略.new wday = RingArray.new(["日", "月", "火", "水", "木", "金", "土"])
p wday[6] #=> "土" p wday[11] #=> "木" p wday[15] #=> "月" p wday[-1] #=> "土"

p wday.[](6) #等同于p wday[6], 因为string 和 array经常用,所以有简写的写法.


8.41 alias

 设置别名 alias 别名 原名

class C1 # 定义C1
def hello # 定义hello
"Hello"
end
end
class C2 < C1 # 定义继承了C1 的子类C2
alias old_hello hello # 设定别名old_hello
def hello # 重定义hello
"#{old_hello}, again"
end
end
obj = C2.new
p obj.old_hello #=> "Hello"
p obj.hello #=> "Hello, again 


如果: 在子类中有,和父类相同的方法名,并没有设置别名,调用的时候,用子类的方法。

class C1
  def hello
    "Hello"
  end
end
class C2 < C1
  # alias old_hello hello
  def hello
    "sadf, again"
  end
end
obj = C2.new
p obj.hello     #=> "sadf, again"

 

8.42 undef 用于删除已定义的方法。

例如:在子类中删除父类定义的方法。

class C1
  def hello
    "Hello"
  end
end
class C2 < C1
  undef hello
end
obj = C2.new
p obj.hello   #=>undefined method `hello'

 

8.5单例类

Ruby基础教程100页

 class 类名 << ~ end 这种写法定义称为单例类定义,里面的方法称为单例方法。


8.6模块

 模块是事物的行为部分。和类有2点不同。

 

  •  模块不能拥有实例
  •  模块不能被继承

 

 

8.7使用方法2个

 

8.71利用Min-in扩展功能

 

Min-in用于解决2个问题:

  • Ruby不支持父类的多重继承,因此无法对已经继承的类添加共通的功能
  • 虽然两个类拥有相似的功能,但不希望把它们视作相同的class来考虑 

单一继承的优点是简单,不会因为多重继承而导致类之间的关系变得复杂。但有时,我们希望更灵活的重用已有的类,或者把多个类的特性合并为更高级的类,在这种情况下,灵活运用 单一继承 和 Mix-in,既能使类结构简单易懂,又能灵活地应对各种需求。

module MyModule
# 希望提供的共通方法等
end
class MyClass1
include MyModule
# MyClass1中特有的方法等
end
class MyClass2
include MyModule
# MyClass2中特有的方法等
end

 

8.72提供namespace 

 module_function:模块函数。用 模块名.方法名调用在模块中定义的方法。

例子:

p FileTest.exist?("/usr/bin/ruby")  #=> true

p Math.sqrt(2) #=> 1.414...

p Math::PI #=> 3.1415926..  ⚠️ PI是常量,所以用:: 

 


 

8.8创建模块module 

module HelloModule # module关键字
 Version = "1.0"
 def hello(name)  
 puts "Hello, #{name}."
end
module_function :hello # 指定hello方法为模块函数 
end  # 如果不指定为模块函数,则不能公开给外部使用,即不能通过模块名调用	
p HelloModule::Version #=> "1.0" 模块中的常量,也通过模块名访问。
HelloModule.hello("Alice") #=> Hello, Alice. 🈯️定为模块函数后,可以用模块名调用
include HelloModule # 包含模块
p Version #=> "1.0" ⚠️ :因为没有定义与模块内方法,常量相同的名称,引用时可省略模块名。
hello("Alice") #=> Hello, Alice.

 

8.9 Mix-in

通过Mix-in,既可以保持单一继承的关系,又可以同时让多个类共享功能。

module M
 def meth
 "meth"
 end
end
class C
 include M # 包含M模块
end
c = C.new
p c.meth #=> meth

p C.include?(M) #=> true

p C.ancestors #=> [C, M, Object, Kernel, BasicObject]

p C.superclass #=> Object


8.91 使用Mix-in时方法的查找顺序。

1. 同继承关系一样,原类已经定义了同名的方法,优先使用原类中的方法。

2. 在同一类中定义了多个模块时,优先使用最后一个包含的模块。

3.嵌套includes时,查找顺序也是线性的,此时的关系:

module M1
end

 

module M2
end

 

module M3
  include M2
end

 

class C
  include M1
  include M3
end
p C.ancestors  #=> [C, M3, M2, M1, Object, Kernel, BasicObject]

4.相同的模块被包含两次以上时,第二次会被省略。


8.92 extend方法 和单例方法

 

# class 类名 << ~ end 这种写法定义称为单例类定义,里面的方法称为单例方法。

 

str1 = "Ruby"
str2 = "Ruby"
class << str1
  def hello         #=> 单例方法(属于 类方法)
    "Hello,#{self}!"
  end
end
p str1.hello    #=> "Hello,Ruby!"
p str2.hello    #=> 错误String(NoMethodError)

 

#Object#extend方法,可以批量定义单例方法。
#extend可以跨过类,帮助开发者直接通过模块扩展对象的功能

 

module Edition
  def edition(n)
    "#{self}第#{n}版"
  end
end
str3 = "Ruby基础教程"
str4 = "Ruby基础教程"
str3.extend(Edition)
str4.extend(Edition)
p str3.edition(4)
p str4.edition(5)

备注:在Ruby 中,所以方法的执行,都需要作为接受者的某个object的调用。从这个角度说,开发者只是为了便于识别接受者object的类型,才分别使用了“实例方法”和“类方法”这样的说法。 

 

 


 

 8.10面向对象的程序设计

 

什么是对象? 🈯️ 数据集合及操作该数据的方法的组合 。

 

8。102 面向对象的特性

封装:

🈯️ 对象管理的数据不能直接从外部操作,数据的更新,查询都必须通过调用对象的方法完成。

 

  1. 防止把非法数据设置给对象,产生异常
  2. 可以隐藏对象内部处理数据的具体细节。

 

多态:

不同的对象有同名的方法 ,但处理的结果各不一样的现象。例子:

obj = Object.new
str = "Ruby"
num = Math::PI
p obj.to_s   #=> "#<Object:0x007fb3c5807bb0>"
p str.to_s   #=> "Ruby"
p num.to_s   #=> "3.141592653589793"

 

虽然方法名一样,但调用的是各自类专用的版本。

 

 

8.103  Duck typing

 能像鸭子一样走路,能像鸭子一样叫的,肯定是鸭子--古谚语。

 古老智慧: 根据动物/人的行为来判断它是什么动物或是什么样的人。

 对象:对象的特征不是由其种类(类及继承关系)决定的,而是由它本身具有什么样的行为(method)决定的。 

 

def fetch_and_downcase(ary, index)
  if str = ary[index]
    return str.upcase
  end
end
ary = ["Boo", "Foo", "Woo"]
p fetch_and_downcase(ary, 1)
hash = {0 => "Boo", 1 => "Foo", 2 => "Woo"}
p fetch_and_downcase(hash, 1)

 

Ruby中的变量不需要先声明类型。不限制类型。 

只要能执行相同的操作,我们并不介意执行者是否一样;相反,虽然是不同的执行者,但通过定义相同名称的方法,也可以实现处理通用化。 这就是鸭子类型的思考方法。 

 

8.104面向对象的例子。 

 如何自然的写程序,需要丰富的程序设计经验,和设计模式等类结构的相关只是。

p114页有相关书籍推荐:《图解设计模式》结城浩,《对象是怎样工作的》平泽章 ,《设计模式精解》

 





 第九章 运算符号

 

 9.1 赋值运算符

将二元运算符号和赋值组合起来的运算符号。

 +=,-=,/=, *=,%= ,**= 等

除了变量,赋值运算符号适用于经由method的object.

 

9.2 逻辑运算符

  1. 表达式的执行顺序是从左往右
  2. 如果逻辑表达式的真假已经可以✅,则不会再判断剩余的表达式
  3. 最后一个(判断了的)表达式的值为整体逻辑表达式的值 

 

 item = ary && ary.first     #如果ary为nil,则读取ary.first的值为❌。等同于下面的写法:

 item = ary&.first  #对象&.方法 ,称为安全运算符,或nil检查方法调用

 

9.3条件运算符(三元运算符)

 条件 ? 表达式1 :表达式2

等同于

if 条件

  表达式1

else

  表达式2

end

如果表达式太复杂,则不容易理解,不要滥用此写法。

 

9.4范围运算符

 

range:在Ruby中表示数值范围的对象。

Range.new(1,10)   #=> 1..10 ,

简化写法 : 1..10

 

for循环里面用到了 in 1..5,见本篇上章节。 

 

(5..10).to_a    #=> [5, 6, 7, 8, 9, 10]
(5...10).to_a   #=> [5, 6, 7, 8, 9]       三个点的作用范围是从5到10的前一个元素

 ("a"..."g").to_a    #=> ["a", "b", "c", "d", "e", "f"]

 

succ方法(succession: happening one after the other without anything diffe-rent happening in between

在Range对象内部,根据起点逐个生成接下来的值。
> val = "a"

 

 => "a"
> val = val.succ
 => "b"
> val = val.succ
 => "c"
> val = val.succ
 => "d"

 

var = "a"
while var != "g"
  var = var.succ
  puts var

end

b
c
d
e
f
g
 => nil

 

9.5 运算符的优先级

 如果没有熟练掌握优先级,多用()

 

9.6 定义运算符号

Ruby中的运算符大多可以作为实例方法提供使用,因此可以重新定义运算符。

 

二元运算符:定义四则运算符等二元运算符时,会将运算符名字作为method name。运算符左侧为receiver,右侧作为method的参数被传递 

class Point
attr_reader :x, :y
def initialize(x=0, y=0)
@x, @y = x, y
end
def inspect  # 用于显示
"(#{x}, #{y})"
end
def +(other)   # x、y分别进行加法运算
self.class.new(x + other.x, y + other.y)
end
def -(other)  # 定义二元运算符,习惯把参数定义为 other
self.class.new(x - other.x, y - other.y)
end
end
point0 = Point.new(3, 6)
point1 = Point.new(1, 8)
p point0 #=> (3, 6)
p point1 #=> (1, 8)
p point0 + point1 #=> (4, 14)
p point0 - point1 #=> (2, -2)

 在方法内创建当前类的对象,不直接写类名,而使用self.class,这是防止point类的子类调用 + 方法的时候,错误的返回了point类,还有就是如果有Min-in的话也容易造成❌。 

 

 9.62 一元运算符

+,-,~,!一元运算符没有参数

class Point
...
def +@
dup # 返回自己的拷贝
end
def -@
self.class.new(-x, -y) # 颠倒x、y各自的正负
end
def ~@
self.class.new(-y, x) # 使坐标翻转90度
end
end
point = Point.new(3, 6)
p +point #=> (3, 6) 符号写在对象前面
p -point #=> (-3, -6)
p ~point #=> (-6, 3)

 

9-63 下标方法

 数值,散列中的obj[i]以及obj[i] = x 读写方法,称为下标方法。方法名为:[], []=


class Point
  attr_accessor :x, :y #⚠️ 通过这个method对实例对象查询和修改。
 def initialize(x=0, y=0) 
 @x, @y = x, y
 end 
 def [](index)
case index
when 0
x
when 1
y
else
raise ArgumentError, "out of range `#{index}'"
end
end
def []=(index, val)
case index
when 0
self.x = val
when 1
self.y = val
else
raise ArgumentError, "out of range `#{index}'"
end
end
end
point = Point.new(3, 6)
p point[0] #=> 3
p point[1] = 2 #=> 2 
p point[1] #=> 2
p point[2] #=>错误(ArgumentError)

主动抛出异常: raise 方法(cause a reaction),如上例⬆️

 

 


 

 

第10章  错误处理与异常 

    Descendants of class Exception are used to communicate

between Kernel#raise and rescue statements in begin ... end blocks. Exception objects carry information about the exception – its type (the exception's class name), an optional descriptive string, and optional traceback information. Exception subclasses may add additional information like NameError#name.

 

异常对象的方法:class, message, backtrace

 

10.1关于错误处理

错误类型:

  • 数据错误:
  • 系统错误:
  • 程序错误:如调用了不存在method,弄错参数,算法错误等程序本身的缺陷导致的❌。 

相应处理:

  1. 排除错误原因
  2. 忽略错误(无伤大雅的错误)
  3.  恢复错误发生前的状态
  4. 重试一次
  5. 终止程序(个人用的小程序)

 

 重点:

  • 错误是否破坏了输入的数据
  • 是否可以对错误的内容以及原因做出提示 

 

10.2异常处理 

例子: 

文件名 : 行号: in `方法名': 错误信心(异常类名) from 文件名 :行号 :in 方法名

 

Ruby使用异常处理语法:

begin

  可能发生异常的代码

rescue => 引用异常对象的变量(自定义的)

  发生异常时的处理

end 

 

例子:

ltotal=0 # line合计
wtotal=0 # words合计
ctotal=0 # 字节数character合计
ARGV.each do |file|  # ARGV 是输入
begin
input = File.open(file) # 打开文件(A)
l=0 # file内的行数
w=0 # file内的单词数
c=0 # file内的字数
input.each_line do |line|
l += 1
c += line.size
line.sub!(/^\s+/, "") # 删除行首的空白符,解释见⬇️
  ary = line.split(/\s+/) # 用空白符分解为数组 w += ary.size end input.close # 关闭文件 printf("%8d %8d %8d %s\n", l, w, c, file) # 整理输出格式 ltotal += l wtotal += w ctotal += c rescue => ex # 引用异常对象的变量ex print ex.message, "\n" # 输出异常信息(B) end end printf("%8d %8d %8d %s\n", ltotal, wtotal, ctotal, "total")

 

str.sub( patternreplacement ) -> aString       # return 一个新的string  
str.sub!patternreplacement  ) -> str  or nil   # 原str 被改变。

  

总结:根据实际情况在留意的地方做异常处理,不需要每个method都做异常处理。

 

10.4 后处理 ensure

不论异常是否发生,都希望执行的处理。 

 

begin

  # do something
  raise 'An error has occured.'
rescue => e
  puts "#{e.message}: I am rescued"
ensure
  puts 'ensure: This code gets executed always.'
end

 

结果: 

An error has occured.: I am rescued

This code gets executed always.

10.5 重试 retry

例子:

file = ARGV[0]

begin

  io = File.open(file)

rescue

  sleep 10   #等10秒

  retry

end

 

data = io.read

io.close 

 

10.6 rescue修饰符

 表达式1 resuce 表达式2

等同于:

begin

  表达式1

rescue

  表达式2

end

例子: n = Integer("abc") rescue 0

Integer("..")方法可以把数值形式的字符串转换为数值并返回,否则报告错误ArgumentError。 如果加上 rescue 则返回 表达式2.

⚠️  这个技巧只用于简单的处理。

 

如果异常处理的范围是整个方法,则可以省略begin 和 end 

def 某个方法名称
# do something
raise 'An error has occured.'
rescue => e
puts 'I am rescued.'
ensure
puts 'This code gets executed always.'
end

10.8 指定需要捕捉的异常处理

begin 

  ..

rescue Exception1, Exception2 => 变量

  对exception1和exception2的处理

rescue Exception3 => 变量

  对exception3的处理

rescue

  对上述异常以外的异常的处理

end

 

puts "Start"
begin
puts "Called"
 raise "Errorrrr" 跳到rescue,并执行后续代码。e是自定义的异常变量。
puts "Never execute"
rescue => e
puts 'I am rescued.'
puts e.message
end
puts "Done"

 

10.9 异常类 

 

Ruby中所有异常都是Exception类的子类。rescue中指定异常的种类就是异常类的类名。

文档里面有列表: https://ruby-doc.org/core-2.5.0/Exception.html

resuce不但会捕捉指定的异常类,还会捕捉它的子类。 

MyError = Class.new(StandardError)  #=> 继承一个异常类 

等同标准写法Class MyError < StandardError; end 

 

10.10 raise方法: 主动抛出异常类 

 

raise
With no arguments, raises the exception in $! or raises aRuntimeError if $! is nil.
raise(string)

 With a single String argument, raises a RuntimeError with the string as a message. Otherwise, the first parameter should be the name of an Exception class (or an object that returns an Exception object when sent an exception message) 

raise(exception [, string [, array]])

The optional second parameter sets the message associated with the exception, and the third parameter is an array of callback information. Exceptions are caught by the rescue clause of begin...end blocks.

raise "Failed to create socket" 
raise ArgumentError, "No parameters", caller
 Ruby API

为什么你好像很少用到 raise 这个功能呢?这是因为丢出 raise 的大多是 Ruby 本身或是我们使用的库和框架之中,例如:

  • 当你对一个对象调用一个不存在的方法时,Ruby 会丢出 NoMethodError 异常
  • 在 Rails 中当URL找不到任何路由规则可以符合时,会丢出 Routing Error 异常

 


常见的错误类:

ScriptError:  is the superclass for errors raised when a script can not be executed because of a LoadError, NotImplementedError or a SyntaxError. 

syntax error : Raised when encountering Ruby code with an invalid syntax, belongs to ScriptError .

例子: 

eval("1+1=2")
SyntaxError ((eval):1: syntax error, unexpected '=', expecting end-of-input 1+1=2

❌原因是应该输入 == ,eval返回true. 

 

NameError: Raised when  a given name is invalid or undefined.

 

NameError::NoMethodError:  Raised when a method is called on a receiver which doesn't have it defined and also fails to respond with method_missing.

 

ActiveRecord::RecordNotFound:

Raised when Active Record cannot find a record by given id or set of ids. 

 

ActiveModel::ForbiddenAttributesError :

Raised when forbidden attributes are used for mass assignment.

class Person < ActiveRecord::Base
end

params = ActionController::Parameters.new(name: 'Bob')
Person.new(params)
# => ActiveModel::ForbiddenAttributesError

params.permit!
Person.new(params)
# => #<Person id: nil, name: "Bob">

 

ActiveModel::RecordInvalid

Raised by ActiveRecord::Base#save! and ActiveRecord::Base#create! when the record is invalid. 

Use the record method to retrieve the record which did not validate. 

 


第十一章 块block

 

11.1block

 

调用method时能够与参数一起传递的多个处理的集合。

对象.方法名(参数列表) do |块变量|

  希望的处理

end

对象.方法名(参数列表){|块变量|

  希望的处理 

method:  each_with_index(*args){|obj, i| block}  -> enum

 

11.2 使用方法

 

  1.  循环
  2. 隐藏常规处理
  3. 替换部分算法 

 

 11.21循环


sum = 0
outcome = {"参加费"=>1000, "挂件费用"=>1000, "联欢会费用"=>4000}
outcome.each do |pair|
 sum += pair[1] # hash会将[key,value]的组合作为数组作为块变量传入块
end
puts "合计:#{sum}"

 

sum = 0
outcome = {"参加费"=>1000, "挂件费用"=>1000, "联欢会费用"=>4000}
outcome.each do |item, price|
 sum += price
end
puts "合计:#{sum}"


file = File.open("sample.txt")
file.each_line do |line|
 print line
end
file.close

 

 11.22 隐藏常规处理

确保 后处理 ensure 被执行。

File.open("sample.txt") do |file|
 file.each_line do |line|
 print line
end
end

begin..end被隐藏掉了,这符号Ruby的特点多约定少配置。

file = File.open("sample.txt")
begin
 file.each_line do |line|
 print line
 end
ensure
 file.close
end

 

 11.23 替换部分算法

 元素排序算法中共通的部分由方法本身提供,我们可以用块来替换方法中元素排列的顺序,或者根据不同的目的来替换需要更改的部分。

如,自定义排序顺序,Array#sort. 

 块经常被用来在sort中实现自定义排序顺序。

 

如,预先取出排序所需信息

# %w(...)是一个用于生成一个以各单词作为元素的数组的字面量
ary = %w(
Ruby is a open source programming language with a focus
on simplicity and productivity. It has an elegant syntax
that is natural to read and easy to write
)
call_num = 0 # 块的调用次数
# 第一种写法。
sorted = ary.sort do |a, b|
 call_num += 1 # 累加块的调用次数
 a.length <=> b.length
end

# 数组的元素数量 28 调用块的次数 72
# a <=> b 比较运算符号:如果a<b返回值-1,a=b返回0, a>b返回1.
sorted = ary.sort_by do |item|

call_num += 1

item.length

end

#sort_by方法会将每个元素在块中调用一次,然后再根据结果进行排序处理,节省了运算时间

# 数组的元素数量 28 调用块的次数 28

puts "排序结果 #{sorted}"
puts "数组的元素数量 #{ary.length}"
puts "调用块的次数 #{call_num}"


 11.3  定义带块的方法

 

11.31执行块

 

def myloop
  while true
    yield     # 执行myloop方法的块
  end
end
num = 1
myloop do
  break if num > 10       #用于退出循环
  puts "num is #{num}"
  num = num * 2
end

 

11.32 传递块参数, 获取块的值

 

def total(from, to)
result = 0 # 合计值
from.upto(to) do |num| # upto(Integer)方法处理从from到to的值
if block_given? # block_given?(kernel)方法用来判断是否有block被传给method.
result += yield(num) # 累加经过块处理的值
else # 如果没有块的话
result += num # 直接累加
end
end
return result # 返回方法的结果
end
p total(1, 10) # 从1到10的和 => 55
p total(1, 10){|num| num ** 2 } # total方法带参数也带block
#从1到10的2次冥的和 => 385 

 

upto(limit) {|i| block } → self

Iterates the given block, passing in integer values from int up to and including limit. 

block_given? → true or false

Returns true if yield would execute a block in the current context.  

 

def block_args_test
yield() # 0个块变量
yield(1) # 1个块变量
yield(1, 2, 3) # 3个块变量
end
puts "通过|a|接收块变量"
block_args_test do |a|
p [a]   #p 输出nil,puts 和 print不输出nil
end puts puts "通过|a, b, c|接收块变量" block_args_test do |a, b, c| p [a, b, c] end puts puts "通过|*a|接收块变量" block_args_test do |*a| p [a] #*a是将所以块变量整合成为一个数组来接收,类似可变参数 end puts
结果: 

通过|a|接收块变量

[nil]
[1]
[1]
通过|a, b, c| 接收block variable
[nil, nil, nil]
[1, nil, nil]
[1, 2, 3]
通过|*a| 接收block variable
[[]]
[[1]]
[[1, 2, 3]]

 

11.33控制块的执行 

break 和 next。

 

$result = 0           #全局变量

 

def total(from, to)
  from.upto(to) do |num|
    if block_given?
      $result += yield(num)
    else
      $result += num
    end
  end
  return $result
end

 --------break: 遇到程序会终止。

n = total(1, 10) do |num|
  if num == 5
    break 
  end
  num
end

 

p n #=>  nil  ,没有执行 return $result
p $result #=>  10

 

---------next:遇到停止当前处理,执行下面的处理

n = total(1, 10) do |num|
  if num % 2 != 0
    next 0
  end
  num
end
p n #=> 30
p $result #=> 30  (即2+4+6+8+10等于30 )

  

 

11.34将块封装为对象。

需要用到Proc类 https://ruby-doc.org/core-2.5.0/Proc.html 

 目的:在接收块的方法之外的其他地方执行块,或者把块交给其他方法执行。

hello = Proc.new do |name|
puts "Hello, #{name}."
end
hello.call("World")
hello.call("Ruby")

 

def total2(from, to, &block)  # &block叫做Proc参数,Proc参数一定要放在最后。
result = 0 # 合计值
from.upto(to) do |num| # 处理从from到to的值
if block # 如果有块的话,没有的话返回nil,执行else的语句。
result += # 累加经过块处理的值
block.call(num)
else # 如果没有块的话
result += num 
end
end
return result 
end
p total2(1, 10) # 从1到10的和 => 55
p total2(1, 10){|num| num ** 2 } # 从1到10的2次冥的和 => 385

 将块封装为Proc对象后,我们就可以根据需要随时调用块,甚至还可以将其赋值给实例变量,让别的实例方法去任意调用。

 

此外,也能将Proc对象作为块传递给其他方法处理。 例如:

向Array#each方法传递block:each { |item| block } → ary

def call_each(ary, &block)
ary.each(&block)
end
call_each [1, 2, 3] do |item|
p item
end

 

 

posted @ 2018-01-10 10:01  Mr-chen  阅读(263)  评论(0编辑  收藏  举报