© Shine's Blog

Powered by LOFTER

[Ruby 中文教程 By Chris Pine] 8.写你自己的method

翻译:阳光

原文来自 <https://pine.fm/LearnToProgram/?Chapter=08> 


8.写你自己的method


如我们已看到过的,循环和迭代子能让我们不断地重复做一件事(运行同样的代码)。但是,有时我们想要做一件事若干次,但是实在程序中不同的地方。比如,假设我们在为一个心理学的学生写一个问卷调查程序。从我认识的心理学学生和他们给我的问卷调查哪里,这个程序看起来可能是这样的:

输入:

puts 'Hello, and thank you for taking the time to'

puts 'help me with this experiment. My experiment'

puts 'has to do with the way people feel about'

puts 'Mexican food. Just think about Mexican food'

puts 'and try to answer every question honestly,'

puts 'with either a "yes" or a "no". My experiment'

puts 'has nothing to do with bed-wetting.'

puts


# We ask these questions, but we ignore their answers.


goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating tacos?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

else

puts 'Please answer "yes" or "no".'

end

end


goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating burritos?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

  goodAnswer = true

else

  puts 'Please answer "yes" or "no".'

end

end


# We pay attention to *this* answer, though.

goodAnswer = false

while (not goodAnswer)

puts 'Do you wet the bed?'

answer = gets.chomp.downcase

if (answer == 'yes') or answer == 'no')

  goodAnswer == true

  if answer == 'yes'

    wetsBed = true

  else

    wetsBed = false

  end

else

  puts 'Please answer "yes" or "no".'

end

end


goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating chimichangas?'

answer = gets.chomp.downcase

if (answer == 'yes' of answer == 'no')

  goodAnswer = true

else

  puts 'Please answer "yes" or "no".'

end

end


puts 'Just a feww more questions...'


goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating sopapillas?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

  goodAnswer = true

else

  puts 'Please answer "yes" or "no".'

end

end


# Ask lots of other questions about Mexican food.


puts

puts 'DEBRIEFING:'

puts 'Thank you for taking the time to help with'

puts 'this experiment. In fact, this experiment'

puts 'has nothing to do with Mexican food. It is'

puts 'an experiment about bed-wetting. The Mexican'

puts 'food was just there to catch you off guard'

puts 'in the hopes that you would answer more'

puts 'honestly. Thanks again.'

puts

puts wetsBed

输出:

Hello, and thank you for taking the time to

help me with this experiment. My experiment

has to do with the way people feel about

Mexican food. Just think about Mexican food

and try to answer every question honestly, 

with either a "yes" or a "no". My experiment

has nothing to do with bed-wetting.


Do you like eating tacos?

(用户输入)yes

Do you like eating burritos?

(用户输入)yes

Do you wet the bed?

(用户输入)no way!

Please answer "yes" or "no".

Do you wet the bed?

(用户输入)NO

Do you like eating chimichangas?

(用户输入)yes

Just a few more questions...

Do you like eating aspapillas?

(用户输入)yes


DEBRIEFING:

Thank you for taking the time to help with this experiment. In fact, this experiment

has nothing to do with Mexican food. It is

an experiment about bed-wetting. The Mexican

food was just there to catch you off guard

in the hopes that you would answer more honestly. Thanks again.


flase


这是个很长的程序,有很多的重复(所有关于墨西哥食物的问题都是相似的,而关于尿床的问题只有些微的不同)。重复不是件好事。但是,我们依然无法将它纳入到一个大的loop或迭代子中去,因为有时我们在问题之间还有事情要做。在这种情况下,最好是自己写一个method,就像这个:

输入:

def sayMoo

puts 'mooooooo...'

end


额……我们的程序没有说Moo。为什么没有?因为我们没有让它说。我们告诉他如何去说Moo,但是我们没有告诉它这么做。让我们再来写点:

输入:

def sayMoo

puts 'mooooooo...'

end


sayMoo

sayMoo

puts 'coin-coin'

sayMoo

sayMoo

输出:

mooooooo...

mooooooo...

coin-coin

mooooooo...

mooooooo...


啊,好多了。(为了防止你不认识法语,代码中间有一只法国鸭子。在法语中,鸭子念作“coin-coin”)。

我们定义了sayMoo这个方法(Method的名字,与变量名字一样,以一个小写字母开始。也有一些例外,比如+或者==就不是这样)。但方法不是总是与object相关联的吗?好吧,它们确实是,在这种情况下(就像puts和gets一样),这个method只是与代表整个程序的object相关联。在下一章中,我们会看到如何给其他object添加method。但首先……


Method参数


