为什么Ruby编译器在这种情况下将预期的参数数计算为零?

为什么Ruby编译器在这种情况下将预期的参数数计算为零?,ruby,Ruby,我是Ruby新手,在定义类的对象时遇到了问题。如果我理解正确,def initialize应该请求三个计算器,但是代码编译时出现了一个错误,即给定了三个,但它预期为零值 我原以为attr_accessor会出错,但快速测试表明情况并非如此 ruby版本是2.6.4 test.rb:16:在'new'中:参数数目错误(给定3,应为0)(ArgumentError) 问题从一个问题开始 knight = Battler.new(1000, 10, 3) 正如您所知,attr\u accessor接

我是Ruby新手,在定义类的对象时遇到了问题。如果我理解正确,def initialize应该请求三个计算器,但是代码编译时出现了一个错误,即给定了三个,但它预期为零值

我原以为attr_accessor会出错,但快速测试表明情况并非如此

ruby版本是2.6.4

test.rb:16:在'new'中:参数数目错误(给定3,应为0)(ArgumentError)

问题从一个问题开始

knight = Battler.new(1000, 10, 3)

正如您所知,
attr\u accessor
接受符号(或字符串)列表,并且为每个符号(或字符串)定义了两种方法:所谓的setter(例如
health=
)和getter(
health
)。列表(不是实际的类型,顺便说一句)是一个或多个用逗号分隔的表达式,因此在您的示例中,Ruby希望在
:armor
之后有另一个表达式,因为您在其后放置了逗号

(如果后面的逗号是故意的,请告诉我们。一些程序员在某些情况下使用并推荐它们(具体取决于语言)。尽管这会使任何答案变得更复杂,因为Ruby实际上可以使用它们——但在这种情况下不可以。)

然而,这只是bug解释的一部分。作为一种编程语言,Ruby最基本的特性之一是几乎所有东西都有一个值(表达式)。例如,在其他语言中尝试类似于
x=if…
的方法!它通常不会像许多语言的if语句,而不是if表达式那样工作

在Ruby中,方法定义也是表达式:通过
def
def foo;end
)定义方法,用于返回
nil
。由于Ruby 2.1方法定义返回方法的名称(
:foo
)。现在,def表达式的返回值似乎对您不是很有用,而且在许多情况下也不是很有用,但是您可能同意方法名称(
:foo
)肯定比
nil
更有用

这方面的主要用例是方法,如
private
public
method\u function
,等等。这些方法不是关键字,而是像关键字一样使用(它们的定义特性是调用,括号省略):

我们的朋友
attr_accessor
也是这样一种类似关键字的方法,但是
def
表达式的值在这里更为重要。记住,它是作为符号的方法名称。你写了(简化的):

为了更清楚一点,如果没有
attr\u访问器
,您的代码可能如下所示:

class Battler
  def initialize(health, damage, armor)  # takes 3 required arguments
  end
  def health
    @health
  end
  def health=(v)
    @health = v
  end
  # damage, armor
  def initialize  # takes no arguments
    @initialize
  end
  def initialize=(v)
    @initialize = v
  end
end
您看到您正在重新定义
Battler#initialize
,而您的新版本不接受参数,因为getter通常不接受参数

重新定义不会被视为错误,因为它们可能是故意的,但是Ruby会为您发出警告。在命令行上运行
ruby-w test.rb
,它将输出:

test.rb:15:警告:已分配但未使用的变量-骑士
test.rb:16:警告:已分配但未使用的变量-goblin
测试rb:2:警告:重新定义方法;丢弃旧的初始化
test.rb:3:警告:这里有初始化的先前定义
回溯(最近一次呼叫最后一次):
1:来自测试。rb:15:in`'
test.rb:15:在'new'中:参数数目错误(给定3,应为0)(ArgumentError)

在这里,您可以看到在第2行中重新定义了最初在第3行定义的
initialize
方法。如果您不明白为什么第3行出现在第2行之前,那么请记住,方法定义是传递给
attr\u访问器的列表的一部分,因此必须在调用之前评估def expr。

删除
attr\u访问器:健康,:伤害,:装甲,
末尾的逗号,一切都会好起来的。天哪,谢谢!成功了!我要求您删除的逗号似乎告诉Ruby,
attr\u accessor
方法有一个或多个参数,这些参数将在下一行继续。为什么
initialize
的定义没有引发异常对我来说是个谜。但是,
Battler.instance_方法(:initialize).owner=>Battler;Battler.instance_方法(:initialize).parameters#=>[]
告诉我们方法
initialize
是在没有参数的情况下创建的,这与您的错误消息一致。非常奇怪。
def initialize的值;end
:initialize
所以OP基本上写了
attr\u accessor:initialize
@cremno,很好的观察!请作为答案发表文章,详细阐述一下,因为我不确定所有读者都会理解你的观点。(正确的方法
:initialize
是[用三个参数]创建的,但随后立即被
@initialize
的访问器覆盖)顺便说一句,我不明白为什么有人投票关闭这个问题。这是一个很好的问题!选中:
:initialize=
已定义,而不是
:initialize
。尝试调用
Battler.new.methods
我同意你不应该忽视警告,它们存在是有原因的!非常感谢您如此详细的回答!至于那个额外的逗号,好吧,应该有其他变量,但我决定用其他代码检查代码,只是为了让基本的东西工作。。。在删除它们时,我似乎遗漏了最后一个逗号。所以这里没有魔法或高科技的东西。
private def foo
end
# before 2.1
private  # from now on every method in this class context will be private
def foo
end
# or
def foo
end
private :foo  # only foo will be private but we have to repeat the name
attr_accessor :health, :damage, :armor,
def initialize(health, damage, armor)
end

# which after the method was defined becomes
attr_accessor :health, :damage, :armor, :initialize
class Battler
  def initialize(health, damage, armor)  # takes 3 required arguments
  end
  def health
    @health
  end
  def health=(v)
    @health = v
  end
  # damage, armor
  def initialize  # takes no arguments
    @initialize
  end
  def initialize=(v)
    @initialize = v
  end
end
test.rb:15: warning: assigned but unused variable - knight
test.rb:16: warning: assigned but unused variable - goblin
test.rb:2: warning: method redefined; discarding old initialize
test.rb:3: warning: previous definition of initialize was here
Traceback (most recent call last):
         1: from test.rb:15:in `<main>'
test.rb:15:in `new': wrong number of arguments (given 3, expected 0) (ArgumentError)