Menu

פונקציות ב-C: הצהרה, קריאה והחזרת ערכים

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

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

למה פונקציות

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

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

את square כותבים פעם אחת וקוראים לה שלוש פעמים, כולל פעם אחת על התוצאה של עצמה. בעצם כבר קראתם לפונקציות: printf היא אחת, וגם main.

המבנה של פונקציה

returnType name(parameterList) {
    // גוף
    return value;
}

ארבעה חלקים:

  • טיפוס ההחזרה: הטיפוס של הערך שהפונקציה מחזירה. int, double, char, טיפוס מצביע, או void במשמעות "כלום".
  • שם: כפוף לאותם כללים של שם משתנה, וצריך לומר מה הפונקציה עושה או מפיקה.
  • רשימת הפרמטרים: הקלטים, לכל אחד טיפוס משלו, מופרדים בפסיקים. (void) פירושו שהיא לא מקבלת אף אחד.
  • גוף: הפקודות, בתוך סוגריים מסולסלים, שמסתיימות ב-return אלא אם טיפוס ההחזרה הוא void.

שימו לב ש-pi() עדיין צריכה את הסוגריים הריקים במקום הקריאה: זה מה שהופך אותה לקריאה ולא סתם לאזכור של השם. ו-printBanner(20); היא פקודה בפני עצמה, כי היא לא מפיקה ערך לשימוש.

כתבו (void), לא (), עבור פונקציה בלי פרמטרים. ב-C שתי הצורות האלה אומרות דברים שונים, ו-() היא המתירנית. עוד על זה בהמשך.

קריאה והחזרה

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

return עושה שני דברים בבת אחת: הוא מספק את הערך והוא מסיים את הפונקציה במקום. שום דבר אחרי return באותו מסלול לא רץ, וזה מה שגורם לסגנון ההחזרה המוקדמת ב-larger לעבוד: אין צורך ב-else, כי אם הגענו לשורה האחרונה, סימן ש-a > b היה שקר.

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

פונקציות void

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

בתוך פונקציית void, return; בלי ערך יוצא מוקדם. return someValue; שם הוא שגיאת קומפילציה, וכך גם שימוש בקריאה בתוך ביטוי: int x = printTable(7, 5); לא יתקמפל, וזו מערכת הטיפוסים שעושה את העבודה שלה.

פירוק לחלקים: העיקר האמיתי

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

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

שלושה כללי אצבע לחלוקת עבודה לפונקציות:

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

פונקציות שקוראות לפונקציות

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

main קוראת ל-report, שקוראת ל-sumEvens, שקוראת ל-isEven בתוך לולאה. כל קריאה מקבלת סט חדש משלה של משתנים מקומיים, שנערם מעל אלה של הקוראת, והם נזרקים כשהיא חוזרת.

ההגדרות למעלה מסודרות לפי תלות: isEven לפני sumEvens לפני report, כי C דורשת שפונקציה תוצהר לפני שקוראים לה. קראו לפונקציה שמופיעה בהמשך הקובץ והקומפיילר יתלונן על הצהרה מובלעת. הפתרון הכללי הוא אב טיפוס (prototype) בראש הקובץ, שמאפשר לסדר את ההגדרות איך שרוצים. גם פונקציה שקוראת לעצמה היא חוקית ושימושית: זו רקורסיה.

גם main היא פונקציה

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

int main(void) {
    /* ... */
    return 0;
}

מאז C99, הגעה לסוף main בלי return מחזירה 0 באופן מובלע, כלל מיוחד שחל רק על main. כל פונקציה אחרת שאינה void ומסתיימת בלי להחזיר ערך גורמת להתנהגות לא מוגדרת כשהקוראת משתמשת בתוצאה.

הצורה התקנית השנייה היא int main(int argc, char *argv[]), שמקבלת ארגומנטים משורת הפקודה. שתיהן נכונות; void main() לא נכונה, לא משנה מה אומרים מדריכים ישנים.

טעויות נפוצות

  • לשכוח return בפונקציה שאינה void. הקוראת קוראת אז ערך זבל. gcc -Wall מזהיר על כך.
  • להצהיר בתוך הגוף על משתנה עם אותו שם כמו פרמטר. הוא מסתיר את הפרמטר, וההשמה שהתכוונתם לעשות לקלט הולכת לאיבוד.
  • לצפות שפונקציה תשנה את הארגומנטים שלה. C מעבירה הכול by value, כך שפונקציה מקבלת עותקים. void reset(int x) { x = 0; } לא משנה כלום במקום הקריאה, וזה הנושא של העמוד הבא.
  • להשמיט את הסוגריים בקריאה. printBanner; הוא ביטוי חוקי שמחשב את הכתובת של הפונקציה וזורק אותה. הוא מתקמפל, לא עושה כלום, ו--Wall מסמן אותו.
  • לכתוב () במקום (void). חוקי, אבל זה מכבה את בדיקת הארגומנטים בדיאלקטים ישנים של C.

שאלות נפוצות

איך מצהירים על פונקציה ב-C?

כותבים את טיפוס ההחזרה, את השם ורשימת פרמטרים בסוגריים, ואחר כך את הגוף בתוך סוגריים מסולסלים: int add(int a, int b) { return a + b; }. פונקציה שלא מקבלת כלום משתמשת ב-(void), ופונקציה שלא מחזירה כלום מקבלת את טיפוס ההחזרה void.

מה עושה return בפונקציה ב-C?

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

למה main מחזירה int ב-C?

הערך המוחזר של main הוא קוד היציאה של התוכנית, ומערכת ההפעלה וסקריפטים של shell יכולים לקרוא אותו. return 0; פירושו הצלחה, וכל ערך שונה מאפס פירושו כישלון. מאז C99, הגעה לסוף main בלי פקודת return מחזירה 0 באופן מובלע.

האם פונקציה ב-C יכולה להחזיר יותר מערך אחד?

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

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל