2009-04-29 45 views
32

Tôi có một hàm nhận một biến và một mảng kết hợp, nhưng dường như tôi không thể chuyển chúng sang phải. Tôi nghĩ rằng điều này có một cái gì đó để làm với khai báo chức năng, tuy nhiên tôi không thể tìm ra cách họ làm việc trong Perl. Có một tài liệu tham khảo tốt cho điều này và làm thế nào để tôi thực hiện những gì tôi cần?Làm cách nào để chuyển một hàm băm vào một hàm trong Perl?

Tôi nên thêm rằng nó cần phải được chuyển qua tham chiếu.

sub PrintAA 
{ 
    my $test = shift; 
    my %aa = shift; 
    print $test . "\n"; 
    foreach (keys %aa) 
    { 
     print $_ . " : " . $aa{$_} . "\n"; 
     $aa{$_} = $aa{$_} . "+"; 
    } 
} 
+0

Bạn có thể hiển thị mã cho chúng tôi không? –

+0

Bạn có thể hiển thị mã gọi cho chúng tôi không? –

Trả lời

60

Chuyển tham chiếu thay vì bản thân hàm băm. Như trong

PrintAA("abc", \%fooHash); 

sub PrintAA 
{ 
    my $test = shift; 
    my $aaRef = shift; 

    print $test, "\n"; 
    foreach (keys %{$aaRef}) 
    { 
    print $_, " : ", $aaRef->{$_}, "\n"; 
    } 
} 

Xem thêm perlfaq7: How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}?

+0

Mặc dù không phải như thế nào tôi nghĩ rằng nó cần phải được thực hiện, đây là phương pháp dễ nhất. – rlbond

4

Dường như bạn nên vượt qua trong một tham chiếu đến một băm.

sub PrintAA 
{ 
    my $test = shift; 
    my $aa = shift; 
    if (ref($aa) != "HASH") { die "bad arg!" } 
    .... 
} 

PrintAA($foo, \%bar); 

Lý do bạn không thể làm một

my %aa = shift; 

là vì Perl flattens tất cả các đối số cho một chương trình con vào một danh sách, @_. Mỗi phần tử được sao chép, vì vậy việc chuyển vào tham chiếu cũng tránh được các bản sao đó.

12

Hoặc:

sub PrintAA 
{ 
    my $test  = shift; 
    my %aa   = @_; 
     print $test . "\n"; 
     foreach (keys %aa) 
     { 
       print $_ . " : " . $aa{$_} . "\n"; 
       $aa{$_} = $aa{$_} . "+"; 
     } 
} 

Điều bạn về cơ bản còn thiếu là một mảng kết hợp không phải là một đối số duy nhất (mặc dù một tham chiếu mảng kết hợp là, như trong câu trả lời Paul Tomblin của).

3

Như thường lệ, có một số cách. Đây là những gì Perl Best Practices, mà tôn kính nhất của con trỏ phong cách, đã nói về truyền thông số chức năng:

Sử dụng một hash của các đối số được đặt tên cho bất kỳ chương trình con đó có hơn ba thông số

Nhưng kể từ bạn chỉ có hai, bạn có thể xoay xở;) với truyền chúng trực tiếp như thế này:

my $scalar = 5; 
my %hash = (a => 1, b => 2, c => 3); 

func($scalar, %hash) 

Và hàm được định nghĩa như thế này:

sub func { 
    my $scalar_var = shift; 
    my %hash_var = @_; 

    ... Do something ... 
} 

Có thể hữu ích hơn nếu bạn có thể hiển thị một số mã.

15

Mã này hoạt động:

#!/bin/perl -w 

use strict; 

sub PrintAA 
{ 
    my($test, %aa) = @_; 
    print $test . "\n"; 
    foreach (keys %aa) 
    { 
     print $_ . " : " . $aa{$_} . "\n"; 
    } 
} 

