שפת C/פונקציות: הבדלים בין גרסאות בדף

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי
תוכן שנמחק תוכן שנוסף
שורה 196: שורה 196:
</source>
</source>


בדיקה
==מעט על פונקציות והנדסת תוכנה==

שפת C משמשת לכתיבת תוכנות מסובכות מאד. הקוד של [[w:לינוקס (ליבה)|ליבת לינוקס]], לדוגמה, מורכב ממיליוני שורות קוד. בשפת C מתמודדים עם מורכבות זו בעזרת חלוקת הקוד לפונקציות (וכן, במידה מסויימת, על ידי חלוקה ל[[שפת C/מודולים|מודולים]]). תכנות טוב מבוסס על חלוקת כל תוכנית למספר פונקציות, כך שלכל אחת מטרה מוגדרת אחת. כאשר פונקציה עושה יותר מדי פעולות, או כאשר קטעי קוד חוזרים על עצמם בפונקציות שונות, מחלקים את הקוד לפונקציות קטנות יותר. בצורה זו ניתן לפשט תוכנית שמבצעת משימות מורכבות לתוכנית שבה כל פונקציה לבדה מבצעת משימה פשוטה, ומורכבות התוכנית נובעת מהבנייה ההדרגתית של פונקציות אחת על השנייה. הייתרונות המושגים על ידי כך:
*הקוד נוח לקריאה וברור.
*קל יותר לשנות או לתקן כל פונקציה בנפרד, כך שאם מתגלה בעיה באחד מחלקי התוכנית מספיק לתקן רק את החלק הזה, מבלי שהדבר ישפיע על שאר חלקי התוכנית.
*הקוד מאפשר שימוש חוזר. אם קטע קוד נבדק ועובד, ואנו צריכים את אותו קטע קוד בחלק אחר של התוכנית, אין צורך לשכפל אותו.

נשתמש בקוד שראינו ב[[שפת C/פונקציות#הצורך בפונקציות|צורך בפונקציות]] כדוגמה (למרות שזהו קוד פשוט מאד). ראשית נתבונן בפונקציה main:
<source lang = "c">
#include <stdio.h>


int main()
{
int c, f;

for(c = 0; c <= 40; c += 4)
{
f = 1.8 * c + 32;
printf("%d in Celsius is %d in Fahrenheit\n", c, f);
}

printf("Enter degrees in Clesius: ");
scanf("%d", &c);
f = 1.8 * c + 32;
printf("This is %d in Fahrenheit\n", f);
return 0;
}
</source>
ברור למדי שהפונקציה מבצעת שני דברים: מדפיסה טבלת המרות, וממירה שאילתה בודדת. נחלק, לכן, את הקוד לפונקציות:
<source lang = "c">
#include <stdio.h>


void print_init_conversion_table();
void handle_conversion_query();


int main()
{
print_init_conversion_table();
handle_conversion_query();
return 0;
}


void print_init_conversion_table()
{
int c, f;

for(c = 0; c <= 40; c += 4)
{
f = 1.8 * c + 32;
printf("%d in Celsius is %d in Fahrenheit\n", c, f);
}
}


void handle_conversion_query()
{
int c, f;

printf("Enter degrees in Clesius: ");
scanf("%d", &c);
f = 1.8 * c + 32;
printf("This is %d in Fahrenheit\n", f);
}
</source>
כעת נשים לב לשורת ההמרות שחוזרת על עצמה (כפי שראינו מקודם), ונהפוך אותה לפונקציה:
<source lang = "c">
#include <stdio.h>


float celsius_to_fahrenheit(int celsius);
void print_init_conversion_table();
void handle_conversion_query();


int main()
{
print_init_conversion_table();
handle_conversion_query();
return 0;
}


void print_init_conversion_table()
{
int c, f;

for(c = 0; c <= 40; c += 4)
printf("%d in Celsius is %d in Fahrenheit\n", c, (int)celsius_to_fahrenheit(c));
}


void handle_conversion_query()
{
int c;

printf("Enter degrees in Clesius: ");
scanf("%d", &c);
printf("This is %d in Fahrenheit\n", (int)celsius_to_fahrenheit(c));
}


float celsius_to_fahrenheit(int celsius)
{
return 1.8 * celsius + 32;
}
</source>
איכות הקוד כעת טובה יותר:
*הקוד חסין יותר מטעויות - צמצמנו את מספר המקומות בהם נצטרך לשנות משהו אם יש טעות בנוסחת ההמרה, לדוגמה.
*הקוד גמיש יותר - קל יהיה לשנות את הקוד אם תגיע דרישה לתוכנית שתעשה משהו אחר, לדוגמה:
**תוכנית ששואלת את המשתמש האם להדפיס טבלת המרה '''או''' לענות על שאילתה
**תוכנית שמדפיסה טבלת המרה, ואז עונה על שאילתות בלולאה עד שהמשתמש מציין שסיים



ב[[שפת C/ניהול זיכרון דינאמי#מעט על מבנים והנדסת תוכנה|מעט על מבנים והנדסת תוכנה]] נדבר עוד על עניינים אלה בהקשר של ''[[שפת C/מבנים|מבנים]]''.

{{שפת C|מוגבל}}

[[קטגוריה:שפת C|פונקציות]]

גרסה מ־09:45, 16 באפריל 2010

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

הצורך בפונקציות

נניח שאנו כותבים תוכנית קטנה להמרת מעלות מ-Celsius ל-Fahrenheit (ראה גם כאן וכאן). התכנית תתחיל בכך שתדפיס את התרגום למעלות Fahrenheit של מעלות ה-Celsius בערכים 0, 4, 8, ..., 40, ולאחר מכן תבקש מהמשתמש מעלה ב-Celsius, ותדפיס את ערכו ב-Fahrenhei. נרשום את התוכנית כך:

#include <stdio.h>

int main()
{
  int c, f;

  for(c = 0; c <= 40; c += 4)  
  {
    f = 1.8 * c + 32;
  
    printf("%d in Celsius is %d in Fahrenheit\n", c, f);
  }

  printf("Enter degrees in Celsius: ");
  scanf("%d", &c);
  
  f = 1.8 * c + 32;
  
  printf("This is %d in Fahrenheit\n", f);
  
  return 0;
}

נוכל לשים לב שהשורה

f = 1.8 * c + 32;

מופיעה פעמיים בתוכנית. זהו דבר בעייתי:

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

ככל שהתוכנית ארוכה ומסובכת יותר, הבעייתיות בדברים כאלה גדלה.

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

float celsius_to_fahrenheit(float celsius)
{
  return 1.8 * celsius + 32;
}

פונקציות שכבר ראינו

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


הפוקנציה main

כל תוכנית בשפת C חייבת לכלול את הפונקציה main. זוהי הפונקציה הראשונה שמורצת כאשר מורצת התוכנית, וכאשר מסתיימת הרצתה, מסתיימת הרצת התוכנית.

אנו ראינו אותה בגרסה הזו:

int main()
{
  <body>
}

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

[1]


כדאי לדעת:

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

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

[[קובץ:

כיתוב תמונה

]]]]]]]]]]]]]]]]]]]]]]]]===פונקציות פלט וקלט===

