为什么当我调用一个函数时,一个传递对数组的引用,数组在 Ruby 中发生了变化?[关闭]

why when I call a function passing a reference to an array, the array is changing in ruby? [closed]

提问人:MR-RenatoBlue 提问时间:5/17/2023 更新时间:5/18/2023 访问量:64

问:


编辑问题以包括所需的行为、特定问题或错误以及重现问题所需的最短代码。这将帮助其他人回答这个问题。

6个月前关闭。

社群在 6 个月前审查了是否重新打开这个问题,并将其关闭:

原始关闭原因未解决

我正在学习一个关于在 Ruby 中创建一个小游戏的教程,当时我遇到了一个我无法理解的错误。当我仔细观察时,我意识到错误发生在 on 的 51 行。

在本教程的这一点上,英雄“H”应该能够使用键“ASDW”四处走动。我注意到第 49 行的数组已更改,这在应用程序中引入了错误。之后,我创建了另一个变量,该变量调用了代码。

为什么当我调用 的第 51 行时,数组正在更改?

从我在 Rubydocs 中读到的内容来看,我只是不明白为什么会发生这种情况。
fogefoge.rbnova_posicao = calcula_nova_posicao heroi, direcaoheroiheroi = encontra_jogador mapaheroi2fogefoge.rb

File "main.rb"
require_relative "fogefoge"

inicia_fogefoge


File "mapa1.txt"
XXXXX
X H X
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X
XXXXX

File "ui.rb"
def da_boas_vindas
  puts "bem vindo ao Foge-foge"
  puts "Qual é o seu nome?"
  nome = gets.strip
  puts "\n\n\n\n\n"
  puts "Começaremos o jogo para você, #{nome}"
  nome
end
def desenha(mapa)
  puts mapa
end
def pede_movimento
  puts "Para onde deseja ir?"
  movimento = gets.strip
end


#File "fogefoge.rb"
require_relative "ui"


def le_mapa(numero)
  arquivo = "mapa#{numero}.txt"
  texto = File.read(arquivo)
  mapa = texto.split "\n"
#  p mapa
end
def encontra_jogador(mapa)
  caractere_do_heroi = "H"
  mapa.each_with_index do |linha_atual, linha|
    coluna_do_heroi   = linha_atual.index caractere_do_heroi
    if coluna_do_heroi
      return [linha, coluna_do_heroi]
    end
  end
end
def calcula_nova_posicao(heroi, direcao)
  case direcao
    when "W"
      heroi[0] -= 1
    when "S"
      heroi[0] += 1
    when "A"
      heroi[1] -= 1
    when "D"
      heroi[1] += 1
  end
  heroi
end
def posicao_valida?(mapa, posicao)
  linhas = mapa.size
  colunas = mapa[0].size
  estourou_linhas = posicao[0] < 0 || posicao[0] >= linhas
  estourou_colunas = posicao[1] < 0 || posicao[1] >= colunas
  if estourou_linhas || estourou_colunas
    return false
  end
  if mapa[posicao[0]][posicao[1]] == "X"
    return false
  end
  true
end
def joga(nome)
  mapa = le_mapa 1
  while true
    desenha mapa
    direcao = pede_movimento
    heroi = encontra_jogador mapa
    #heroi2 = encontra_jogador mapa
    nova_posicao = calcula_nova_posicao heroi, direcao #At this call

    if !posicao_valida? mapa, nova_posicao
      next
    end
    mapa[heroi[0]][heroi[1]] = " "
    mapa[nova_posicao[0]][nova_posicao[1]] = "H"
  end
end

def inicia_fogefoge
  nome = da_boas_vindas
  joga nome
end

我本以为“H”会在空白处四处移动,但“H”被重复到几个方向


输出:

bem vindo ao Foge-foge
Qual é o seu nome?
marcos





