בואו נסתכל על התוכנית הכי פשוטה ובסיסית שאפשר להעלות ללוח ארדואינו מסביבת הפיתוח שלו:
void setup() { } void loop() { }
מה שיש לנו כאן זה בעצם הגדרה של שתי פונקציות, אחת בשם setup ואחת בשם loop. בהמשך, כשאדבר על פונקציות, אסביר את המשמעות של המילה void ואת הסוגריים הרגילים. בינתיים, נתמקד בסוגריים המסולסלים – } ו-{. בשפת C, סוגריים מסולסלים יוצרים "בלוק" של קוד, מבנה שמכיל פקודות. כמו שאפשר לראות בתוכנית למעלה, המבנה הזה יכול להיות ריק לגמרי. כמו כן, בלוק קוד יכול להכיל בלוקים אחרים, מה שאומר שהתוכנית הבאה לגיטימית לגמרי – אפילו שהיא מטופשת לחלוטין…
void setup() { {} {} {} {} {} {} {} {{{ {} }}} {} } void loop() { { { { } } } }
שימו לב שצורת הסידור של הסוגריים המסולסלים לא חשובה: הם יכולים להיות באותה שורה או בשורות נפרדות, עם או בלי רווחים. מה שחשוב זה שלכל פתיחה של סוגריים מסולסלים ("}") תהיה סגירה תואמת ("{") איפשהו אחריה. אם תשכחו אחד מהשניים, סביבת הפיתוח תפיק הודעות שגיאה מוזרות כמו "error: expected `}' at end of input" או "error: expected declaration before '}' token".
למעשה, כל תוכנית בשפת C היא בלוק קוד אחד גדול – אבל את "בלוק האב" המיוחד הזה אנחנו לא מסמנים בסוגריים מסולסלים. אם ננסה לעשות זאת, נקבל הודעת שגיאה. הקומפיילר, שלוקח את התוכנית שכתבנו והופך אותה למשהו שהמעבד יכול להריץ, עובד עם היררכיה ברורה מאד:
בקוד C "קלאסי", יש רק פונקציית חובה אחת ושמה חייב להיות main. בסביבת הפיתוח של הארדואינו יש שתי פונקציות חובה – setup ו-loop שראינו קודם. כל פונקציה אחרת שנכתוב היא רשות, בחירה שלנו בהתאם למבנה התוכנית הרצוי.
מהאיור למעלה אפשר להסיק שתי מגבלות חשובות: אי אפשר להגדיר פונקציה בתוך פונקציה (אפילו אם היא "מתחבאת" בתוך בלוק פנימי), ואי אפשר ליצור בלוק רגיל מחוץ לפונקציה. מה שכן אפשר לעשות זה ליצור הרבה פונקציות נפרדות בתוך "בלוק האב", והרבה בלוקים נפרדים בתוך פונקציות או בתוך בלוקים אחרים – בדיוק כמו שעשינו בקוד לדוגמה שהופיע קודם.
כל הסידור הזה של הבלוקים הוא לא סתם עניין של אסתטיקה. מבחינת הקומפיילר, כל בלוק – ולא משנה אם זו פונקציה בעלת שם או בלוק רגיל ואנונימי – הוא יחידה עצמאית של קוד. אחת המשמעויות החשובות של הדבר היא שאם הגדרנו משתנה בתוך בלוק מסוים, הקוד שמחוץ לאותו בלוק לא יכיר את המשתנה הזה! הנה דוגמה קצת מורכבת, אז שימו לב:
int global; // Create a variable void setup() { int local; global = 1; // Set the value of "global" to 1 local = 1; } void loop() { global = global + 1; // Increase the value of "global" by 1 }
כשאנחנו כותבים int ואחריו שם כלשהו, נניח x, אנחנו מגדירים משתנה מספרי ששמו x (אל תדאגו, בפוסטים הבאים אני אסביר מאד לעומק מה זה משתנה ומה זה int). למשל, בקוד למעלה הוגדר משתנה מספרי בשם global בבלוק האב, ומשתנה בשם local הוגדר בתוך הפונקציה setup. התוכנית הזו תעבוד בלי שום בעיה: כל פונקציה עובדת עם משתנים שהוגדרו או בבלוק האב (שמכיל את הפונקציות) או בתוך הפונקציה עצמה. לעומת זאת, אם ננסה לגשת למשתנה local מתוך הפונקציה loop, ככה:
void loop() { global = global + 1; local = local + 1; }
נקבל הודעת שגיאה: "error: 'local' was not declared in this scope". למה? כי המשתנה local הוגדר מקומית, רק בתוך הפונקציה setup. הפונקציה loop היא בלוק חיצוני ל-setup, מחוץ לטווח (Scope) של הפקודות שבתוך setup, ולכן אין לה גישה למשתנה הזה.
כזכור, בלוק האב הוא העליון בהיררכיה: אין שום דבר מחוצה לו, הכל נמצא בתוכו (אנחנו מתעלמים בינתיים מקובצי ספריה – זה נושא מתקדם הרבה יותר). לכן, משתנה שהוגדר ישירות בבלוק האב, כמו global בדוגמה למעלה, הוא משתנה גלובלי שמוכר בכל הפונקציות ובכל הבלוקים שבתוכנית. לעומתו, local הוגדר מקומית: הוא לוקאלי, מוכר רק בתוך הטווח (Scope) של הפונקציה/בלוק בו הוא הוגדר ומה שבתוכם.
אסור לנו להגדיר שני משתנים גלובליים בעלי שם זהה, ואסור לנו להגדיר שני משתנים לוקאליים בתוך אותו בלוק בעלי שם זהה. אנחנו כן יכולים להגדיר משתנים לוקאליים בעלי שם זהה בתוך בלוקים נפרדים, ואנחנו יכולים להגדיר משתנים לוקאליים עם שם זהה למשתנים גלובליים. במקרה זה, המשתנה הלוקאלי מקבל קדימות, ובתוך אותו בלוק הקומפיילר פשוט מתעלם מהגלובלי.
שיעורי בית
מעבר למה שהוסבר עד כה, יש שני דברים נוספים שאתם צריכים לדעת:
- כאשר מגדירים משתנה מספרי ולפני שנותנים לו ערך בעזרת הסימן "=", ערך ברירת המחדל שלו הוא אפס
- אחרי שסיים את הגדרות המשתנים הגלובליים, הארדואינו מבצע קודם כל מה שכתוב בפונקציה setup, ואחר כך מבצע שוב ושוב בלי סוף מה שכתוב בפונקציה loop
והנה השאלה בשבילכם. מה הערך של המשתנה x בכל אחת מהשורות שמסומנות בכוכבית? אם היינו מריצים את התוכנית הזו, וכל כוכבית היתה גורמת להדפסה של ערך x המעודכן לאותה שורה, איך היה נראה הפלט של התוכנית?
int x; // * void setup() { int x; // * x = 1; // * { int x; // * x = 99; // * } x = x + 1; // * } void loop() { x = x + 1; // * }
בהצלחה!
מוזר לי מה שאתה כותב.
בסביבות הפיתוח שאני מכיר (לא לארדואינו, אלא ל-windows/unix) משתנה שהוגדר ולא אותחל, מקבל את ערך הזבל שהושאר בתאי הזכרון.
האם אתה מתכוון שהקומפיילר של סביבת העבודה של הארדואינו מאתחל משתנים בעצמו?
נתחיל במה שאני יודע בוודאות: לפי התקנים של שפת C, משתנה מספרי סטטי גלובלי שלא אותחל במפורש מאותחל אוטומטית על ידי הקומפיילר ל-0 (ופוינטרים מאותחלים ל-null). זה לא אומר שכל הקומפיילרים עומדים בתקן, אבל זה התקן. המצב קצת פחות ברור לגבי משתנים "automatic", כלומר כאלה שמוגדרים בתוך פונקציות או בלוקים רגילים. בדיקות מדגמיות שערכתי על הארדואינו מראות שהוא מאפס אותם. האם אפשר לסמוך על התנהגות כזו בכל גרסה עתידית ובכל קומפיילר? בשום פנים ואופן. לכן חשוב לציין – ואני אוסיף את זה לפוסט בצורה מסודרת כאשר יהיה לי יותר מידע – שבכל מקרה לא כדאי להשתמש במשתנים לפני שקובעים להם… לקרוא עוד »
מדריך מצויין! תודה רבה עידו, תמשיך ככה.
בכיף 🙂
אוקיי, נו, אני לא באמת יכול לדעת שזה מחכה שם בלי לכתוב:
0
0
1
0
99
2
0
1
2
3
והלאה והלאה….
אני רק רוצה לחדד אותך קצת ש כאשר הוא מאתחל משתנה הוא לא אפס אין לא ערך זאת אמרת null ולכן בשביל להפוך אותו למשתנה שיכול לצבור מספר (שיש בו פעולת חיבור) חייב להיות לפני זה מספר (לכן צריך להתחל את ה X השני והראשון ב 0 אבל יש לי פה בעיה אחרת שהיא מופנת לעידו אתה לא יכול לעשות 3 משתנים שלכולם קוראים X זאת שגיאה בפני עצמה בנוסף בגלל מה שאמרתי קודם (שצריך להתחל את הראשון ב 0) משום שאחרי זה אתה מוסיף לו 1 בכל פעם (אתה לא יכול לעשות null +1) אם אני טועה אני אשמח… לקרוא עוד »
היזהר מאד מכל השוואה בין ג'אווה ל-C. למרות הדמיון המסוים בתחביר, מדובר בשפה שעובדת לגמרי אחרת כמעט בכל מובן. בקומפיילרים סטנדרטיים של C, משתנים מספריים ללא אתחול מפורש מאותחלים אוטומטית ל-0, ו-null הוא ערך ששמור לפוינטרים (אגב, מבחינת ביטים גם null זה בעצם 0). אני יכול להגדיר משתנים בעלי אותו שם כל עוד הם לא חולקים את אותו בלוק. כמובן שזו פרקטיקת תכנות שמזמינה צרות, ואם בגא'ווה אסור לעשות דבר כזה, יש סיכוי טוב מאד שאסרו על כך בדיוק בגלל זה! בכל אופן , אני שמח על ההערה שלך. כל הרעיון בסדרת הפוסטים הזו הוא להתנסות, לחקור ולשאול. אתה יותר… לקרוא עוד »
אני רואה שהבנת את הרעיון, רק שים לב שאתה מתייחס אחרת לביטוי x = x + 1 בשני המופעים שלו…
מעולה! יכולתי בעצמי ללמוד כאן C מאפס! זה באמת טוב מאוד. כל אדם חסר ניסיון שיכנס לכאן יצא כשהוא יודע C לארדואינו :). כל הכבוד שלקחת את זה על עצמך.
תודה! ואם אתה מכיר אנשים שיכולים להיעזר בפוסטים האלה, אשמח אם תפנה אותם לכאן…
תודה 🙂
מה תודה? איפה התשובות?! 😉
תודה על המשך הסדרה! שיעורי בית יש לי קודם משל עצמי… 🙂