由于你接受输入的方式,推到一个列表上,每个都push(@get_name, <STDIN>);
将按行读取,直到用户点击 Ctrl-D。这是一个糟糕的用户界面,但这也意味着可以将多行推送到@get_name
朋友身上。每行都是数组的一个元素。为了演示...
$ perl -we '@lines = <STDIN>; for my $line (@lines) { print "Line: $line"; }'
Foo
Bar
Line: Foo
Line: Bar
如果用户按两次输入期望输入结束,就会发生这种情况。
你想要的是my $name = <STDIN>; push @names, $name;
. 这将只读取一行$name
,然后自动停止。然后你把那个单一的名字推到@names
.
然后在输出端还有另一个问题。 printf
接受参数列表。Perl 不知道传入三个包含一个元素的列表和传入一个包含三个元素的列表之间的区别。它们都被压缩成一个大列表。因此printf FILE "%-0.25s %3.3s %4.4s\n", @get_name, @get_age, @get_gpa
,只需将所有姓名、年龄和 gpas 混合到一个大列表中,printf 只打印前三个。所以如果有两个学生,你就通过了name1, name2, age1, age2, gpa1, gpa2
。你需要一个循环。
for my $idx (0..$#get_name) {
printf FILE "%-0.25s %3.3s %4.4s\n", $get_name[$idx], $get_age[$idx], $get_gpa[$idx];
}
这表示从 0 循环到@get_name
. 这是一减去循环中的项目数。由于历史原因,数组从零开始计数。然后,您可以在每个数组中为 printf 提供第零个、第一个、第二个...条目。
更深入地讲,有三种方法可以改进您的代码。
首先是更好地使用子程序。它们可以返回结果,而不是设置全局变量的例程。
sub get_name
{
printf("Name :");
my $name = <STDIN>;
chomp($name);
return $name;
}
# @names is a better name because it describes what it contains, not how you got it
push @names, get_name;
其余的也一样。一旦你这样做了,很明显你不需要三个例程,而是一个。唯一改变的是提示。
sub get_input {
my($prompt) = @_; # this pulls in subroutine arguments
print("$prompt: "); # no need to use printf here
my $input = <STDIN>;
chomp($input);
return $input;
}
push @names, get_input("Name");
push @ages, get_input("Age");
push @gpas, get_input("GPA");
现在,您不再需要维护三个相同的例程和三个出现错误的机会,而是拥有一个。
continue
因为它是 Perl 中的关键字,所以对于子例程名称来说是一个糟糕的选择。你最好调用它main
,因为它是你程序的主要部分,一个典型的约定。
与其continue
调用自身,倒不如调用递归,这在这里非常有用但不是必需的,最好使用 while 循环。
sub main {
while(1) { # this is how you say "loop forever"
print "Please enter data about a student.\n";
push @names, get_input("Name");
push @ages, get_input("Age");
push @gpas, get_input("GPA");
print "Would you like to enter more data?\n";
my $more = <STDIN>;
last if !$more =~ m/y/; # last exits the current loop
}
print "\nEnter file name to output the data to: ";
my $data_file = <STDIN>;
open(my $fh, ">>$data_file") or die $!;
for my $idx (0..$#names) {
printf $fh "%-0.25s %3.3s %4.4s\n", $names[$idx], $ages[$idx], $gpas[$idx];
}
}
你的printf
有问题。在%3.3s
.3 部分对字符串没有任何意义。对于一个数字,它意味着将其打印到小数点后三位。但是你已经使用%s
了这意味着一个字符串。我不知道你要干什么。对于 GPA,我假设您的意图是像 2.30 一样打印。那是%.2f
(f 表示“浮点数”,这是程序员说“十进制数”的令人困惑的方式)。年龄总是一个整数,所以%3d
. 是的,d 代表...整数。
如果您想再次读回此数据并分隔字段,通常的方法是使用选项卡分隔字段。然后,您可以在重新读取时拆分选项卡上的每一行。把它们放在一起......
for my $idx (0..$#names) {
printf $fh "%s\t%3d\t%.2f\n", $names[$idx], $ages[$idx], $gpas[$idx];
}
下一步要探索的是重组数据。将每个学生的数据拆分到多个数组中,这使得协调和传递到子程序变得很尴尬。相反,您将使用哈希。
my %student;
$student{name} = get_input("Name");
$student{age} = get_input("Age");
$student{gpa} = get_input("GPA");
push @students, \%student;
@students
现在包含哈希引用列表。这有点高级,我略读了一下,但现在你可以像这样得到第一个学生的名字。
my $first_student = $students[0];
my $name = $first_student->{name};
当将它们全部打印出来时,循环看起来像这样。
for my $student (@students) {
printf $fh "%s\t%3d\t%.2f\n", $student->{name}, $student->{age}, $student->{gpa};
}
总而言之,它看起来像这样。
use strict;
use warnings;
main();
sub main {
my @students;
# Get students
while(1) { # this is how you say "loop forever"
print "Please enter data about a student.\n";
my %student;
$student{name} = get_input("Name");
$student{age} = get_input("Age");
$student{gpa} = get_input("GPA");
push @students, \%student;
print "Would you like to enter more data?\n";
my $more = <STDIN>;
last unless $more =~ m/y/; # last exits the current loop
}
# Get the file to output to
print "\nEnter file name to output the data to: ";
my $data_file = <STDIN>;
open(my $fh, ">>$data_file") or die $!;
# Dump the students into the file
for my $student (@students) {
printf $fh "%s\t%3d\t%.2f\n", $student->{name}, $student->{age}, $student->{gpa};
}
}
sub get_input {
my($prompt) = @_; # this pulls in subroutine arguments
print("$prompt: "); # no need to use printf here
my $input = <STDIN>;
chomp($input);
return $input;
}