שפת C למתחילים: בלוקים של קוד

בואו נסתכל על התוכנית הכי פשוטה ובסיסית שאפשר להעלות ללוח ארדואינו מסביבת הפיתוח שלו:

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
היררכיית הבלוקים בשפת 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) של הפונקציה/בלוק בו הוא הוגדר ומה שבתוכם.

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

שיעורי בית

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

  1. כאשר מגדירים משתנה מספרי ולפני שנותנים לו ערך בעזרת הסימן "=", ערך ברירת המחדל שלו הוא אפס
  2. אחרי שסיים את הגדרות המשתנים הגלובליים, הארדואינו מבצע קודם כל מה שכתוב בפונקציה setup, ואחר כך מבצע שוב ושוב בלי סוף מה שכתוב בפונקציה loop

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

int x; // *

void setup() {
  int x; // *
  x = 1; // *
  {
     int x; // *
     x = 99; // *
  } 
   x = x + 1; // *
}

void loop() {
  x = x + 1; // *
}

בהצלחה!

להרשמה
הודע לי על
13 Comments
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

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

מדריך מצויין! תודה רבה עידו, תמשיך ככה.

אוקיי, נו, אני לא באמת יכול לדעת שזה מחכה שם בלי לכתוב:
0
0
1
0
99
2
0
1
2
3
והלאה והלאה….

אני רק רוצה לחדד אותך קצת ש כאשר הוא מאתחל משתנה הוא לא אפס אין לא ערך זאת אמרת null ולכן בשביל להפוך אותו למשתנה שיכול לצבור מספר (שיש בו פעולת חיבור) חייב להיות לפני זה מספר (לכן צריך להתחל את ה X השני והראשון ב 0 אבל יש לי פה בעיה אחרת שהיא מופנת לעידו אתה לא יכול לעשות 3 משתנים שלכולם קוראים X זאת שגיאה בפני עצמה בנוסף בגלל מה שאמרתי קודם (שצריך להתחל את הראשון ב 0) משום שאחרי זה אתה מוסיף לו 1 בכל פעם (אתה לא יכול לעשות null +1) אם אני טועה אני אשמח… לקרוא עוד »

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

תודה 🙂

תודה על המשך הסדרה! שיעורי בית יש לי קודם משל עצמי… 🙂