my(%hash) = ('aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA); 

PrintAA("test", %hash); 

Điểm mấu chốt là việc sử dụng bối cảnh mảng trong tôi() 'tuyên bố' trong hàm.


Doanh nghiệp ngữ cảnh mảng thực sự làm gì?

Nhỏ gọn, nó hoạt động chính xác.

Điều đó có nghĩa là giá trị đầu tiên trong mảng đối số @_ được gán cho $test và các mục còn lại được gán cho hàm băm %aa. Theo cách tôi gọi, có một số mục lẻ trong số @_, vì vậy khi mục đầu tiên được gán cho $test, thậm chí có một số mục có sẵn để gán cho %aa, với mục đầu tiên của mỗi cặp là ('aaa', 'bbb', 'ccc' trong ví dụ của tôi) và thứ hai là giá trị tương ứng.

Có thể thay thế %aa bằng @aa, trong trường hợp đó, mảng sẽ có 6 mục trong đó. Cũng có thể thay thế %aa bằng $aa và trong trường hợp đó, biến số $aa sẽ chứa giá trị 'aaa' và các giá trị còn lại trong @_ sẽ bị bỏ qua bởi nhiệm vụ.

Nếu bạn bỏ qua các dấu ngoặc đơn xung quanh danh sách biến, Perl từ chối biên dịch mã. Một trong những câu trả lời thay thế cho thấy ký hiệu:

my $test = shift; 
my(%aa) = @_; 

Điều này tương đương với những gì tôi đã viết; sự khác biệt là sau hai câu lệnh my, @_ chỉ chứa 6 phần tử trong biến thể này, trong khi trong một phiên bản my duy nhất, nó vẫn chứa 7 phần tử.

Chắc chắn có các câu hỏi khác trong SO về ngữ cảnh mảng.


Trên thực tế, tôi đã không hỏi về my($test, %aa) = @_; Tôi đã hỏi về my(%hash) = ('aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA); so my %hash = { 'aaa' => 1, ... };

Sự khác biệt là các {...} ký hiệu tạo ra một ref băm và (...) ký hiệu tạo ra một danh sách, ánh xạ tới một giá trị băm (trái ngược với hàm băm). Tương tự, [...] tạo ra một mảng ref và không phải là một mảng.

Thật vậy, hãy thay đổi mã 'chính' để đọc: my (% hash) = {...}; và bạn sẽ có được một thời gian chạy (nhưng không phải thời gian biên dịch) lỗi - số dòng điều trị một cách thận trọng kể từ khi tôi đã thêm mã khác thay thế cho tập tin của tôi:

Reference found where even-sized list expected at xx.pl line 18. 
... 
Use of uninitialized value in concatenation (.) or string at xx.pl line 13. 
+2

TMTOWTDI, nhưng tôi thích cách tiếp cận này. – spoulson

+0

Doanh nghiệp ngữ cảnh mảng thực sự làm gì? –

+0

Thực ra, tôi không hỏi về số của tôi ($ test,% aa) = @_; Tôi đã hỏi về my (% hash) = ('aaa' => 1, 'bbb' => 'quả bóng', 'ccc' => \ & PrintAA); so với % băm của tôi = {'aaa' => 1, ...}; –

3

Tất cả các phương pháp trên làm việc, nhưng điều này luôn luôn là con đường Tôi thích làm những việc như sau:

sub PrintAA ($\%) 
{ 
    my $test  = shift; 
    my %aa   = ${shift()}; 
    print "$test\n"; 
    foreach (keys %aa) 
    { 
     print "$_ : $aa{$_}\n"; 
     $aa{$_} = "$aa{$_}+"; 
    } 
} 

Lưu ý: Tôi cũng đã thay đổi mã của bạn một chút. Các chuỗi được trích dẫn kép của Perl sẽ giải thích "$test" là giá trị của $test thay vì chuỗi thực tế '$test', do đó bạn không cần nhiều số . s.

Ngoài ra, tôi đã sai về cách nguyên mẫu hoạt động.Để vượt qua một hash, sử dụng này:

PrintAA("test", %hash); 

Để in một tài liệu tham khảo băm, sử dụng này:

PrintAA("test", %$ref_to_hash); 

Tất nhiên, bây giờ bạn không thể sửa đổi băm tham chiếu bởi $ref_to_hash vì bạn đang gửi một bản sao, nhưng bạn có thể sửa đổi một số %hash thô vì bạn đang chuyển nó làm tài liệu tham khảo.

+0

Bị bỏ qua vì 1) mã này không hoạt động và 2) Nguyên mẫu Perl không làm những gì mà hầu hết mọi người (dường như bao gồm cả bạn) mong đợi họ. Chúng tốt cho việc mô phỏng hành vi của các hàm dựng sẵn nhưng không sử dụng nhiều cách khác. Phần "\%" của nguyên mẫu nói rằng đối số thứ hai phải là một băm và để vượt qua nó bằng tham chiếu. Nó phải là một băm * thực *. Nó không thể là một hashref hoặc một danh sách các cặp key => value. –

+0

Cảm ơn bạn đã sửa chữa. Nó không có ý nghĩa rằng \% không thể là một tham chiếu, nhưng nó là sự thật. Tôi không nghĩ anh ta sẽ chuyển một danh sách các cặp key => giá trị, vì hàm của anh ta dường như đang sửa đổi băm mà anh ta chuyển vào như một tham số, nhưng các điểm khác là hợp lệ. –