Começaremos o jogo para você, marcos
XXXXX
X H X
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X
XXXXX
Para onde deseja ir?
d
XXXXX
X H X
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X
XXXXX
Para onde deseja ir?
D
XXXXX
X HHX
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X
XXXXX
Para onde deseja ir?
S
XXXXX
X HHX
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X


如果取消注释第 50 行的第二个引用,然后将第 51 行更改为,则代码将起作用。nova_posicao = calcula_nova_posicao heroi2, direcao

数组 ruby 函数

评论

2赞 Jörg W Mittag 5/17/2023
请确保构建一个最小的可重现示例。请注意,这三个词都很重要:它应该只是一个示例,您不应该发布整个实际代码,而应该创建一个简化的示例来演示您的问题。此外,它应该是最小的,即它不应该包含任何不是证明问题绝对需要的东西。(大多数初学者问题都可以在不到 5 行简短的代码中演示。它应该是可重现的,这意味着如果我复制并粘贴并运行代码,我应该会看到与您看到的完全相同的问题。
0赞 user1934428 5/17/2023
这就是引用的全部要点:如果具有对某个对象的引用,则可以将修改方法应用于此对象。如果您不希望这种情况发生,请传递对象的副本,或求助于函数式编程语言(例如 Haskell)。
0赞 Narfanator 5/18/2023
你好!我们无法从中分辨出哪一行是第 51 行,因为我们没有得到行号。你能编辑它以表明你在说哪一行吗?

答:

0赞 Stefan 5/17/2023 #1

您的实现会更改传递的数组,然后返回它。因此,调用后 your 和 将是相同的(不仅包含相同的值,而且都引用相同的对象)。calcula_nova_posicaoheroinova_posicaoheroi

下面是一个关于如何验证这一点的简单示例:(equal?检查两个对象是否实际上是同一个对象)

a = [0, 0]
b = calcula_nova_posicao(a, "S")

a #=> [1, 0]  <- note that "a" has changed!
b #=> [1, 0]

a.equal?(b) #=> true

为什么当我调用一个函数时,一个传递对数组的引用,数组在 Ruby 中发生了变化?

这是 Ruby 中一个非常基本的行为。有些对象是可变的,即您可以通过向它们发送某些消息来更改这些对象(所谓的破坏性方法)。可变对象的示例包括字符串、哈希和数组。(破坏性)消息的发送方式或位置或谁发送都无关紧要。一旦接收到,该物体就会自行改变。

下面是两个变量的示例,它们引用了同一个对象,即一个字符串。如果将 upcase! 消息发送到字符串,它将将其字符大写,并且两个变量都将反映该更改:(因为它们仍然引用现在已更改的同一对象)ab

a = "foo"
b = a

a.upcase!   # or b.upcase!

a #=> "FOO"
b #=> "FOO"

这同样适用于方法参数。如果将可变对象传递到方法中,则该方法可以更改它,就像在方法外部一样:

def my_method(s)
  s.upcase!
end

a = "foo"
my_method(a)

a #=> "FOO"

回到你的问题。您可能希望 1) 返回一个新数组,2) 保持传入的数组不变。后者对于清除地图中的电流很重要。但一般来说,不要在方法中更改参数也是一个好主意,因为它可能会导致意外的结果并且很难找到错误。calcula_nova_posicaoH

下面是一个实现:

def calcula_nova_posicao(heroi, direcao)
  case direcao
  when "W"
    [heroi[0] - 1, heroi[1]]
  when "S"
    [heroi[0] + 1, heroi[1]]
  when "A"
    [heroi[0], heroi[1] - 1]
  when "D"
    [heroi[0], heroi[1] + 1]
  end
end

这是另一个使用数组分解的方法:

def calcula_nova_posicao(heroi, direcao)
  y, x = heroi
  case direcao
  when "W" then y -= 1
  when "S" then y += 1
  when "A" then x -= 1
  when "D" then x += 1
  end
  [y, x]
end

您可以考虑为您的方法编写测试