Rust/מערכים

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי
Rust

בשיעור זה נלמד על מערכים. מערך הוא מבנה נתונים שמאפשר שמירה של משתנים רבים מאותו טיפוס תחת אותו שם, כאשר כל אחד מהמשתנים מקבל מספר מזהה ייחודי. שימוש במערכים מאפשר עבודה נוחה עם מידע שמורכב מחלקים רבים הזהים זה לזה.

מהו מערך?[עריכה]

בשיעור על משתנים, עמדנו על כך שאפשר לחשוב על משתנה כ"תיבה", שאפשר לשים בה ערכים. באופן דומה, אפשר לחשוב על מערך כעל "שורת תיבות". לשורת התיבות יש שם (באופן דומה לכך שלמשתנה יש שם), ואפשר לגשת לתיבה ספציפית על ידי ציון מספר התיבה בשורה.

בתרשים הבא, לדוגמה, מוצג המשתנה grade, שהוא כעין תיבה (המורכבת משני בתים, לצורך העניין מסוג u16), ובתוכה כעת המספר 80. בתרשים גם מוצג המערך grades, מערך באורך ארבע, שהוא כעין שורה של ארבע תיבות (שכל אחת מהן מורכבת משני בתים), ובתיבות כעת המספרים 90, 80, 56, ו-100.

משתנה ומערך
משתנה ומערך

הגדרת מערך[עריכה]

הצהרה על מערך תעשה בצורה הבאה:

let <name>: [<type>; <size>] = [ <values> ];

כאשר name (שם המערך) הוא השם שאנו בוחרים למערך כולו, type (טיפוס המערך) הוא טיפוס המשתנה של המערך ו-size (גודל המערך), שחייב להיות מספר שלם חיובי, הוא מספר האיברים שיכיל המערך. values (איברי המערך) הם האיברים שאנחנו נשים בכל אחד מתאי המערך והם יופרדו ביניהם בפסיקים, אשר יתחמו בסוגריים מרובעות.

נתבונן בדוגמת הקוד הבאה:

let array1: [char; 3] = ['a', 'b', 'c'] ;
let array2 = [1, 2, 3];
let mut array3: [f64; 10];
  1. השורה הראשונה מכריזה על מערך של תווים בשם array1 בעל שלושה איברים. האיבר הראשון הוא 'a', האיבר השני הוא 'b' והאיבר השלישי הוא 'c'. שימו לב שהמערך מוגדר כ-immutable כברירת מחדל ולכן לא נוכל לשנות את איברי המערך.
  2. השורה השנייה מכריזה על מערך של מספרים שלמים בשם array2 שגם בו שלושה איברים. שימו לב שלא חובה לציין את גודל המערך ואת סוג המשתנה מפני שכאשר מאתחלים את האיברים של המערך, הקומפיילר יודע לזהות את הסוג שלו ואת הגודל אוטומטית.
  3. השורה השלישית מכריזה על מערך של מספרים עשרוניים (float) בשם array3. בדוגמה זו איננו מאתחלים את איברי המערך. שימו לב שבניגוד לשתי הדוגמאות האחרות איברי המערך מוגדרים כ-muttable ולכן נוכל לשנות אותם במהלך התוכנית. כמו כן, כאשר אנחנו לא מאתחלים את איברי המערך, חובה לציין את סוג המערך (במקרה זה f64) ואת הגודל שלו (10), כדי שהקומפיילר ידע כמה מקום בזיכרון עליו להקצות למערך (זכרו: כל טיפוס של משתנה תופס מספר שונה של בתים בזיכרון!).

גישה לאיברי מערך[עריכה]

כדי לגשת לאיבר במערך, נשתמש באינדקס שלו - נכתוב את שם המערך ואחריו בתוך סוגריים מרובעים את המספר הסידורי של האיבר במערך:

println!("{}", array1[2]);

בדוגמה זו, הדפסנו את המשתנה שהאינדקס שלו הוא 2 במערך array1.

שימו לב כי בדומה לשפות תכנות רבות, גם בשפת Rust נהוג כי האינדקס הראשון במערך הוא 0 ולא 1. כלומר,

array1[0]

הוא האיבר הראשון במערך. כתוצאה מכך, האיבר האחרון במערך בגודל n הוא בעל המספר הסידורי n - 1. אם נצהיר על מערך בגודל 10:

let mut my_array: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

האיבר האחרון שנוכל לגשת אליו הוא האיבר באינדקס ה-9 (my_array[9]). אם ננסה לגשת לאיבר באינדקס ה-10 נקבל שגיאת קומפילציה:

my_array[10];  // error: index out of bounds: the len is 10 but the index is 10

באותו אופן, ניתן לבצע השמה לאיברי המערך:

let mut leet = [1, 2, 3, 4];
leet[1] = 3;
leet[3] = 7;

// leet is now [1, 3, 3, 7]

שימו לב שכמו כל המשתנים האחרים שלמדנו עד כה בשפת ראסט, מערכים מוגדרים כ-immutable כברירת מחדל ואם נרצה לשנות את האיברים שלהם במהלך התוכנית, נצטרך להוסיף להם את המילה mut.


כדאי לדעת:

זו לא הפעם הראשונה בה אנו רואים שהמספר הראשון המשתמש לאינדקס של משתנים הוא המספר 0. כבר נתקלנו בזה בשיעור על פלט והצללה כששלחנו ארגומנטים לפונקציית המאקרו println()!.

