Rust/משתנים
Rust |
---|
|
על מנת שתכנית תוכל לשמור מידע[ לעבד אותו או לקבל החלטות הנוגעות אליו, יש לשמור את המידע במשתנים. משתנים הם כמעין "תיבות" שבהן שומרים דברים. לכל משתנה יש שם ותוכן. שם המשתנה מאפשר לנו לגשת למידע. תוכן המשתנה הוא הערך שאותו אנו מחפשים.
מה זה משתנים?
עריכהמשתנים הם תאי זיכרון בהם אנחנו משתמשים לצרכינו. משתנים יכולים להחזיק סוגים רבים של מידע: מספרים, אותיות, מילים, וכן הלאה. כמעט בכל פעולה אותה נרצה לבצע יהיו מעורבים משתנים. על המשתנים ניתן לבצע פעולות שונות, אותן נראה בהמשך. באופן ציורי, ניתן לתאר את זיכרון המחשב הזמין כמספר רב של תיבות ריקות בהן ניתן להשתמש. כאשר אנו מגדירים משתנה, אנו בעצם משתמשים באחת מהקופסאות האלה, ומדביקים עליה תווית - שם. לכל משתנה חייב להיות שם ובכל משתנה נוכל לאחסן מידע מסוים. לאחר שהגדרנו משתנה, נוכל אם נרצה לבדוק או לקחת את תוכן הקופסה וזאת על ידי שימוש בתווית המתאימה (השם שהגדרנו).
הצהרה על משתנים
עריכהכדי להצהיר על קיומו של משתנה יש להשתמש במילה השמורה let.
let x = 5;
כפי שאמרנו מקודם לכל משתנה יש שם (תווית) וניתן לאחסן בו ערך מסויים. בקטע זה הגדרנו את קיומו של משתנה ששמו x, ואתחלנו את הערך שלו למספר 5. כעת, במקום להשתמש במספר 5, נוכל להשתמש במשתנה x. שימו לב שגם כאן, השורה מסתיימת בנקודה-פסיק (;), דבר שמראה לקומפיילר שהפעולה שביקשנו ממנו לעשות הסתיימה ושהוא יכול לעבור לפעולה הבאה.
טיפוסי משתנים
עריכהרבים מכם שעסקו בתכנות בסיסי בשפה אחרת יגידו ודאי שזה לא מספיק להצהיר על משתנים, אלא שיש להצהיר גם על הטיפוס שלהם. מהו בעצם הטיפוס של המשתנה? כפי שכבר אמרנו מקודם, משתנה יכול להכיל בתוכו ערכים מסוגים רבים. טיפוס המשתנה נועד לתת למחשב מידע על סוג המידע שהתא מאחסן - כלומר, אם מדובר במספר, אות או מילה. לכן, בשפות תכנות רבות חשוב להצהיר על הטיפוס של המשתנה (כלומר הסוג של המידע המאוחסן בו) כדי להבין איזה סוג ערכים הוא יכול לקבל.
עם זאת, כפי שראיתם בדוגמה מקודם לא הצהרנו על הטיפוס של המשתנה x, למרות שברור לכולם שהוא מסוג מספר ואם רוצים להיות יותר ספציפיים, מספר שלם (Integer). איך הדבר מסתדר עם הבטיחות לכאורה של שפת ראסט - הרי אמרנו מקודם ששפת ראסט היא שפה מאוד בטוחה ואם אנחנו לא מצהירים על סוג המשתנה, בטח הדבר יכול לגרום לנו לבעיות בהמשך. התשובה לשאלה היא שיש דרך להצהיר על משתנים, אבל כמעט ולא משתמשים בה משום שהיא מובנית בקומפיילר. כלומר, הקומפיילר מצליח לזהות בעצמו את הטיפוס של המשתנה ולכן ברוב המקרים אין צורך להצהיר סוג התוכן שמאוחסן בו.
באותו אופן, בדוגמה הקודמת יכולנו להגדיר את x כך:
let x: i32 = 5;
שורה זו מגדירה משתנה בשם x שהטיפוס שלו הוא i32 (כלומר שהתוכן שבו יהיה מספר שלם - Integer בעל 32 ביטים). הערך המאותחל (כלומר המספר ההתחלתי אותו אחסנו במשתנה) הוא 5. סוג הטיפוס מצוין לאחר הנקודותיים (:). למעשה, המשתנה שהגדרנו בשורה זו זהה למשתנה שהגדרנו מקודם. כשאנחנו מגדירים משתנה x מסוג מספר שלם ללא טיפוס, ברירת המחדל של הקומפיילר היא להגדיר אותו בתור 32 ביטים. עם זאת, ניתן להגדיר משתנים אחרים בעלי מספר שונה של ביטים.
נעיין בקטע הקוד הבא:
let x = 5;
let y: i16 = 5;
אמנם המשתנים x ו-y זהים אחד לשני בערך המאותחל שלהם, אבל הטיפוס שלהם שונה. x מוגדר כברירת מחדל בתור מספר שלם בעל 32 ביטים (כלומר הוא תופס 32 ביטים - 4 תאים בזיכרון), בעוד את y הגדרנו ידנית בתור מספר שלם בעל 16 ביטים (כלומר הוא תופס רק 2 תאים בזיכרון).
סוגי טיפוסים
עריכהכפי שציינו קודם, בשפת ראסט יש המון טיפוסים של משתנים. בטבלה זו נמנה את הטיפוסים העיקריים:
טיפוסיים עיקריים בראסט | |||
---|---|---|---|
קבוצת הטיפוס | סוג הטיפוס | טווח | הערות |
תו בודד | char | בשפת ראסט המבנה של char הוא ארבעה בתים ולא אחד, כך שהטיפוס תומך לא רק בתווים בטבלת ASCII (אסקי), אלא גם ביוניקוד ובתווים מיוחדים. | |
משתנה בוליאני | bool | true או false | משתנה בוליאני הוא משתנה המכיל אחד משני ערכים - true (אמת) או false (שקר). שימושי בעיקר בתנאים לוגיים. |
מספרים שלמים (integers) | i8 | -128 עד 127 | בגודל בית אחד. |
i16 | -32768 עד 32767 | בגודל שני בתים. | |
i32 | -2147483648 עד 2147483647 | ברירת המחדל של משתנה מסוג מספר שלם כשלא מצהירים על טיפוס. בגודל ארבעה בתים. | |
i64 | -922337203854775808 עד 9223372036854775807 | בגודל שמונה בתים. | |
מספרים עשרוניים או מספרי נקודה צפה (float) | f32 | משמשים לייצוג מספרים עשרוניים (כמו 7.3535 או 101.01). | |
f64 | |||
משתנים חסרי סימן (unsigned) | u8 | 0 עד 255 | משמשים לייצוג מספרים חיוביים בלבד. |
u16 | 0 עד 65535 | ||
u32 | 0 עד 4294967295 | ||
u64 | 0 עד 18446744073709551615 |
דרך ההצהרה על משתנה לפי הטיפוס שלו (כאשר <type> הוא הטיפוס ו-<value> הוא הערך המאותחל):
let x: <type> = <value>;
שימו לב שהטבלה לא כוללת את כל הטיפוסים בשפת ראסט, אלא רק את הטיפוסים שעליכם להכיר לבינתיים. בהמשך נכיר ונעבוד עם משתנים מטיפוסים אחרים.
כדאי לדעת: כל הטיפוסים שהצגנו בטבלה לעיל מכונים "טיפוסים פרימיטיביים" וזאת משום שהם משמשים לאחסון של מבני הנתונים הבסיסיים ביותר ושהקומפיילר יודע כמה ביטים הוא צריך להקצות להם מראש. בהמשך נלמד גם על "טיפוסים מורכבים" שכשמם, הם בעלי מבני נתונים מורכבים יותר ושלעתים הקומפיילר לא יודע כמה ביטים הוא צריך להקצות עבורם. |
Mutable לעומת Immutable
עריכהננסה להריץ את הקוד הבא:
let x = 5;
x = 10; // error: x is immutable
קוד זה מנסה לבצע פעולת השמה למשתנה x, כלומר מתן ערך חדש למשתנה. פעולת ההשמה היא הפעולה הבסיסית ביותר בעבודה עם משתנים ולעתים קרובות נרצה להעניק למשתנים ערך חדש - הרי זו היא הסיבה שמשתנים נקראים משתנים, סביר להניח שנשנה את הערך שלהם בזמן הריצה של התוכנית. עם זאת, כפי שוודאי תגלו ניתקל בשגיאת קומפילציה.
כל המשתנים בראסט מוגדרים כברירת מחדל בתור Immutable (כלומר, לא ניתנים לשינוי אחרי האתחול שלהם). לכן, כאשר ניסינו לבצע פעולת השמה למשתנה x הקומפיילר דיווח לנו על שגיאה - לא ניתן לשנות את ערך המשתנה לאחר האתחול שלו. אבל אם אנחנו בכל זאת רוצים לשנות את הערך של x ולבצע פעולת השמה, מה נעשה? לשם כך יש להוסיף בעת ההצהרה על המשתנה את המילית "mut" (קיצור של המילה mutable - ניתן לשינוי).
let mut x = 5;
x = 10;
כפי שתראו בדוגמה, לא נקבל שגיאת קומפילציה ופעולת ההשמה לתוך x אכן מתרחשת. אז מדוע בעצם כל המשתנים בראסט מוגדרים כברירת מחדל בתור immutable? האמת שאין לכך סיבה ממשית, אבל הדבר יכול להעיד על אחת התכונות החשובות ביותר של ראסט - בטיחות. אם נשכח להוסיף את המילית "mut" בעת הצהרה על משתנה וננסה לבצע פעולת השמה, הקומפיילר יזהה את הבעיה ויזהיר אותנו מפניה. אולי קרה ששינינו בטעות משתנה שאנחנו בכלל לא רוצים לשנות אותו.
כדאי לדעת: ראינו שהסימן '=' משמש אותנו לביצוע פעולת ההשמה. סימן זה, כמו סימנים אחרים שנלמד בהמשך, מכונה "אופרטור". האופרטורים הם בדרך כלל פונקציות שהמתכנת הרגיל משתמש בהן פעמים רבות לאורך התוכנית שלו ולכן הוקצה להם סימן מיוחד (של תו בודד או מספר תווים) כדי להקל על המתכנת בעת כתיבת קוד.כל האופרטורים מחזירים ערך מסוים לתוכנית; למשל האופרטור '+' שמשמש לחיבור, מחזיר את תוצאת החיבור (הסכום) של שני משתנים. במקרה שלנו, האופרטור '=' שמשמש להשמה, מחזיר את הערך המושם למשתנה. |
קונבנציה (מוסכמה): למען הסדר והאחידות, תמיד נצהיר על משתנים בתחילת הקוד. בעזרת דרך עבודה זו, נוכל להסתכל על המשתנים שההדרנו שוב, בסיום כתיבת התוכנית, ולראות שהשתמשנו בכולם כפי שרצינו מכלכתחילה. אולי היה משתנה אחד שהגדרנו אותו בהתחלה ובסופו של דבר בכלל לא השתמשנו בו? אולי היה משתנה שהגדרנו אותו כ-mutable, אך התברר לנו שלא היינו צריכים לשנות אותו בכלל בתוכנית ואפשר היה להגדיר אותו כ-immutable? על שני המקרים האחרונים גם הקומפיילר ייתן לנו הודעה. |
משתנים ללא ערך מאותחל
עריכהכפי שציינו מקודם, כאשר אנו מצהירים על משתנה עם ערך מאותחל, המחשב מקצה לו כתובת מסוימת בזיכרון שלו (שגודלה תלוי בטיפוס המשתנה) ולתוכה הוא מכניס את הערך המאותחל של המשתנה (הערך אותו אנו מגדירים). כאשר התוכנה תסתיים, היא תחדל להשתמש בכתובות הזיכרון שהוקצו לה, ותחזיר אותן למחשב שיכול עתה להקצות אותן לתוכנות חדשות. כשהמחשב מקבל את הכתובות בחזרה מן התכונה, הוא אינו מבצע ניקוי לזיכרון שנתן ולכן בכתובות אותן קיבל בחזרה עשוי להישמר ערך מסויים מהפעם האחרונה שהמחשב הקצה אותן לשימוש כלשהו על ידי תוכנה. למעשה, אם נעבור על הזיכרון נראה שאין במחשב שאין בו ערך כלשהו.
הערכים הנשארים בתאי הזיכרון של המחשב מפעולת הריצה האחרונה של תכונה כלשהי מכונים "ערכי זבל". למעשה, כשאנו מגדירים משתנה עם ערך התחלתי, המחשב דורס את הערך הקודם (ערך הזבל) שהיה בכתובת הזו ומציב במקומו את הערך החדש. עם זאת, רוב שפות התכנות מאפשרות לנו להגדיר גם משתנה ללא ערך מאותחל - משתנה שלא הכנסנו אליו ערך התחלתי ושסביר להניח שנשנה אותו רק בהמשך התוכנית.
מכיוון שלא הכנסנו למשתנה ערך התחלתי, המחשב לא מכניס לכתובת שלו בזיכרון ערך כלשהו ולכן כרגע בתוך הכתובת של המשתנה נמצא עדיין ערך הזבל שנשאר בזיכרון מתוכנית קודמת. גם שפת ראסט מאפשרת לנו להגדיר משתנים ללא ערך מאותחל. בעת הצהרה על משתנה ללא ערך מאותחל בראסט, חשוב להעביר את טיפוס המשתנה כדי שהקומפיילר ידע כמה ביטים להקצות לו בזיכרון. לדוגמה:
let x: i32;
נבהיר עתה אספקט נוסף של הבטיחות בראסט. כפי שאמרנו קודם, אחד היתרונות המרכזיים של ראסט הוא שמדובר בשפה בטוחה ולכן איננו יכולים לגלוש לחלקים מסוכנים בזיכרון כמו בשפות אחרות. הדבר מתבטא בין היתר בדרך הטיפול של ראסט בערכי זבל: אמנם ראסט מאפשרת לנו להגדיר משתנים לא מאותחלים, אך בניגוד לשפות אחרות היא איננה מאפשרת לנו לגשת לערכי הזבל שנשארו בזיכרון. לדוגמה, אם נגדיר משתנה ללא ערך מאותחל וננסה להדפיס את ערך הזבל שנשאר בו, נקבל מהמהדר שגיאת קומפילציה שהוא איננו מאפשר לנו לגשת לזיכרון שלא אתחלנו בעצמנו.
קונבנציה (מוסכמה): תמיד נאתחל משתנים עם ערך מסוים בעת ההצהרה, גם אם אנחנו עדיין לא יודעים איזה ערך עתידי המשתנה יכיל.• משתנים מטיפוס מספר שלם (Integers) ומשתנים חסרי סימן (Unsigned) יאותחלו תמיד עם המספר 0. • משתנים מטיפוס מספר עשרוני (Float) יאותחלו תמיד עם הערך 0.0 (הערך 0 ללא נקודה מתאים לטיפוסים אחרים). • משתנים מסוג תו (char) יאותחלו תמיד עם התו '\0' שמסמל תו ריק. |
הפרק הקודם: שלום עולם! |
משתנים | הפרק הבא: פלט והצללה |