你可能注意到了,一些method是可以直接应用于一个object的(比如gets,to_s,revers……)。但是,另一些method则需要参数来告诉object如何实施这个method(比如+、-、puts……)。例如,你不会说5+,对吧?你告诉5要加上某个数,但是你并没有告诉它“加什么”。


要给sayMoo添加一个参数(让我们假设,是moos的次数),我们可能这么写:

输入:

def sayMoo numberOfMoos

puts 'mooooooo...'*numberOfmoos

end


sayMoo 3

puts 'oink-oink'

sayMoo # This should give an error because the parameter is missing.

输出:

mooooooo...mooooooo...mooooooo...

oink-oink

#<ArgumentError: wrong number of arguments (0 for 1)>


numberOfMoos是一个变量,它指向输入的参数。我再说一次,但这有点难讲清楚:numberOfMoos是一个变量,它指向输入的参数。所以如果我们输入sayMoo 3,那参数就是这个3,变量numberOfMoos指向了3。如你所见,现在参数成了必要的了。毕竟,如果你不给出一个参数,那sayMoo怎么能知道把'mooooooo...'乘以多少遍?你那可怜的计算机可不知道这种事。

如果Ruby中的object与英语中的名字一样,那method就像是个动词,而参数就是形容词(就像sayMoo一样,参数告诉我们如何sayMoo)或者有时用来指引object(就像puts,它的参数是输出什么)。


本地变量


在下面的程序中,有两个变量:

输入:

def doubleThis num

numTimes2 = num*2

puts num.to_s+' doubled is '+numTimes2.to_s

end

doubleThis 44

输出:

44 doubled is 88

两个变量是num和numTimes2。它们都位于方法doubleThis中。这些就是本地变量(你目前所见的变量都是本地变量)。这意味着它们存在于method之中,不能独立存活。如果你试试,你会得到错误:

输入:

def doubleThis num

numTimes2 = num*2

puts num.to_s+' doubled is '+numTimes2.to_s

end

doubleThis 44

puts numTimes2.to_s

输出:

44 doubled is 88

#<NameError: undefined local variable or method `numTimes2' for #<StringIO:0x82ba21c>>

未定义的本地变量……事实上,我们确实定义了这个本地变量,但它不位于我们尝试使用它的地方,它只对method来说是本地的。

这看起来可能不是太方便,但它其实是很好的。当你不能从外部调用method内部的变量时,这也意味着你在method内部也改变不了外面的变量:

输入:

def littlePest var

var = nil

puts 'HAHA! I ruined your variable!'

end

var = "You can\'t even touch my variable!"

littlePest var

puts var

输出:

HAHA! I ruined your variable!

You can't even touch my variable!

实际上在这个小程序中有两个叫做var的变量:一个在littlePest中,一个在它外面。当我们使用littlePest var时,我们实际上是把字符串从一个var传递给了另一个,它们两个指向的是同一个字符串。然后littlePest将它本地的var指向了nil,它这对外部的var来说丝毫没有影响。


返回数值


你可能注意到了,在你使用一些method时,它可能会返回给你一些东西。例如,gets会返回一个字符串(你输入的字符串),5+3中的method +返回8(实际上它是5. +(3))。数字的算法method返回数字,字符串的算法method返回字符串。


知道两种method的不同是很重要的,一种是返回一个值,一种是向你的屏幕发送一个信息,比如puts。注意 5+3会返回 8,而不会输出8.

所以puts返回的是什么?我们以前没有关注过这一点,但是让我们现在来看一看:

输入:

returnVal = puts 'This puts returned:'

puts returenVal

输出:

This puts returned:

nil


第一个puts返回的是nil。虽然我们并没有测试过,第二个puts返回的也是nil,puts总是返回nil。每个method都必须返回一些东西,即使只是一个nil。

休息一下,然后写一个程序来找出sayMoo返回了什么

你被惊到了吗?好吧,这是它如何工作的:一个method返回的值是这个method的最后一行。在sayMoo这个例子中,这表示它会返回 puts 'mooooooo...'*numberOfMoos,而puts返回的只是nil,所以返回的还是nil。如果我们想要所有的method都返回字符串'yellow submarine',我们可能只需要将这个字符串放到它们的最后一行:

输入:

def sayMoo numberOfMoos

puts 'mooooooo...'*numberOfMoos

'yellow submarine'


x = sayMoo 2

puts x

输出:

mooooooo...mooooooo...

yellow submarine