למה בכלל צריך מערכים?[עריכה]

נניח שהיינו רוצים לכתוב תוכנית שמדפיסה את ממוצע הציונים שתלמיד קיבל בארבעה מבחנים. עד עכשיו, בעזרת הכלים שלמדנו היינו יכולים לעשות זאת כך:

let first_grade = 90;
let second_grade = 80;
let third_grade = 56;
let fourth_grade = 100;
	
println!("The averege grade is {}",
        (first_grade + second_grade + third_grade + fourth_grade) as f64 / 4 as f64);

אבל מה אם היו לנו ציונים של 10 מבחנים? או אם המורה של התלמיד ממש הגזים והיו לנו 100 ציונים? זה היה מאוד לא נוח ליצור 100 משתנים בזה אחרי זה ואחרי כן לשלוח את כולם באותה שורה להדפסה... ובכן, בדיוק בשביל זה יש לנו מערכים!

את אותו הקוד של התוכנית למעלה יכולנו לכתוב בצורה הבאה:

let grades = [80, 90, 56, 100];
let mut sum_of_grades = 0;
let mut current_grade: usize = 0;
	
while current_grade < 4 {
    sum_of_grades += grades[current_grade];
    current_grade += 1;
}
println!("The averege grade is {}", sum_of_grades as f64 / 4 as f64);

אם היו לנו יותר ציונים, לא היינו צריכים לשנות את הקוד בהרבה:

let grades = [80, 90, 56, 100, 92, 96, 75, 80, 100, 91];
let mut sum_of_grades = 0;
let mut current_grade: usize = 0;
	
while current_grade < 10 {
    sum_of_grades += grades[current_grade];
    current_grade += 1;
}
println!("The averege grade is {}", sum_of_grades as f64 / 10 as f64);

הטיפוסים usize ו-isize[עריכה]

איך מערכים נשמרים בזיכרון?[עריכה]

מערכים - עשה ואל תעשה[עריכה]

עשה עשה - קבלת אורך המערך[עריכה]

ניתן לקבל את אורכו של המערך על ידי שימוש בפונקציה .len(). לדוגמה:

let my_array = [1, 2, 3, 4];
let x = my_array.len();
println!("the array's length is: {}", x);

// output: the array's length is: 4

אל תעשה אל תעשה - השמה למערך לא מאותחל[עריכה]

לא ניתן לבצע פעולת השמה על אף איבר במערך לא מאותחל, גם אם הוא הוגדר כ-muttable.

let mut my_array: [i32; 10];
my_array[5] = 5; // error: use of possibly uninitialized variable: `my_array`

הסיבה לכך היא שכאשר המחשב מתרגם את הקוד שלנו לשפת סף, הוא בדרך כלל משתמש באינדקס הראשון של המערך כדי לגשת לאינדקס המסויים אותו אנו רוצים. כך, אם הערך הראשון של המערך לא אותחל, המחשב יוכל לגשת לערך זבל לא מאותחל. כזכור, אחד הדגשים העיקריים של שפת ראסט הוא בטיחות לפני הכל, ולכן וכדי למנוע מצב דומה למצב שתואר, הוחלט לא לאפשר באופן גורף גישה לאיברים במערך לא מאותחל (לא משנה באיזה איבר מדובר).

עשה עשה - אתחול כל איברי המערך עם ערך קבוע[עריכה]

ניתן לאתחל את כל האיברים של מערך מסויים עם ערך קבוע:

let mut my_array = [value; length];

בדוגמה מעל, בערך value נרשום את הקבוע שאנחנו רוצים שישוכפל וב-length את האורך של המערך.
בדרך כלל נבחר לאתחל מערכים של מספרים עם המספר 0, למשל:

let mut my_array = [0; 10]; // my_array is [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

אל תעשה אל תעשה - יצירת מערך בגודל משתנה[עריכה]

לא ניתן ליצור מערך בגודל של משתנה מסויים. בהמשך נלמד מספר פתרונות להתגבר על בעיה זו.

let length = 10;
let mut my_array = [0; length]; // error: attempt to use a non-constant value in a constant

עשה עשה - יצירת מערך בגודל משתנה קבוע[עריכה]

למרות האמור בסעיף הקודם, ניתן ליצור מערך בגודל של משתנה קבוע (שהוגדר כ-const). שימו לב שהטיפוס של הקבוע חייב להיות usize.

const length: usize = 10;
let mut my_array = [0; length];

אל תעשה אל תעשה - אתחול חלק מאיברי המערך[עריכה]

לא ניתן לאתחל רק את חלק מאיברי המערך עם ערך קבוע כלשהו.

let mut array1: [0; 10] = [0; 5]; // error: expected an array with a fixed size of 10 elements, found one with 5 elements
let mut array2: [0; 8] = [0, 3; 4, 5]; // error: wrong syntax
let mut array3: [0; 8] = [[0; 3], 4, 5]; // error: wrong syntax

עשה עשה - השמת מערך אחד לתוך מערך אחר בעל גודל זהה[עריכה]

אל תעשה אל תעשה - השמת מערך אחד לתוך מערך אחר בעל גודל שונה[עריכה]

שימוש בלולאת for על מערכים[עריכה]

תרגול[עריכה]

הפרק הקודם:
פונקציות
מערכים -