מבוא לשפת C תירגול 8: פונקציות
שבוע שעבר... מערכים מיזוג מערכים ממויינים מערכים דו-ממדיים 2
תוכנייה פונקציות ברמת התקשורת הבין-אישית חלוקה לתתי בעיות בדומה למפתח של ספר קריאות גבוהה יותר של הקוד, "כמעט אנגלית" הסתרת הפרטים מהמשתמש, לדוגמא printf() ברמת התקשורת עם המחשב מחסנית הקריאות העברת פרמטרים ע"י ערך )call by value( תחום הגדרה של משתנה 3
פונקציות 4
שימוש בפונקציה הכרזה על הפונקציה double delta(double a, double b); int main() { שימוש בפונקציה double a, b, abs_diff; scanf("%lf%lf", &a, &b); absolutedifference = delta(a, b); printf("%f", absolutedifference); return 0; הגדרת הפונקציה double delta(double a, double b) { double result = a b; if (result < 0) result = -result; return result; 5
הגדרה של פונקציה המבנה של פונקציה: בסוגריים: רשימת הפרמטרים )מופרדים בפסיקים ביניהם(. גוף הפונקציה שם הפונקציה הטיפוס המוחזר ע"י הפונקציה מופיע לפני שמה. double delta(double a, double b) { double result = a b; if (result < 0) result = -result; return result; ההוראה return משמשת לקביעת הערך המוחזר על-ידי הפונקציה. משתנה מקומי: קיים רק בתוך הפונקציה, בזמן שהפונקציה פועלת. "נשכח" לאחר סיום פעולת הפונקציה. 6
פעולת RETURN פעולת return מסיימת את הרצת הפונקציה הנוכחית. ממשיכים את ההרצה מהמקום בו קראנו לפונקציה. הערך שהועבר ל- return הוא הערך שיוחזר לקורא. מוחזר תמיד עותק של הערך, אפילו אם נכתוב שם של משתנה. כאשר הפונקציה לא מחזירה ערך )טיפוס מוחזר,)void return לא מקבלת ערך )וגם לא הכרחית לכתיבה(. אם בתוך לולאה או מבנה switch מתבצעת פעולת return במקום,break הרצת הפונקציה נגמרת )ולא רק של המבנה!(. 7
הכרזה של פונקציה חייבים אם פונקציה לא הוגדרה או לא הוכרזה לפני הקריאה אליה, הקומפיילר ייתן שגיאה. פתרון 1: מגדירים את הפונקציה מעל הפונקציה הראשונה שקוראת אליה. פתרון 2: מכריזים על הפונקציה מעל הפונקציה הראשונה שקוראת אליה. את הפונקציה מגדירים למטה יותר בתוכנית. איך מכריזים? 8 double delta(double a, double b); ";" בסוף השורה אין צורך לרשום את שמות המשתנים: double delta(double, double);
קריאה לפונקציה על מנת לקרוא לפונקציה, יש לתת ערך לכל פרמטר הסדר(. ניתן, אך לא הכרחי, להשתמש בערך המוחזר. כל פונקציה יכולה לקרוא לכל פונקציה. לדוגמה: ערכי הפרמטרים )לפי result = delta(c/2.0, d) * 50; הערך המוחזר 9
חישוב הפרמטרים בקריאה לפונקציה ראשית, יש לחשב את כל הפרמטרים המועברים לפונקציה. למשל, כדי לחשב את הפונקציה הבאה: delta(tan(cos(0.2)), sin(1.3)) יש לחשב ראשית את כל ערכי הפרמטרים... ראשית, נחשב את.tan(cos(0.2)) לשם כך, יש לחשב קודם את.cos(0.2) התוצאה: כעת, נחשב את.tan(0.98) התוצאה: 1.49. 10.0.98 שנית, נחשב את.sin(1.3) התוצאה: 0.96. וכעת סוף-סוף נוכל לחשב את (0.96.delta(1.49, התוצאה: 0.53.
פרמטרים וערך מוחזר מהם הפרמטרים והערך המוחזר? int gcd(int n, int m); double sin(double number); void print_account(int id, double cash); double get_time(void); void print_table(); ניתן לכתוב גם : double get_time(); int get_temperature(today); 11
מה לא בסדר בפונקציות? double min(int a, int b) { if (a > b) return a; void print_value(int m) { printf("value=%d\n", m); return m; int find_divisor(int num) { int j = num / 2; for ( ; j>1; j--) { if (num % j == 0) return 0; if (j == 1) return 0; else return j; 12
קריאה נכונה לפונקציה איזו מן הקריאות לא חוקיות? מדוע? int gcd(int n, int m); void print_value(int num); int dist(double, double); char get_letter(void); הפונקציות: j = gcd(j, j); result = print_value(i+1); dist(2.2, 1.5); printf("input: %c\n", get_letter(k)); get_letter; הסוגריים "מסבירים" לקומפיילר כי מדובר בפונקציה. אחרת זה שם של משתנה 13
מהלך ריצה של תוכנית 1. מתחילים בפונקצית.main 5.2. קריאה מריצים את לפונקצית פונקצית,scanf כדי לקבל,delta() ערך ל- x. כאשר הפרמטר a 3.מקבל כנ"ל, 2.1 עבור y. ופרמטר b מקבל 1.5.4. קוראים לפונקצית.delta 5. הפרמטרים מקבלים את 6. פעולת return מחזירה הערכים שהועברו להם מהצד עותק של הערך שיש ב- result. הקורא. בפועל מתבצע: return 0.6 6. הפונקציה מחזירה את ערכו של.result ל- d. ההרצה הוכנס את המוחזר מתחילים הערך.1.7 מפונקצית main() 2.8. קריאה מפעילים את לפונקצית הפונקציה,scanf() 3. 4. אשר.printf קריאה דואגת נוספת לפונקצית להכניס את לפונקצית,delta() 7.9. עם הערך ריצת,scanf() הערך ערכים 0.62.1 אשר 2.1 התוכניתו- מוצב במשתנה 1.5 מכניסהx. בתוך את מסתיימת עם 8. הערך סיום בפועל, משתנה קריאהd 1.5 ריצת הקריאה לפונקצית במשתנה y הפונקציה נראית: printf() main() את מפונקציתהתוצאה. delta( אשר, 1.5 יוצאים 2.1 מדפיסה.main.9) ע"י return 0 14 #include <stdio.h> double delta(double a, double b) { double result = a b; if (result < 0) result = -result; return result; int main() { double x, y; scanf("%f",&x); scanf("%f",&y); d = delta(x, y); printf("the difference is %f\n", d); return 0;
חישוב הפרמטרים בקריאה לפונקציה מה קרה בעצם כאשר חישבנו?delta(x,y) x y d 2.1 1.5 0.6? d = delta(x, delta(2.1, 0.6; y); 1.5); y); בשפת C, מעבירים ערכים לפרמטרים ולא את המשתנים עצמם! 15
דוגמה: מספר סימטרי 16
דוגמה מספר סימטרי נגדיר מספר להיות מספר סימטרי אם מתקיים ש: המספר בעל ספרה אחת או הספרות שנמצאות במרחקים שווים מאמצע המספר זהות. לדוגמה: 3 הוא מספר סימטרי, כיוון שהוא בעל ספרה אחת. 12321 הוא מספר סימטרי. אמצע המספר הוא הספרה 3, וספרות שנמצאות במרחקים שווים מהאמצע זהות. 1771 גם הוא מספר סימטרי. כתוב פונקציה: (x int symmetric(int המקבלת מספר שלם חיובי, ומחזירה 1 אם המספר סימטרי, אחרת 0. 17
מספר סימטרי - פתרון int symmetric(int x) { /* assuming that x is not negative */ int reverse=0, tmp=x; if (x < 10) { /* a single digit */ return 1; while (tmp > 0) { reverse = reverse*10 + tmp%10; tmp /= 10; return (x==reverse); 18
מחסנית הקריאות 19
דע מאין באת ולאן אתה הולך.f התוכנית מתחילה בפונקציה.main main מפעילה פונקציה אחרת, נניח g. מפעילה פונקציה אחרת, נניח f וכן הלאה... כיצד יודעת התוכנית לאן לחזור כשכל פונקציה מסתיימת? למשל, כש- g מסתיימת, כיצד יודעת התוכנית לחזור ל- f ולא ל- main? 20
דע מאין באת ולאן אתה הולך למשל, הנה קטע תוכנית: int delta_age; int delta_height; delta_age = delta(age1, age2); delta_height = delta(height1, height2); הפונקציה delta נקראת כאן פעמיים. בכל פעם, לאחר שהפונקציה מסתיימת, התוכנית ממשיכה ממקום אחר. איך זה קורה? 21
המחסנית במהלך ריצת התוכנית, המערכת מנהלת מחסנית של קריאות. המחסנית מנוהלת בשיטת Last-In-First-Out )ומכאן שמה(. על המחסנית נשמרים הנתונים הבאים: לאן לחזור לאחר סיום הפונקציה. ערכי הפרמטרים שהועברו לפונקציה. משתנים מקומיים של הפונקציה. 22
מקסימום מבין שני מספרים הפונקציה הבאה תחזיר את המקסימום מבין שני הערכים שלה: double max2(double a, double b) { if (a > b) return a; return b; כיצד ניתן לכתוב פונקציה המחשבת את המקסימום מבין 4 ערכים? 23
מקסימום מבין 4 מספרים ניתן לממש את max4 ע"י שימוש ב- max2 : double max4(double a, double b, double c, double d) { double temp1 = max2(a, b); double temp2 = max2(c, d); double max = max2(temp1, temp2); return max; שימו לב בגוף הפונקציה אין שום השוואה ישירה בין הפרמטרים...! 24
מקסימום בין 8 מספרים ואיך נממש מציאת מקסימום מבין 8 מספרים? double max8(double a, double b, double c, double d, double e, double f, double g, double h) { double temp1 = max4(a, b, c, d); double temp2 = max4(e, f, g, h); double max = max2(temp1, temp2); return max; שימו לב השתמשנו גם ב- max4 וגם ב- max2. 25
המחסנית בזמן הריצה איך נראית המחסנית כשמבוצעת הקריאה?main מתוך max8(1,8,4,5,9,6,1,3) max2 params: 1,8 max4 משווה בין 2 הפרמטרים הבאים ע"י קריאה ל- max2 max4 מקבל את התוצאה של הקריאה ל- max2 הראשונה max4 משווה בין 2 הפרמטרים הראשונים ע"י קריאה ל- max2 max8 משווה בין 4 הפרמטרים הראשונים ע"י קריאה ל- max4 max2 params: 4,5 קוראים ל- max8 max4 params: 1,8,4,5 max4 params: 1,8,4,5 max4 params: 1,8,4,5 max4 params: 1,8,4,5 תחילת ריצת main max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none main params: none main params: none main params: none main params: none main params: none 26
המחסנית בזמן הריצה )המשך( max4 משווה בין 2 תוצאות הביניים ע"י קריאה ל- max2 max4 מקבלת את התוצאה של הקריאה ל- max2 השנייה max2 params: 8,5 max4 סיימה לחשב את המקסימום בין 4 המספרים max4 משווה בין 2 הפרמטרים הראשונים ע"י קריאה ל- max2 max8 משווה בין 4 הפרמטרים הבאים ע"י קריאה ל- max4 max2 params: 9,6 max4 params: 1,8,4,5 max4 params: 1,8,4,5 max4 params: 1,8,4,5 max8 מקבלת את תוצאת השוואה של הרביעה הראשונה. max4 params: 9,6,1,3 max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none main params: none main params: none main params: none main params: none main params: none 27
המחסנית בזמן הריצה )המשך( max4 משווה בין 2 הפרמטרים הבאים ע"י קריאה ל- max2 max2 params: 1,3 max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none max4 משווה בין 2 תוצאות הביניים ע"י קריאה ל- max2 max2 params: 9,3 max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none max8 משווה בין 2 תוצאות הביניים ע"י קריאה ל- max2 max4 מקבל את התוצאה של הקריאה ל- max2 השנייה max4 מקבל את התוצאה של הקריאה ל- max2 הראשונה max8 משווה בין 4 הפרמטרים הבאים ע"י קריאה ל- max4 max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none max4 params: 9,6,1,3 max8 params: 1,8,4,5, 9,6,1,3 main params: none max8 params: 1,8,4,5, 9,6,1,3 main params: none 28
המחסנית בזמן הריצה )המשך( כאן max8 קוראת ישירות ל- max2. בעזרת המחסנית, max2 תמיד ידעה לאן לחזור, למרות שהיא נקראת לעיתים מתוך max8 ולעיתים מתוך.max4 max8 משווה בין 2 תוצאות הביניים ע"י קריאה ל- max2 max2 params: 9,8 max8 params: 1,8,4,5, 9,6,1,3 main params: none max8 params: 1,8,4,5, 9,6,1,3 main params: none חוזרים ל- main Max8 מחזירה את התוצאה ל- main main params: none 29
העברת פרמטרים ע"י ערך )call by value( 30
פרמטרים אקטואליים ופורמליים הפרמטרים שהפונקציה מקבלת )כפי שהם מופיעים בהגדרת הפונקציה( נקראים פרמטרים פורמליים. מבחינת הפונקציה, הפרמטרים הפורמליים הם משתנים לכל דבר. הם מוכרים בתוך הפונקציה בלבד והם מאותחלים מחדש עם כל קריאה אליה. הערכים המועברים לפרמטרים )כפי שהם מופיעים בכל נקודה של קריאה לפונקציה( נקראים פרמטרים אקטואליים. הפרמטר האקטואלי הוא תמיד ערך! הפונקציה לא יכולה לשנות פרמטר אקטואלי. 31
call by value היות שהפרמטרים האקטואליים הם תמיד שיטת הקריאה הזו לפונקציה נקראת ערכים, call by value בהמשך נכיר שיטת קריאה נוספת. 32
פרמטרים אקטואליים ופורמליים מהם ערכי המשתנים לאחר קריאה ל- max? פונקצית :max int max(int a, int b) { int res; if (a > b) { res = a; a = b; else { res = b; b = a; return res; קריאה ל- max מפונקצית פרמטרים פורמליים פרמטרים אקטואליים :main int x = 7, y = 8, d; d = max(x, y); int a = 7, y = 8, d; d = max(a, y); int a = 7, b = 8, d; d = max(a, b); int a = 7, b = 8, res; res = max(a, b); 33
תחום הגדרה של משתנה 34
הגדרת משתנים בתוך בלוק ניתן להגדיר משתנים בתחילת כל בלוק. מה זה בלוק? גוף פונקציה כל רצף פקודות בתוך סוגריים מסולסלים: {. משתנה קיים ונגיש מתחילת הבלוק בו הוא מוגדר ועד סוף הבלוק. המשתנה אינו נגיש מחוץ לבלוק. במקרה של כמה כניסות לבלוק בכל כניסה המשתנה "מוגדר" מחדש. )למשל בתוך לולאה(: במקרה של שמות זהים לכמה משתנים, המהדר יתייחס תמיד למשתנה שהוגדר בבלוק הפנימי ביותר )ביחס לנקודת הגישה למשתנה (. 35
מדוע "משתנים מקומיים"? משתנים המוגדרים בתוך בלוק פונקציה( נקראים מקומיים. )כולל בתחילת הם מקומיים לבלוק שהגדיר אותם! משתנה מקומי אינו קיים מחוץ לבלוק שבו הוא הוגדר. לכן לא ניתן להשתמש בהם מחוץ לבלוק. 36
"המשתנה אינו נגיש מחוץ לבלוק" חידה: מה תדפיס התוכנית הבאה? #include <stdio.h> int main(void) { int a = 10; if (a > 0) { int temp = 20; printf("%d", temp); return 0; 37
"בכל כניסה 'מוגדר' המשתנה מחדש" #include <stdio.h> מה תדפיס התוכנית הבאה? int main(void) { int a; int i; for (i = 0; i < 10; i++) { int temp = 0; temp++; a = temp; printf("%d", a); return 0; 38
"המהדר יתייחס תמיד למשתנה שהוגדר בבלוק הפנימי ביותר" #include <stdio.h> מה תדפיס התוכנית הבאה? int main(void) { int a = 10; if (a > 0) { int a = 0; printf("%d\n", a); printf("%d", a); return 0; 39
"המהדר יתייחס תמיד למשתנה שהוגדר בבלוק הפנימי ביותר" משתנה מבלוק פנימי מסתיר משתנה מבלוק חיצוני שיש לו את אותו שם. תכונה זו נקראת הסתרה. קוד המשתמש בהסתרה עשוי להיות מבלבל. לכן, מומלץ להימנע מכך. 40
הסתרה בעזרת טיפוס שונה במקרה של שני משתנים בעלי אותו שם )משתנה "חיצוני" ומשתנה "פנימי" יותר(, אין חובה שלשניהם יהיה את אותו טיפוס. #include <stdio.h> למשל: int main(void) { int a = 10; if (a > 0) { double a = 0.0; printf("%f\n", a); printf("%d", a); return 0; 41
הסתרה באותו הבלוק? האם לדעתכם ניתן להסתיר משתנה, בעזרת משתנה המוגדר מאוחר יותר בתוך אותו הבלוק )לא בבלוק פנימי(? למשל, מה תדפיס התוכנית הבאה? #include <stdio.h> int main(void) { int a = 10; int b = 5; int c = 13; int a = 12; /* Hide the first definition of a */ printf("%d", a); return 0; 42
משתנים "מחוץ לבלוק"? ניתן, בשפת C, להגדיר משתנים שאינם בתוך אף בלוק. המשתנים הללו מוגדרים מחוץ לכל פונקציה שהיא. הם נקראים משתנים גלובליים, משום שהם נגישים בכל מקום בתוכנית )מנקודת ההגדרה בקוד "ומטה"(. לעומתם, משתנים מקומיים נקראים בלעז "לוקאליים". 43
משתנים גלובליים מול לוקאליים משתנה גלובלי בא לידי קיום עם תחילת התוכנית, והוא קיים עד סופה. ערך שנכתב אליו נשמר עד תום התוכנית, גם אם יצאנו מהפונקציה שבצעה את הכתיבה. משתנה לוקאלי בעל אותו שם יכול להסתיר משתנה גלובלי. ברוב המקרים ניתן )ועדיף!( להשתמש במשתנים מקומיים. באופן כללי, שימוש במשתנים גלובליים ללא הצדקה מיוחדת נחשב לתכנות גרוע. 44
דוגמה: שימוש במשתנים גלובליים #include <stdio.h> int lights_on = 0; /* Global variable */ void flip_lights(void) { lights_on =!lights_on; void print_light_status(void) { if (lights_on) printf("lights are on."); else printf ("Lights are off."); מה תדפיס התוכנית הבאה? int main(void) { int lights_on = 1; /* Local variable */ flip_lights(); print_light_status(); return 0; 45