Доступ к двумерному массиву в Perl
Пытаюсь выучить Perl. У меня есть массив, населенный городами. Я хочу передать массив по ссылке на подпрограмму и распечатать каждый город для вывода. Однако у меня есть следующие проблемы:
1) Я могу получить доступ к каждому элементу перед циклом while в подпрограмме. Но я не могу получить доступ к элементам в цикле while. Я получаю сообщение об ошибке:
...
Use of uninitialized value in print at <filename> line 44, <GEN2> line 997 (#1)
Use of uninitialized value in print at <filename> line 44, <GEN2> line 998 (#1)
...
Ниже приведен код. Я прокомментировал, что печатает, а что нет (я пытался вырезать код, который не нужен для моего объяснения...):
@cities;
#Assume cities is loaded successfully
&loadCities(getFileHandle('cities.txt'), $NUM_CITIES, \@cities);
&printElements(getFileHandle('names.txt'), \@cities);
sub printElements{
my $counter = 0;
my $arraySize = scalar $_[1];
# Prints fine!!!
print @{$_[1][($counter)%$arraySize];
while ((my $line = $_[0]->getline()) && $counter < 1000){
#Doesn't print. Generates the above error
print @{$_[1][($counter)%$arraySize];
$counter += 1;
}
}
2) Синтаксис Perl очень запутал меня. Я не понимаю, что происходит с @{$_[1]}[0]. Пытаюсь решить это.
- $ _ [1], обрабатывать значение в этом месте как скалярное значение (адрес памяти массива)
- @ {...}, интерпретировать то, что хранится по этому адресу памяти, как массив
- @{...} [x], получить доступ к элементу по индексу x
Я на правильном пути?
5 ответов
Мой первый совет, что вы должны поставить use strict;
а также use warnings;
в верхней части вашего сценария. Это обычно показывает довольно много вещей.
Эта строка: print @{$_[1][($counter)%$arraySize];
не имеет закрытия }
, Вам также не нужны круглые скобки $counter
,
Как вы упомянули, лучший / самый понятный способ получить длину массива my $arraySize = scalar @{$_[1]};
,
Вы можете ознакомиться с документацией здесь для работы со ссылками. Я дам вам краткий обзор.
Вы можете объявить массив как обычно
my @array = (1, 2, 3);
Затем вы можете ссылаться на него, используя обратную косую черту.
my $array_ref = \@array;
Если вы хотите использовать ссылку, используйте @{...}
, Это похоже на использование обычного массива.
print @{$array_ref};
Вы также можете объявить это как ссылку, чтобы начать с использования квадратных скобок.
my $array_ref = [1, 2, 3];
print @{$array_ref}; # prints 123
В Perl 2-мерный массив - это массив ссылок на массивы. Вот пример:
my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
print @{$array[1]}; # prints def
Теперь давайте попробуем передать ссылку на массив в подпрограмму.
my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\@array); # pass in an array reference
sub example {
my @arr = @{$_[0]}; # use the array reference to assign a new array
print @{$arr[1]};
print @{$_[0][1]}; # using the array reference works too!
}
Теперь давайте соберем все вместе и распечатаем весь двумерный массив.
my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\@array);
sub example {
my @arr = @{$_[0]};
for my $ref (@arr) {
print @{$ref};
}
} # prints abcdefghi
Вы можете легко адаптировать этот пример, если хотите использовать его для своего printElements
подпрограмма.
Еще одно замечание о печати элементов в массиве. Давайте возьмем эту строку из последнего примера:
print @{$ref};
Поскольку мы вызываем его каждый раз в цикле, мы можем захотеть напечатать новую строку в конце.
print @{$ref} . "\n";
Что это печатает? Попытайся! Это работает?
Вот тут-то и пригодится встроенное объединение подпрограмм.
print join(" ", @{$ref}) . "\n";
Циклы for, как правило, являются лучшим способом перебора массива. Мой ответ здесь немного говорит о том, как сделать это с помощью цикла while: /questions/22673339/perl-pozhalujsta-obyasnite-mne-sleduyuschee-povedenie-while/22673349#22673349 Вы также можете проверить этот вопрос: Лучший способ перебрать массив Perl
Чтобы ссылки были немного легче понять, я предпочитаю ->
синтаксис вместо синтаксиса munge-it-all-Вместе.
Вместо:
@{$_[1]}[0].
Пытаться
$_[1]->[0];
Это означает то же самое. Это легче и чище видеть. Ты это видишь $_[1]
является ссылкой на массив, и что вы ссылаетесь на первый элемент в этой ссылке на массив.
Тем не менее, еще лучший способ - просто установить переменные для различных элементов в @_
, Вам нужно набрать еще несколько букв, но ваш код намного проще для понимания и намного проще для отладки.
sub print_elements {
my $file_handle = shift; # This isn't a "reference", but an actual file handle
my $cities_array_ref = shift; # This is a reference to your array
my @cities = @{ $cities_array_ref }; # Dereferencing makes it easier to do your program
Теперь ваша подпрограмма имеет дело с переменными, которые имеют имена, а ваша ссылка на массив - это массив, который делает вещи чище. Кроме того, вы не можете случайно повлиять на значения в вашей основной программе. Когда вы используете @_
это прямая ссылка на значения, которые вы передаете ему. Изменение @_
изменяет значение в вашей основной программе, что, вероятно, не то, что вы хотите сделать.
Итак, пройдемся по вашей подпрограмме:
sub printElements {
my file_handle = shift;
my $cities_array_ref = shift;
my @cities = @{ $cities_array_ref };
my $counter;
my $array_size = @cities; # No need for scalar. This is automatic
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities[$city_number]. "\n";
$counter += 1;
}
}
Обратите внимание, насколько проще увидеть, что происходит, просто назначив несколько переменных, а не пытаясь объединить все вместе. Я могу легко увидеть, каковы ваши параметры для вашей подпрограммы. Если вы вызвали подпрограмму с неправильным порядком параметров, вы можете легко определить ее. Также обратите внимание, что я вспыхнул $counter % $array_size
и присвоил это переменной тоже. Внезапно становится очевидно, что я пытаюсь из этого выбраться.
Тем не менее, я не вижу, где вы используете $line
вы получаете с getline
, Я что-то пропустил?
Кстати, я мог бы сделать это без ссылки на массив в while
цикл тоже:
sub printElements {
my file_handle = shift;
my $cities = shift; # This is an array reference!
my $counter;
my $array_size = @{ $cities }; # I need to deref to get an array
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities->[$city_number]. "\n"; # That's it!
$counter += 1;
}
}
Посмотрите, как это ->
Синтаксис позволяет легко увидеть, что $cities
ссылка, указывающая на массив? Намного чище и легче понять, чем ${$cities}[$city_number]
,
Этот код на самом деле не скомпилируется.
print @{$_[1][($counter)%$arraySize];
наверное хочет быть:
print $_[1]->[($counter)%$arraySize];
после того, как вы исправите arraySize.
Если в результате получается указатель на массив,
print "@{$_[1]->[($counter)%$arraySize]}";
Я понял, как решить мою проблему #1 (все еще ищу помощь на моем #2, если кто-нибудь может).
Я изменился
my $arraySize = scalar $_[1];
в
my $arraySize = @{$_[1]};
И мое второе заявление о печати печатается так, как я хочу.
Кажется, что скаляр $_[1] брал адрес памяти массива, и я пытался изменить это, чтобы мой $counter вышел за пределы количества элементов в массиве.
Рекомендации меня тоже смущают! Мне всегда нравится разыменовывать их как можно скорее. Это работает для меня:
sub printElements{
my $counter = 0;
my $fh = $_[0];
my @array = @{$_[1]};
my $arraySize = scalar @array;
# Prints fine!!!
print @array[($counter)%$arraySize];
while ((my $line = $fh->getline()) && $counter < 1000){
#Doesn't print. Generates the above error
print @array[($counter)%$arraySize];
$counter += 1;
}
}
Я уверен, что кто-то еще мог бы объяснить в комментариях, почему он считает, что работа со ссылкой - лучший способ (пожалуйста, сделайте это), но под мантрой "будь проще" я не люблю работать с ними. Возможно, потому что я никогда не был программистом на C...