בפלט וקלט ראינו כבר את הפונקציות printf, scanf, putchar, ו getchar.

==הצהרה על פונקציות== נתבונן בתוכנית הבאה: <source lang = "c"> #include <stdio.h> int main() { int a, b; printf("Enter two numbers:\n"); scanf("%d %d", &a, &b); print_bigger( a, b ); return 0; } void print_bigger(int x, int y) { if (x>y) printf("%d",x); else printf("%d",y); } </source> לכאורה, הכל בסדר בתוכנית. ראשית מתחילה לפעול (כתמיד) הפונקציה main. כאשר מגיעים לשורה <source lang = "c"> print_bigger( a, b ); </source> תיקרא הפונקציה print_bigger, ולאחר שתסתיים הקריאה לפונקציה, תחזור התוכנית ל-main. על אף שהכל נראה בסדר, המהדר יודיע שבתוכנית יש שגיאה. כאשר המהדר מגיע לשורה הקוראת ל-print_bigger, הוא עדיין לא יודע שיש פונקציה כזאת - היא מוגדרת מאוחר יותר בקובץ. המהדר יתלונן שאין פונקציה כזו. לדוגמה, המהדר [http://gcc.gnu.org gcc] מתלונן כך: <source lang = "c"> main.c: In function ‘main’: main.c:11: warning: implicit declaration of function ‘print_bigger’ </source> כמובן שנוכל לפתור את הבעיה על ידי החלפת סדר הפונקציות, אך לא תמיד הדבר אפשרי: נראה כך בהמשך ב[[שפת C/פונקציות#פונקציות רקורסיביות|פונקציות רקורסיביות]] ו[[שפת C/מודולים|מודולים]]. פתרון מקובל אחר, הוא להשאיר את הסדר כפי שהוא, אך ''להצהיר'' על הפונקציה print_bigger לפני הפונקציה main, כך שהמהדר ידע על קיומה ועל האופן שבו היא צריכה להקרא. הצהרה כזאת ([[w:en:Declaration (computer science)|declaration]] בלעז) מתבצעת על ידי כתיבת האב-טיפוס ([[w:en:function_prototype|prototype]] בלעז) של הפונקציה, כלומר: הטיפוס המוחזר, שם הפונקציה וטיפוסי הפרמטרים, עם נקודה-פסיק בסוף. במקרה שלנו, לדוגמה, ההצהרה תראה כך: <source lang = "c"> void print_bigger(int x, int y); </source> כעת, אם ההצהרה מופיעה לפני הקריאה לפונקציה, נוכל לכתוב את הגדרת הפונקציה (definition בלעז) אפילו אחרי הקריאה לפונקציה, והתוכנית עדיין תעבור הידור ותרוץ כנדרש: <source lang = "c"> #include <stdio.h> /* This is a declaration. */ void print_bigger(int x, int y); int main() { int a, b; printf("Enter two numbers:\n"); scanf("%d %d", &a, &b); print_bigger( a, b ); return 0; } /* And here is the definition. */ void print_bigger(int x, int y) { if (x>y) printf("%d",x); else printf("%d",y); } </source>הפענוח נכשל (שגיאת תחביר): {\displaystyle [[מדיה:formula]][[קובץ: == PictureFileName.jpg|left|thumb|250px|כיתוב תמונה == [[מדיה:[[מדיה:Example.ogg]][[מדיה:[[מדיה:Example.ogg]][[מדיה:[[מדיה:Example.ogg]][[מדיה:Example.ogg]]]]]]]]]]}

פונקציות רקורסיביות

שקלו לדלג על נושא זה

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



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

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

unsigned long factorial(unsigned int n)
{
  unsigned long fact = 1;
  unsigned int i;

  for(i = 1; i <= n; ++i)
   fact *= i;

  return fact;
}

ולהלן פונקציה רקורסיבית לחישוב עצרת:

unsigned long factorial(unsigned int n)
{
  if(n == 0)
    return 1;
    
  return n * factorial(n - 1);
}

או בצורה קצרה יותר:

unsigned long factorial(unsigned int n)
{
  return n == 0? 1 : n * factorial(n - 1);
}

בדיקה

  1. ^ לעתים אפשר לראות את הפונקציה main מוגדרת כך: void main() כלומר, בגרסה שאינה מחזירה ערך. מדובר בשגיאה.