מערכים קשורים#
למערך קשור יש שתי שכבות של ממשק. השכבה התחתונה - FETCH, STORE, FETCHSIZE, STORESIZE, CLEAR - היא חובה ומטפלת בגישה לאלמנטים ובשינוי גודל. השכבה העליונה - PUSH, POP, SHIFT, UNSHIFT, SPLICE, DELETE, EXISTS - היא אופציונלית ומאפשרת למערך להגיב לפונקציות המובנות התואמות; ללא הוו נתון, אותה פונקציה מובנית או נופלת חזרה לשכבה התחתונה או מתה.
התפריט המלא נמצא בעמוד הייחוס של tie. פרק זה בונה מחלקה, ולאחר מכן מסביר את פרוטוקול הגודל ואת המַשַׁנִּים האופציונליים, כאשר תזמון הקריאה שלהם אומת מול המפרש הרץ.
מחלקה שלמה#
מערך מספרי שבו חריצים ריקים נקראים בחזרה כ־0 במקום undef, ושבו STORESIZE ממלא חריצים חדשים ב־0:
use v5.36;
package ZeroFill {
sub TIEARRAY { my $class = shift; return bless { data => [] }, $class }
sub FETCH { my ($self, $i) = @_; return $self->{data}[$i] // 0 }
sub STORE { my ($self, $i, $v) = @_; $self->{data}[$i] = $v }
sub FETCHSIZE { my $self = shift; return scalar @{ $self->{data} } }
sub STORESIZE {
my ($self, $n) = @_;
my $data = $self->{data};
if ($n > @$data) {
push @$data, (0) x ($n - @$data); # pad with 0, not undef
} else {
$#$data = $n - 1;
}
}
sub CLEAR { my $self = shift; $self->{data} = [] }
sub PUSH { my $self = shift; push @{ $self->{data} }, @_; return scalar @{ $self->{data} } }
sub POP { my $self = shift; return pop @{ $self->{data} } }
}
tie my @a, 'ZeroFill';
$a[0] = 11;
$a[2] = 33; # FETCH on the skipped slot 1 reads back 0
say scalar @a; # 3
say $a[1]; # 0
$#a = 4; # STORESIZE grows the array, padding with 0
say "@a"; # 11 0 33 0 0
push @a, 44;
say pop @a; # 44
העצם הוא הפניה blessed להאש העוטפת מערך אמיתי בחריץ ה־data שלה - תבנית נפוצה, מכיוון שהמחלקה בדרך כלל רוצה מקום למצב מעבר למערך עצמו.
השכבה החובה#
TIEARRAY classname, LISTהבנאי; מחזיר הפניה blessed שהופכת לעצם הקשור.
FETCH this, index/STORE this, index, valueקריאה וכתיבה של אלמנט יחיד. אינדקס שלילי מתורגם לחיובי - זמן הריצה קורא תחילה ל־
FETCHSIZEומוסיף אותו לאינדקס - אלא אם המחלקה מציבה ב־$NEGATIVE_INDICESערך אמת, ובמקרה זה האינדקס השלילי הגולמי מגיע אלFETCHואלSTORE.FETCHSIZE thisמחזיר את מספר האלמנטים. זה עומד מאחורי
scalar @arrayומאחורי סימן הגובה העליון של$#array. זו המתודה הנקראת ביותר על מערך קשור, מכיוון שזמן הריצה זקוק לגודל כדי לנרמל אינדקסים שליליים וכדי לתחום את האיטרציה.STORESIZE this, countמציב את אורך המערך. הגדלה צריכה למלא את החריצים החדשים במושג ה״ריק״ של המחלקה (כאן,
0); הקטנה צריכה להשמיט את העודף. זה עומד מאחורי$#array = Nומאחורי שינוי הגודל המונע על ידי השמה.CLEAR thisמרוקן את המערך - מופעל על ידי
@array = ().
פרוטוקול הגודל בפועל#
FETCHSIZE ו־STORESIZE הם המקום שבו מערכים קשורים נבדלים יותר מכל דבר אחר מהאשים קשורים. שלושה דברים שכדאי לזכור:
$#aקורא דרךFETCHSIZEוכותב דרךSTORESIZE. הצבת$#a = 4קוראת ל־STORESIZE($obj, 5).חריצים שדולגו הם הבעיה של המחלקה. השמה ל־
$a[2]כאשר אורך המערך הוא 1 קוראת ל־STORE($obj, 2, ...)ישירות - זמן הריצה אינו קורא ל־STORESIZEכדי למלא את הפער. אם קריאה של חריץ 1 צריכה להחזיר0ולאundef, עלFETCHלומר זאת (ה־// 0בדוגמה).EXTENDהוא מייעץ. כאשר זמן הריצה מצפה שמערך יגדללמשל ממש לפני השמה גדולה - הוא עשוי לקרוא ל־
EXTEND this, countכרמז להקצאה מוקדמת. תמיד בטוח להשאיר אתEXTENDבלתי־מוגדר או ריק; זוהי הזדמנות אופטימיזציה גרידא, וגרימה לו להתנהג כמוSTORESIZEהיא באג, מכיוון שזמן הריצה קורא ל־EXTENDבזמנים שבהם הוא למעשה אינו רוצה שהאורך הנראה ישתנה.
המַשַׁנִּים האופציונליים#
כל אחת מ־push, pop, shift, unshift, splice, delete, ו־exists מנתבת אל הוו האותיות הגדולות בעל אותו השם כאשר המחלקה מגדירה אותו. תזמון הקריאה שלהן, שאומת מול המפרש:
push @a, 1, 2→ קריאתPUSHאחת עם כל הרשימה.pop @a→POP;shift @a→SHIFT;unshift @a, 0→UNSHIFT.splice @a, 1, 1, 'x', 'y'→ קריאתSPLICEאחת הנושאת היסט, אורך, ואת רשימת ההחלפה; ערך ההחזרה שלה הופך לזה שלsplice.exists $a[0]→EXISTS;delete $a[0]→DELETE.
כאשר הוו מַשַׁנֶּה נעדר, ההתנהגות תלויה באופרטור. Tie::Array (להלן) מספק נפילות חזרה לחמשת הנפוצים במונחי השכבה החובה; ללא מחלקת בסיס זו, קריאה למַשַׁנֶּה שאינו ממומש מתה בנקודת השימוש.
תזמון הקריאה#
$a[$i]→FETCH;$a[$i] = $v→STORE.scalar @a, נרמול אינדקס שלילי →FETCHSIZE.$#a = $n, שינוי גודל של@a = (...)→STORESIZE(ו־CLEARתחילה עבור השמה של המערך כולו).push/pop/shift/unshift/splice/delete/exists→ הוו האותיות הגדולות התואם כאשר הוא מוגדר.פרוסת מערך
@a[1, 3]קוראת ל־FETCHפעם אחת לכל אינדקס; אין הוו לפרוסה.
מחלקה מינימלית בת־קיימא#
המערך הקשור הקטן ביותר מגדיר TIEARRAY, FETCH, STORE, FETCHSIZE, ו־STORESIZE. עם חמשת אלה, גישה לאלמנטים, scalar @a, $#a, והאיטרציה כולם עובדים. הוסיפו CLEAR כדי ש־@a = () יתנהג כראוי. הוסיפו את המַשַׁנִּים רק עבור הפונקציות המובנות שהקוראים שלכם משתמשים בהן בפועל - או ירשו אותם.
הדרך הקלה: לרשת מ־Tie::Array#
Tie::Array ממש את PUSH, POP, SHIFT, UNSHIFT, ו־SPLICE במונחי החמישה החובה, כך שתת־מחלקה המספקת TIEARRAY, FETCH, STORE, FETCHSIZE, ו־STORESIZE מקבלת את כל מַשַׁנֵּי הרשימה בחינם:
package Logging {
use Tie::Array;
our @ISA = ('Tie::Array');
sub TIEARRAY { my $class = shift; return bless { data => [] }, $class }
sub FETCH { my ($self, $i) = @_; return $self->{data}[$i] }
sub STORE { my ($self, $i, $v) = @_; warn "store $i\n"; $self->{data}[$i] = $v }
sub FETCHSIZE { my $self = shift; return scalar @{ $self->{data} } }
sub STORESIZE { my ($self, $n) = @_; $#{ $self->{data} } = $n - 1 }
}
tie my @log, 'Logging';
push @log, 'x', 'y'; # PUSH inherited; calls STORE twice (warns)
say "@log"; # x y
push @log, 'x', 'y' עובד ללא מתודת PUSH מכיוון שהמתודה הנירשת מתרחבת לשתי קריאות STORE - ולכן הדוגמה מתריעה פעמיים. Tie::Array אינו מגדיר את TIEARRAY בעצמו, ולכן תת־המחלקה חייבת לספק את הבנאי; ה־DELETE וה־EXISTS המוגדרים כברירת מחדל פשוט עושים croak, אז דרסו אותם אם הקוראים שלכם זקוקים ל־delete ול־exists.