所以,让我们再试试那个心理学实验,但这次我们要写一个method来为我们问一些问题。我会把问题当作一个参数,并在他们回答yes的时候返回true,在他们回答no的时候返回flase。(即使大多数时间我们会忽略答案,但让我们的method返回答案仍是一个不错的主意。我们也可以用这种方式来处理尿床的问题)。我也会简化恭维和任务报告,因为这样读起来容易一些:

输入:

def ask question

goodAnswer = flase

while (not goodAnswer)

  puts question

  reply = gets.chomp.downcase


  if (reply == 'yes' or replay == 'no')

    goodAnswer = true

    if reply == 'yes'

      answer = true

    else

      answer = false

    end

   else

  end

end


answer # This is what we returen (true or false).

end


puts 'Hello, and thank you for...'puts


ask 'Do you like eating tacos?'      #  We ignore this return value.


ask 'Do you like eating burritos?'wetsBed = ask 'Do you wet the bed?'  #  We save this return value.ask 'Do you like eating chimichangas?'ask 'Do you like eating sopapillas?'ask 'Do you like eating tamales?'


puts 'Just a few more questions...'ask 'Do you like drinking horchata?'ask 'Do you like eating flautas?'


puts


puts 'DEBRIEFING:'puts 'Thank you for...'putsputs wetsBed


输出:

Hello, and thank you for...

Do you like eating tacos?

(用户输入)yes

Do you like eating burritos?

(用户输入)yes

Do you wet the bed?

(用户输入)no way!

Please answer "yes" or "no".

Do you wet the bed?

(用户输入)NO

Do you like eating chimichangas?

(用户输入)yes

Just a few more questions...

Do you like eating aspapillas?

(用户输入)yes


DEBRIEFING:

Thank you for ...


flase


不错吧?我们可以添加更多的问题了(添加问题现在变得简单了),但是我们的程序却变短了!这是一个很大的进步——一个懒程序员的梦想。


一个更大的例子


我觉得另一个例子在这可能会有帮助。我们称它为englishNumber。它会接收一个数字,比如22,然后返回一个英语版本给你(也就是 'twenty-two')。现在,让我们只让它响应整数0-100.


(注意:这里使用了一个较之前新的方法来从一个method返回值,然后用了一个branching的新内容:elsif。在内容中你会明白它们是干什么用的)。


def englishNumber number

# We only want numbers from 0-100.

if number < 0

  return 'Please enter a number zero or greater.'

end

if number > 100

  return 'Please enter a number 100 or lesser.'

end


numString = '' # This is the string we will return.

# "left" is how much of the number we still have left to write out.

# "write" is the part we are writing out right now.

# write and left... get it? :)


left = number

write = left/100   # How many hundreds left to write out?

left = left - write*100 #Subtract off those hundreds.


if write > 0

  return 'one hundred'

end


write = left/10   # How many tens left to write out?

left = left - write*10 # Subtract off those tens.


if write > 0

  if write == 1 # Uh-oh...

  # Since we can't write "tenty-two" instead of "twelve",

  # we have to make a special exception for these.

  if    left == 0

    numString = numString + 'ten'

  elsif left == 1

    numString = numString + 'eleven'

  elsif left == 2

    numString = numString + 'twelve'

  elsif left == 3

    numString = numString + 'thirteen'

  elsif left == 4

    numString = numString + 'fourteen'

  elsif left == 5

    numString = numString + 'fifteen'

  elsif left == 6

    numString = numString + 'sixteen'

  elsif left == 7

    numString = numString + 'seventeen'

  elsif left == 8

    numString = numString + 'eighteen'

  elsif left == 9

    numString = numString + 'nineteen'

  end

  # Since we took care of the digit in the ones place already,

  # we have nothing left to write.

  left = 0

elsif write == 2

  numString = numString + 'twenty'

elsif write == 3

  numString = numString + 'thirty'

elsif write == 4

  numString = numString + 'forty'

elsif write == 5

  numString = numString + 'fifty'

elsif write == 6

  numString = numString + 'sixty'

elsif write == 7

  numString = numString + 'seventy'

elsif write == 8

  numString = numString + 'eighty'

elsif write == 9

  numString = numString + 'ninety'

end


if left > 0

  numString = numString + '-'

end

    end


write = left  # How many ones left to write out?

left = 0  # Subtract off those ones.

if write > 0

  if    write == 1

    numString = numString + 'one'

  elsif write == 2

    numString = numString + 'two'

  elsif write == 3

    numString = numString + 'three'

  elsif write == 4

    numString = numString + 'four'

  elsif write == 5

    numString = numString + 'five'

  elsif write == 6

    numString = numString + 'six'

  elsif write == 7

    numString = numString + 'seven'

  elsif write == 8

    numString = numString + 'eight'

  elsif write == 9

    numString = numString + 'nine'

  end