0

Sử dụng phụ folowing để có được băm hoặc hashref - bất cứ điều gì thông qua :)

sub get_args { ref($_[0]) ? shift() : (@_ % 2) ? {} : {@_}; } 
sub PrintAA 
{ 
    my $test = shift; 
    my $aa = get_args(@_);; 
    #then 
    $aa->{somearg} #do something 
    $aa->{anotherearg} #do something 

} 

Gọi chức năng của bạn như thế này:

printAA($firstarg,somearg=>1, anotherarg=>2) 

Hoặc như thế này (không có vấn đề):

printAA($firstarg,{somearg=>1, anotherarg=>2}) 

Hoặc thậm chí như thế này (không có vấn đề):

my(%hash) = ('aaa' => 1, 'bbb' => 'balls', 'ccc' => \PrintAA); 

PrintAA("test", %hash); 

Chúc mừng!

+0

Sử dụng các nguyên mẫu có thể cho phép bạn làm điều tương tự, cũng như kiểm tra thời gian biên dịch các đối số của chương trình con của bạn. Ngoài ra, get_arg của bạn sẽ bị lừa bởi một mảng tham chiếu. –

+0

Nó là dành cho băm (ref) :) - Tôi biết nó không hoạt động với các mảng –

+0

@Chris - nguyên mẫu không có tác dụng khi một chương trình con được gọi như một phương thức, chúng rất khủng khiếp và khó hiểu và phá vỡ mọi thứ. Là người khác đã đăng trước đó, không sử dụng chúng trừ khi bạn cần tạo một công việc phụ như tích hợp sẵn. – converter42

1

Đối số cho hàm được làm phẳng thành một mảng duy nhất (@_). Vì vậy, nó thường dễ nhất để vượt qua băm để hoạt động bằng cách tham chiếu.

Để tạo một HASH:

my %myhash = (key1 => "val1", key2 => "val2"); 

Để tạo một tham chiếu đến HASH rằng:

my $href = \%myhash 

Để truy cập băm rằng bằng cách tham khảo;

%$href 

Vì vậy, trong tiểu của bạn:

my $myhref = shift; 

keys %$myhref; 
1

Tất cả các câu trả lời khác ở đây cho đến nay dường như khá phức tạp đối với tôi. Khi tôi viết hàm Perl, tôi thường "mở rộng" tất cả các đối số đã truyền trong dòng đầu tiên của hàm.

sub someFunction { 
    my ($arg1, $arg2, $arg3) = @_; 

này cũng tương tự như các ngôn ngữ khác, nơi mà bạn khai báo các chức năng như

... someFunction (arg1, arg2, arg3) 

Và nếu bạn làm điều đó theo cách đó và vượt qua băm như là đối số cuối cùng, bạn sẽ ổn thôi mà không cần bất kỳ thủ đoạn hoặc ma thuật đặc biệt. Ví dụ:

sub testFunc { 
    my ($string, %hash) = @_; 
    print "$string $hash{'abc'} $hash{'efg'} $string\n"; 
} 

my %testHash = (
    'abc' => "Hello", 
    'efg' => "World" 
); 
testFunc('!!!', %testHash); 

Kết quả được như mong đợi:

!!! Hello World !!! 

này hoạt động becaus trong lập luận Perl luôn trôi qua như một mảng các giá trị vô hướng và nếu bạn vượt qua một hash, đó là giá trị quan trọng/cặp là được thêm vào mảng đó. Trong ví dụ trên, các đối số được chuyển đến hàm dưới dạng mảng (@_) thực tế là:

'!!!', 'abc', 'Hello', 'efg', 'World' 

và '!!!'được chỉ định đơn giản là %string, trong khi %hash "nuốt" tất cả các đối số khác, luôn diễn giải một khóa làm khóa và giá trị tiếp theo là giá trị (cho đến khi tất cả các phần tử được sử dụng hết).

Bạn không thể chuyển nhiều băm theo cách đó và băm không thể là đối số đầu tiên, nếu không nó sẽ nuốt tất cả mọi thứ và để lại tất cả các đối số khác chưa được gán.

Tất nhiên chính xác cùng một công trình cho mảng là đối số cuối cùng. Sự khác biệt duy nhất ở đây là mảng không phân biệt giữa các khóa và giá trị, cho tất cả các đối số còn lại là các giá trị và chỉ được đẩy vào mảng.

Các vấn đề liên quan