为什么puts方法没有调用我的.to_s方法?

3

我认为对于自定义类定义to_s方法意味着在调用puts方法时,返回的输出将按照to_s指定的格式进行。然而,在这个程序中,只有当我写puts bingo_board.to_s时才能得到我想要的结果。发生了什么事情?

class BingoBoard < Array
  @@letters = %w[B I N G O]

  def initialize
    # populates an 5x5 array with numbers 1-100
    # to make this accessible across your methods within this class, I made
    # this an instance variable. @ = instance variable
    @bingo_board = Array.new(5) {Array.new(5)}
    @bingo_board.each_with_index do |column, i|
      rangemin = 15 * i + 1
      @bingo_board[i] = (rangemin..(rangemin+14)).to_a.sample(5)
    end
    @bingo_board[2][2] = "X" # the 'free space' in the middle
    @game_over = false
  end

  def game_over?
    @game_over
  end

  def generate_call
    ....
  end

  def compare_call(call)
    @bingo_board[@@letters.index(call[0])].include? call[1]
  end

  def react_to_call(call)
    ...
  end

  def check_board
    ...
  end

  def show_column(num)
    ...
  end

  def to_s
    result = ""
    0.upto(4) do |val|
      result += " " + @@letters[val] + " "
    end
    result += "\n\n"
    0.upto(4) do |row|
      0.upto(4) do |col|
        val = @bingo_board[col][row]
        result += " " if val.to_i < 10
        result += val.to_s + " "
      end
      result += "\n"
    end
    result
  end
end

my_board = BingoBoard.new
counter = 0
until my_board.game_over?
  puts my_board.to_s # renders the board in accordance with my to_s method
  call = my_board.generate_call
  counter += 1
  puts "\nThe call \# #{counter} is #{call[0]} #{call[1]}"
  my_board.react_to_call(call)
  gets.chomp
end
puts my_board  # renders bubkes (i.e., nothing)
puts "\n\n"
puts "Game over"
4个回答

2
因为你是从数组中继承的,这就是为什么会出现奇怪的行为。我不知道你是否需要继承,如果不需要,请将其删除,这样就可以按照你的期望工作了。
如果您想知道为什么会发生这种情况,以下是更详细的解释。基本上,puts对数组进行了特殊处理,因此当传递一个数组时,puts会对每个成员调用。请参考Ruby Array#puts not using overridden implementation?

哇!那真是一个快速修复。但是Array有它自己的to_s方法。为什么我的自定义to_s方法不能覆盖它呢,@jwater? - pgblu
你留给我的链接的 tl;dr:数组被处理方式不同,而且没有人真正知道原因。这个总结公平吗? 另外:是否可以准确地说,数组的 to_s 方法不能被覆盖? - pgblu
1
@pgblu - 不完全准确,它会被覆盖,如果你使用其他方法比如说print,那么它会调用你的to_s方法,但只要你使用puts方法,你就是正确的。 - DataDao

2
如@jörgwmittag所说,这是一个特殊情况。方法IO#puts对数组的处理方式——包括响应to_ary的任何内容——不同于其他对象。它首先调用to_ary,然后迭代结果数组中的每个元素,并仅对它们调用to_s。它从不在数组本身上调用to_s
如果您委派给成员数组而不是从Array进行子类化,则可以更好地控制“继承”(委派)的内容。然后,您可以从委派中排除to_ary,这将防止puts将您的对象视为数组并触发此行为。
其他通用解决方案:
  1. Use string interpolation or explicit to_s calls so that what puts receives is already a string:

    puts "#{bingo_board}"
    puts bingo_board.to_s
    
  2. Use print or printf instead of puts:

    print bingo_board,"\n"
    printf "%s\n",bingo_board
    

1
如果对象是一个Array或者可以被转换成一个(即实现了to_ary),那么puts不会在对象上调用to_s,而是通过迭代对象并调用其中每个对象的to_s来打印每个对象内部的内容。
参见:
puts [1, 2]
# 1
# 2

[1, 2].to_s
# => '[1, 2]'

这个实际上已经有文档记录, 尽管有点含蓄:

如果用一个数组作为参数调用,会将每个元素写在新行。


0

看起来它运行的是Array#inspect方法,而不是你自定义的to_s。在to_s定义的结束之后添加alias_method :inspect, :to_s将会有所帮助。

但这只适用于p,因为puts运行的是each(&:inspect)


好的。现在,我得到的不再是空值,而是 []。 - pgblu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接