end


if numString == ''

  # The only way "numString" could be empty is if

  # "number" is 0.

  return 'zero'

end


# If we got this far, then we had a number somewhere

# in between 0 and 100, so we need to return "numString".

numString

end


puts englishNumber(  0)

puts englishNumber(  9)

puts englishNumber( 10)

puts englishNumber( 11)

puts englishNumber( 11)

puts englishNumber( 17)

puts englishNumber( 32)

puts englishNumber( 88)

puts englishNumber( 99)

puts englishNumber(100)


输出:

zero

nine

ten

eleven

seventeen

thirty-two

eighty-eight-

ninety-nine

one hundred


好吧,关于这个程序有些事情我还是不喜欢。第一,它有太多重复了。第二,它不能处理大于100的数。第三,有太多的特别情况,太多的return了。让我们用一些array来试着让它更简洁一点:

def englishNumber number

if number < 0 # No negative numbers.

  return 'Please enter a number that isn\'t negative.'

end

if number == 0

  return 'zero'

end

# No more special cased! No more returns!

numString = '' # This is the string we will return.

onePlace = ['one',    'two',    'three',    'four',    'five',    'six',    'seven',    'eight',    'nine']

tensPlace = ['ten',    'twenty',    'thrity',    'forty',    'fifty',    'sixty',    'seventy',    'eighty',    'ninety']

teenagers = ['eleven',  'twelve',  'thirteen',  'fourteen',  'fifteen',  'sixteen',  'seventeen',  'eighteen',  'nineteen']


# "left" is how much of the number we still have left to write out.

# "write" is the part we are writing out right now.

# write and left... get it? :)

left = number

write = left/100   # How many hundreds left to write out?

left = left - write*100 # Subtract off those hundreds.

if write > 0

  # Now here's a really sly trick:

  hundreds = englishNumber write

  numString = numString + hundreds + ' hundred'

  # That's called "recursion". So what did I just do?

  # I told this method to call itself, but with "write" instead of

  #  "number". Remember that "write" is (at the moment) the number of

  # hundreds we have to write out. After we add "hundreds" to "numString",

  # we add the string ' hundred' after it. So, for example, if

  # we originally called englishNumber with 1999 (so "number" = 1999),

  # then at this point "write" would be 19, and "left" would be 99.

  # The laziest thing to do at this point is to have englishNumber

  # write out the 'nineteen' for us, then we write out ' hundred',

  # and then the rest of englishNumber writes out 'ninety-nine'.


if left > 0

  # So we don't write 'two hundredfifty-one'...

  numString = numString + ' '

end

  end

write = left/10   #  How many tens left to write out?

left = left - write*10  # Subtract off those tens.


if write > 0

  if ((write == 1) and (left > 0))

    # Since we can't write "tenty-two" instead of "twelve",

    # we have to make a special exception for these.

    numString = numString + teenagers[left-1]

    # The "-1" is because teenagers[3] is 'fourteen', not 'thirteen'.


    # Since we took care of the digit in the ones place already,

    # we have nothing left to write.

    left = 0

  else

    numString = numString + tensPlace[write-1]

    # The "-1" is because tensPlace[3] is 'forty', not 'thirty'.

  end

  if left >0

    # So we don't write 'sixryfour'...

    numString = numString + '-'

  end

end

write = left # How many ones left to write out?

left = 0     # Subtract off those ones.


if write > 0

  numString = numString + onesPlace[write-1]

  # The "-1" is because onesPlace[3] is 'four', not 'three'.

end


# Now we just return "numString"...

numString

end


puts englishNumber(  0)

puts englishNumber(  9)

puts englishNumber( 10)

puts englishNumber( 11)

puts englishNumber( 17)

puts englishNumber( 32)

puts englishNumber( 88)

puts englishNumber( 99)

puts englishNumber(100)

puts englishNumber(101)

puts englishNumber(234)

puts englishNumber(3211)

puts englishNumber(999999)

puts englishNumber(1000000000000)

输出:

zero

nine

ten

eleven

seventeen

thirty-two

eighty-eight

ninety-nine

one hundred

one hundred one

two hundred thiry-four

thirty-two hundred eleven

ninety-nine hundred ninety-nine hundred ninety-nine

one hundred hundred hundred hundred hundred hundred

啊……好多了。这个程序相当紧凑,这也是我为什么添加了这么多评论。它甚至处理很大的数,虽然并不跟我期望的那么相像。比如,我觉得'one trillion'会是最后一个数更好的一个返回值,或者'one million million'(虽然所有三个都是正确的)。事实上,你现在就可以实现它……


发表于2014-07-15.