Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Studies

הגדרות וסינטקס

מהי פונקציה?

פונקציה היא:

  1. בלוק קוד בעל שם
  2. יכולה (אך אינה חייבת) לקבל ארגומנטים מהקורא
  3. יכולה (אך אינה חייבת) להחזיר ערך מסוים
  4. מבודדת משאר התוכנית

על מנת להדגים את הקונספט, נכתוב תוכנית שמדפיסה את את ה-GCD של כל שני מספרים בין 0 ל-100:

#include <stdio.h>
#define UPPER_LIMIT 100
int main() {
    for (int i = 0; i < UPPER_LIMIT; i++) {
        for (int j = i + 1; j < UPPER_LIMIT; j++) {
            while (i != 0) {
                int tmp = i;
                i = j % i;
                j = tmp;
            }
            printf("gcd(%d, %d) = %d\n", j, i, j);
        }
    }
}

מיד ניתן לראות שיש בעיה מהותית - אנחנו משנים את i, j לאורך הריצה, למרות שהם משמשים אותנו לבצע את הספירה בין 0 ל-100! נתקן:

#include <stdio.h>
#define UPPER_LIMIT 100
int main() {
    for (int i = 0; i < UPPER_LIMIT; i++) {
        for (int j = i + 1; j < UPPER_LIMIT; j++) {
            int m = j;
            int n = i;
            while (n != 0) {
                int tmp = n;
                n = m % n;
                m = tmp;
            }
            printf("gcd(%d, %d) = %d\n", j, i, m);
        }
    }
}

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

  1. ריצה על כל המספרים בין 0 ל-100
  2. חישוב ה-GCD של אותם המספרים

לערבוב הזה יש ביטוי מובהק - בשביל לפתור את בעיית ה-GCD אנחנו צריכים להעתיק את המשתנים i ו-j ל-m ו-n. אידיאלית, היינו רוצים לפתור בצורה מבודדת את הבעיות האלו, בלי שיפריעו אחת לשנייה. לטובת כך, ניתן לכתוב פונקציה נפרדת לבעיית ה-GCD:

#include <stdio.h>
#define UPPER_LIMIT 100

int gcd(int m, int n) {
    while (n != 0) {
        int tmp = n;
        n = m % n;
        m = tmp;
    }
    return m;
}

int main() {
    for (int i = 0; i < UPPER_LIMIT; i++) {
        for (int j = i + 1; j < UPPER_LIMIT; j++) {
            int gcd_result = gcd(j, i);
            printf("gcd(%d, %d) = %d\n", j, i, gcd_result);
        }
    }
}

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

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

הגדרות

הסינטקס להגדרה של פונקציה הוא:

return_type function_name(type1 arg1, type2 arg2) {
    // Function body
    return some_value;
}

לפונקציה function_name יש ערך החזרה מטיפוס return_type, מקבלת שני ארגומנטים מטיפוסים type1 ו-type2 בהתאמה, מבצעת חישוב כלשהו ומחזירה את הערך some_value.

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

Note

אם פונקציה f קוראת לפונקציה g, הפונקציה f נקראת ה-caller והפונקציה g נקראת הפונקציה ה-callee. במקרה שלנו, main היא ה-caller ו-gcd היא ה-callee.

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

בידוד פונקציות

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

בשפת C, ארגומנטים של פונקציות מועתקים אל הפונקציה הנקראת. כלומר, m, n בפונקציה gcd הם עותקים של i, j מפונקציית ה-main - מכילים את אותו ערך, אבל במקום אחר בזכרון. כלומר, במידה ונשנה אותם, לא תהיה השפעה על המשתנים כפי שהם מופיעים בפונקצייה main. לכן ניתן לחשוב על פונקציה בתור מעין קופסא מבודדת - היא לא יכולה להשפיע על הסביבה מחוץ לה, פרק ללהחזיר ארגומנט כלשהו.

Note

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