Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Studies

הרצאה 4 - פונקציות

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

פגשנו כמה פונקציות עד כה:

  1. main - הפונקציה המיוחדת שהקומפיילר קובע בה את תחילת התוכנית. זו פונקציה שמימשנו בעצמנו ולא קראנו לה - מתברר שהקומפיילר הוא זה שקרה לה. כמו כן, אין לה ארגומנטים.
  2. printf & scanf - אלו שתי פונקציות שלא כתבנו בעצמנו, אלא הבאנו ממקום אחר ע״י השימוש במאקרו include <stdio.h>#. קראנו לפונקציות אלו באמצעות הסוגריים () והעברנו להן ארגומנטים על מנת שידעו מה להדפיס והיכן לשים קלט מהמשתמש.

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

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

זמן חיים ו-scope של משתנים

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

הגדרה: זמן החיים (lifetime) של משתנה הוא הזמן בו המשתנה קיים בזכרון. לדוגמא, נבחן את התוכנית הבאה:

int main() {
    int x = 5;
    int y = 7;
}

באופן כללי, משתנים חיים מהרגע שהוגדרו ועד סוף הבלוק בהם הוגדרו. בתוכנית הזו זמן החיים של x ארוך משל y - בשורה הראשונה ניתן להשתמש ב-x אך לא ניתן עדיין להשתמש ב-y. כאשר הגענו לסוף הפונקציה בסוגר התחתון {, שני המשתנים ״מתים״ ולא ניתן להשתמש בהם יותר. נבחן דוגמא מעט מורכבת יותר:

int func() {
    int x = 5;
    printf("%d\n", x);
}
int main() {
    int y = 7;
    func1();
    printf("%d\n", y);
}

זמן החיים של המשתנה x הוא מהשורה הראשונה של func ועד לסופה, בעוד ששל y הוא מהשורה הראשונה ב-main עד לסוף ה-main.

לנושא זמן החיים יש משמעות מהותית - למשל בתוכנית הבאה:

#define N 100
int main() {
    int i = 0;
    while(i < N) {
        char c;
        scanf("%c", &c);
        i++;
    }
    printf("last character you entered is %c\n", c);
}

מתברר שתוכנית זו לא תתקמפל בכלל. מדוע? המשתנה c מוגדר בתוך בלוק ה-while ולכן מת בסופו. לכן אי אפשר להשתמש בו לאחר שהבלוק הסתיים. בצורה שונה, אם היינו מנסים להשתמש ב-i לא הייתה בעיה בכלל - i מוגדר בתוך בלוק פונקציית ה-main ולכן אין מניעה להשתמש בו לאחר סיום ה-while.

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

הגדרה: scope (טווח ראייה) של משתנה הוא האיזור בתוכנית בו משתנה ניתן לשימוש.

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

int func() {
    int x = 5;
    printf("%d\n", x);
}
int main() {
    int y = 7;
    func1();
    printf("%d\n", y);
}

ה-scope של y הוא פונקציית ה-main וה-scope של x הוא func. אם היינו כותבים שורה כמו x = 7 ב-main או y = 2 ב-funcz, התוכנית לא הייתה מתקמפלת. הסיבה לכך? ה-scope של המשתנים מוגבל לבלוק בו הם מוגדרים.

הגדרה: משתנים המוגדרים בתוך בלוק ומתים בסופו נקראים משתנים לוקאליים. זמן החיים וה-scope של משתנים לוקאליים זהה - מהרגע שהוגדרו ועד סוף הבלוק.

כעת נציג כמה אופציות שונות למיקום ההגדרה של משתנים ונבחן את המשמעות והשימוש של כל אחד.

משתנים גלובאליים

משתנה גלובאלי הוא משתנה שלא מוכרז בתוך אף בלוק {}, אלא כשורה בפני עצמה בקובץ - לרוב בראש הקובץ. לדוגמא:

#include <stdio.h>
char c = 'a';
int main() {
    printf("global var = %c\n", c);
    c = 'b';
    printf("global var = %c\n", c);
}

זמן החיים של משתנים גלובאליים וה-scope שלהם זהה - מתחילת התוכנית ועד סופה.

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

משתנים סטאטיים

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

void count_times_called() {
    static int times_called = 0;
    printf("I was called %d times", times_called++);
}

int main() {
    count_times_called();
    count_times_called();
    count_times_called();
}

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

-->