פונקציות  (Functions) 

 

 


תוכן הפרק

 

עקרון הפרד ומשול - חלוקת בעיה לתת בעיות  עקרון השימוש החוזר בקוד שכתבנו   עקרון האחריות היחידה

חלוקת בעיה לתתי בעיות

סגנון גרוע להעברת נתונים בין פונקציות -- משתנים גלובליים

פונקציה בתפקיד פונקציה מתמטית

הצהרה/הגדרה של פונקציות

דוגמאות נוספות לפונקציות מחזירות ערך

פונקציות שאינן מחזירות ערך (פרוצדורות)

העברת ארגומנטים, בקריאה לפונקציה, לפי ערך

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

טיפוס ההתיחסות  &   reference

ארגומנט התייחסות בפונקציה

פונקציה המחזירה  התייחסות

בעיות בארגומנטים מסוג התיחסות

ערכי ברירת מחדל

function name overloading

מערכים ופונקציות

פונקציות העובדות עם מחרוזות:

פונקציות המטפלות במערכי מספרים

חיפוש בינארי

מיון בועות - הפשוט ביותר

פונקציות ומחלקות המכילות מערכים

הסבות טיפוסים

המקרים בהם מתרחשות הסבות טיפוסים:

type promotion

Numbers conversions

storage class specifier - static auto register extern

register auto

static

extern

סטטי לפני שם של משתנה גלובלי או פונקציה

 

 

הנושאים שנדון בהם:

·                      פונקציה כקטע קוד לשימוש חוזר.

·                      פונקציה כפעולה המקבלת קלט מבצעת חישוב ומחזירה פלט.

·                      פונקציה כאמצעי לארגון התוכנית - פירוק בעיה גדולה לתתי בעיות.

·                      כיצד מועברים נתונים בין פונקציות.

·                      רשימת הארגומנטים

·                      צורות אחסון משתנים בזיכרון  auto, static

 

 

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

1.      ב C++   כל חישוב או פעולה נעשים בתוך פונקציה.

2.      וכפי שראינו, הבצוע של כל תוכנית  C++  מתחיל מההוראות הכתובות בפונקציה בשם  main(). לכן, כל תוכנית  C++  חייבת להכיל לפחות פונקציה אחת שהיא הפונקציה  main().

 

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

  1.  שם הפונקציה.

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

  3.  Line Callout 3 (No Border): 2. טיפוס הערך שהפונקציה מחזירההנתונים שהפונקציה מקבלת בכדי לבצע את פעולתה - רשימת ארגומנטים המציינת את טיפוס הנתונים של כל ארגומנט.

  4.  בלוק קוד המכיל את ההוראות שעל הפונקציה לבצע  - גוף הפונקציה.

Line Callout 3 (No Border): 1. שם הפונקציה
 

 

 


Line Callout 3 (No Border): 3. רשימת ארגומנטיםT name( ... )

{

Line Callout 3 (No Border): 4. גוף הפונקציה    ...

}

עקרון הפרד ומשול - חלוקת בעיה לתת בעיות
עקרון השימוש החוזר בקוד
 שכתבנו 
עקרון האחריות היחידה

דוגמא:

·      קיימת לנו הבעיה להדפיס על המסך את המלה אבא בעברית. לשם כך היינו יכולים לכתוב שורות הדפסה רבות בתוך הפונקציה  main() ולגמור את התוכנית.

·      אבלאת שורות ההדפסה של האות 'א' אנו מעתיקים פעמיים.

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

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

 1.   בעית הדפסת האות 'א'.

 2.   בעית הדפסת האות 'ב'.

 3.   בעית הדפסת רווח בין אות לאות הבאה.

אם נוכל לפתור את תתי הבעיות הללו נוכל לפתור את הבעיה כולה.

·        פתרון של כל תת בעיה נכניס לפונקציה וכך נוכל להפעילה מספר פעמים שצריך.

 

·        אם חושבים לטווח ארוך הרי בכדי לפתור את בעית ההדפסה של המלה "אמא" נצטרך רק להוסיף  פונקציה המדפיסה את האות

    'מ' ולשנות את    קוד ההדפסה הראשי. בהדפסת האות 'א' אפשר להשתמש מחדש.

 

·        לכל פונקציה צריך להיות תחום אחריות מוגדר ויחיד.

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

     את האות 'א' נקרא  aleph()  וכו'.

חזרה להתחלה


חלוקת בעיה לתתי בעיות

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

 

Text Box: --Example Output----
**   **
 **   *
 ***  *
 * *** 
*   ** 
**   **


 ****  
 ***** 
     * 
     * 
*******
*******


**   **
 **   *
 ***  *
 * *** 
*   ** 
**   **

#include<iostream.h>

/*

    program prints father in hebrew (aba)

*/

 

// declaration of function space. in order that previous functions

// (aleph, beth) will be able to use it.

void space();

 

// prints the hebrew letter aleph

void alef()

{

    cout << "**   **" << endl;

    cout << " **   *" << endl;

    cout << " ***  *" << endl;

    cout << " * *** " << endl;

    cout << "*   ** " << endl;

    cout << "**   **" << endl;

    space();

}

 

// prints the hebrew letter beth

void beth()

{

        cout << " ****  " << endl;

        cout << " ***** " << endl;

        cout << "     * " << endl;

        cout << "     * " << endl;

        cout << "*******" << endl;

        cout << "*******" << endl;

        space();

    }

 

    // make space between letters

    void space()

   {

        cout << endl << endl;

    }

    void main()

    {  alef();

       beth();

       alef();

    }

לדוגמא, במקרה של הפונקציה  aleph():

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

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

·        רשימת הארגומנטים של הפונקציה ריקה.

·        גוף הפונקציה (בתוך הסוגריים המסולסלים) מבצע הדפסת שורות האות 'א'.

·        קריאה לפונקציה מתבצעת באמצעות ציון שם הפונקציה ולאחריו ;.

·        מותר לקרא לפונקציה רק אם הפונקציה מוגדרת מעל (בקובץ) המקום ממנו רוצים להפעילה. אבל לפעמים רוצים להשתמש בפונקציה

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

     הגדרת הפונקציה (כל הפונקציה).

 

·        במקרה שלנו נאלצנו להצהיר על הפונקציה  space()  לפני שקראנו לה מתוך הפונקציות   aleph(), beth().

 

סגנון גרוע להעברת נתונים בין פונקציות -- משתנים גלובליים

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

·                      שיטה זו הוכחה שוב ושוב כגרועה מבחינת הנדסת תוכנה וכגורמת להרבה בעיות.

·                      שיטה זו מוסברת כדוגמא לסגנון גרוע.

 

הבעיה

הבעיה שלנו מתחלקת לשלושה שלבים

 1.               קילטת נתונים.

 2.               חישוב.

 3.               הדפסת תוצאה.

 

לפי העקרונות שלמדנו אנו זקוקים ל 4  פונקציות:

/*

   progarm performs simple clculation

   ==================================

*/

#include <iostream.h>

 

// functions declaration

void get_data();

void calculate();

void print_results();

 

// Global varaibles definition

// global varaible automatically intialized to ziro

double data, result;

 

// main proccess

void main()

{

    get_data();

    calculate();

    print_results();

}

 

void get_data()

{

    cin >> data;

}

 

void calculate()

{

    result = data * 2;

}

 

void print_results()

{

    cout << "result is: " << result << endl;

}

 

 

·                      קריאות התוכנית  (Program readability)פירושו היכולת של מתכנת לקרא במהירות ולהביו מה התוכנית מבצעת.

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

·                      לכן, נוהגים לכתוב את main()  בראש הקובץ. ומתחתיו שיופיעו יתר הפונקציות. מכיון שחיבים להצהיר על פונקציה לפני

            הקריאה, הרי אנו חייבים להצהיר על הפונקציות לפני  main().

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

·                      נוהגים להצהיר על כל הפונקציות בתחילת הקובץ, כך אין חשיבות לסדר הופעת הפונקציות בקובץ.

·                      משתנה גלובלי הור משתנה המוגדר מחוץ לכל בלוק של פונקציה. משתנה כזה מאותחל אוטומטית ל 0 או לכל ערך קבוע אחר

           שאיתו אפשר לאתחלו.

·                      רק משתנה גלובלי אחד באותו שם יוכל להיות מוגדר בתוכנית.

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

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

            הפונקציות המופיעות מתחתם בקובץ.

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

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

·                      הפונקציה  exit()  מסיימת את בצוע התוכנית. הקריאה  exit(0)  שקולה להופעת    return 0;  בפונקציה  main().

            ערך 0 מציין סיום תקין ערך שונה מ 0 מציין שגיאה.

 

 

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

פונקציה בתפקיד פונקציה מתמטית

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

Text Box:

 

        sqrt(3.1415);

 

 

 

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

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

                                             1.                     שם הפעולה, במקרה שלנו:  sqrt

                                             2.                     ארגומנט אקטואלי  (actual argument) יחיד שחייב להיות אי שלילי:  3.1415

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

 

result function(arg1, arg2, arg3, ....)

Text Box:  דוגמא לבעיה:

 

בכדי לחשב את תצרוכת חשמל של מכשיר בוואטים אנו משתמשים בנוסחא:

 

 

 

 

 

 

 

#include <iostream.h>

#include <math.h>     // sqrt()

 

double power_loss(double R, double I);  // declatation

 

void main()

{

    double p;

    float x = 1.0, y = 2.0;

    double z = 3.0, w = 40.5;

    int a = 8, b = 9;

 

    p = power_loss(3.5, 2.26);  // actual arguments: 3.5, 2.26

    cout << p << endl;

    p = power_loss(x, y);       // actual arguments: 1.0, 2.0

    cout << p << endl;

    p = power_loss(y, x);       // actual arguments: 2.0, 1.0

    cout << p << endl;

    p = power_loss(x + 23, w);  // actual arguments: 24.0, 40.5

    cout << p << endl;

    p = power_loss(sqrt(a), b % 4); // actual arguments: 2.828247, 1.0

    cout << p << endl;

Text Box: -- Output --
17.8766
4
2
39366
2.82843
}

 

double power_loss(double R, double I)  // definition

{

    double watts;

    watts = R * I * I;

    return watts;

}

 

 

 

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

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

·                      ארגומנטים פורמליים - אותם שמות שהמתכנת נותן לארגומנטים של הפונקציה. אנו זקוקים לשמות אלו בכדי לכתוב מה רוצים

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

הצהרה/הגדרה של פונקציות 

TypeName identifier(argument-list);

 

1.      identifier  שם הפונקציה (לפי החוקים למתן שם ב C++).

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

3.      הצהרה על פונקציה תמיד מסתיימת ב  ';'.

 

דוגמא: כל ההצהרות הבאות הן הצהרות חוקיות על הפונקציה  power_loss

 

double power_loss(double R, double I);  // declatation

double power_loss(double a, double b);  // declatation

double power_loss(double, double);  // declatation

 

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

 

double power_loss(double R, I);  // error

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

הצהרה היא הכרזה כי מזהה (identifier) קיים. מזהה הוא שם המוגדר בתוכנית (משתנה, פונקציה וכו'). לעומת

זאת הגדרה נותנת את המשמעות המדויקת של המזהה.

 

הגדרת פונקציה function definition

הצהרה על פונקציה  function prototype

מגדירה מה מבצעת הפונקציה

מתארת כיצד צריך לקרא לפונקציה

אין חובה להגדיר פונקציה לפני קריאה אליה . (כמובן שבשלב ה  link  ההגדרה תחסר לנו).

חייבים להצהיר על פונקציה לפני קריאה אליה

הגדרת פונקציה היא גם הצהרה

הצהרה על פונקציה איננה הגדרה

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

מותר להצהיר הרבה פעמים על פונקציה

 

·                        הצהרה על פונקציה מתארת למהדר כיצד מותר לקרא לפונקציה:

1.      מהו שם הפונקציה.

2.      מהו טיפוס הנתון שהפונקציה מחזירה.

3.      רשימת הארגומנטים  (argument list) המתארת את טיפוסי הארגומנטים שהפונקציה מקבלת. 

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

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


דוגמאות נוספות לפונקציות מחזירות ערך

1.      אפילו אם יש רק הוראה אחת לבצוע חייבים להכניסה לסוגריים  {  }.

2.      בצוע פקודת  return  מפסיקה את בצוע הפונקציה וגורמת לפונקציה להחזיר את ערך הביטוי expression.

3.      return expression;

4.      גם אם מגיעים, במהלך הבצוע, לפקודת  return  המופיעה באמצע הפונקציה, היא תפסיק את בצוע הפונקציה.

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

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

return.

 

#include <iostream.h>

// functios declaration

int ziro();

int char_to_int(char c);

double avg4(int a1, int a2, int a3, int a4);

double max(double a, double b);

char past_fail(int t1, int t2, int t3, int t4, int exam);

 

void main()

{

    int n;

    n = ziro();                                   // n is 0

    n = char_to_int('5') * char_to_int('7');      // n is 35

    double mt_test_avg = avg4(99, 68, 71, 80);   // 79.5

    char grade = past_fail(99, 68, 71, 80, 75); // 'P'

    cout << "grade is "

         << past_fail(99, 68, 71, 80, 75) << endl;

    int i = 20;

    double z = avg4(i, i + 10, i * i, 12);  // z is 115.5

    cout << max(z, mt_test_avg);            // output: 115.5

}

 

// function returns ziro value

int ziro()

{

    return 0;

}

 

// function translate ascii value of a digit to

//  its int value of the digit.

//  ascii values: '0' - 48 .. '9' - 57

 

int char_to_int(char c)

{

    const int ascii_ziro_digit = int('0');

    return c - ascii_ziro_digit;

}

 

// return average of 4 numbers

double avg4(int a1, int a2, int a3, int a4)

{

    return (a1+ a2 + a3 + a4) / 4.0;  // must 4.0 and not 4!

}

 

// return the max of the two arguments.

double max(double a, double b)

{

    if (a > b)

        return a;

    return b;

}

 

/*

   checks if a student pass or fail the course

   returns 'P' or 'F'

   there where 4 tests: t1 t2 t3 t4 which make 60% of total grade

   and a final exam which make 40% of total grade.

  

*/

char past_fail(int t1, int t2, int t3, int t4, int exam)

{

    const double test_weight = 0.6;

    const double exam_weight = 0.4;

    double text_avg = avg4(t1, t2, t3, t4);

    double final_avg = test_weight * text_avg + exam * exam_weight;

    if (final_avg >= 60)

        return 'P';

    else

        return 'F';

}

חזרה להתחלה

פונקציות שאינן מחזירות ערך (פרוצדורות)

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

·                      פונקציה שהערך המוחזר ממנה הוא void  פירושו שאין היא מחזירה ערך.

·                      אפשר להפסיק בצוע של פונקציה המחזירה  void  באמצעות  פקודת  return  ריקה:

return;

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

            האחרונה בה.

 

בשיטה זו אנו מעוניינים ב  "side effect"  שהפונקציה מבצעת ולא ב  "return value" שפונקציה מחזירה.

#include <iostream.h>

#include <iomanip.h>

 

// prints a line of stars minimum  **

void line_of_stars(int len)

{

    cout << setfill('*') << setw(len) << "**" << endl;

    cout << setfill(' '); // reset fill to ' '

}

 

//    prints number n in a stars box of width box_len.

void number_in_abox(int n, int box_len)

{

    line_of_stars(box_len);

    cout << '*' << setw(box_len-2) << n << '*' << endl;

    line_of_stars(box_len);

}

 

void main()

{

    number_in_abox(-777, 8);

}

 

-- Output --

********

*-777  *

********

·                      המניפולטור setw(n)  קובע רוחב שדה ההדפסה להדפסה הבאה (בלבד) של מספר או מחרוזת אך לא תו.

           המניפולטור setfill(c) קובע כי בהדפסה הבאה התווים בשדה ההדפסה יהיו c  במקום התו ' '.

 

4.      מותר לקרא לפונקציה שמחזירה  void  אבל אסור לה להשתתף בחישובים (כי אין לה ערך מוחזר).

 

#include <iostream.h>

void f1()

{

    cout << "a function returning void\n";

    return;

}

 

double f2()

{

    cout << "a function returning double\n";

    return 7.2;

}

 

void f()

{

    int x = 2 + f2(); // OK

    int y = 2 + f1(); // compile Error: function f1() returns void.

}

 

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

 

 

העברת ארגומנטים, בקריאה לפונקציה, לפי ערך 

כאשר קוראים לפונקציה מעתיקים את הערכים של הנתונים לתוך הארגומנטים של הפונקציה
 
(call by value)  ולאחר מכן מבצעים את גוף הפונקציה:

#include<iostream.h>

void swap(int x, int y);  // swap: פירושו להחליף

 

void main()

{

    int j = 2, k = 3;

    cout << j << ' ' << k << '\n';

    swap(j, k);

    cout << j << ' ' << k << '\n';

}

 

void swap(int x, int y)

{

    int t = x;

    x = y;

    y = t;

}

 

 

OUTPUT:

2 3

2 3

הלקח הוא ששינוי בערכים של הארגומנטים בתוך הפונקציה לא יורגש במשתנים או קבועים שאת ערכיהם שלחנו לפונקציה. כלומר, שינוי בערכים של    x, y  בתוך הפונקציה swap()  לא יגרום לשינוי בערכים של j, k.

 

מדוע זה כך?

·                      כל ארגומנט של הפונקציה הוא בעצם משתנה נוסף לכן אפשר לראות את הקריאה לפונקציה כך:

 

swap(j, k);

 

swap(int x, int y)

x = j         הקריאה לפונקציה מבצעת השמה לתוך הארגומנטים שהם משתנים השיכים לפונקציה

y = k

{

    int t = x;    ההחלפה מחליפה משתנים מקומיים בתוך הפונקציה

    x = y;

    y = t;

}

 

זה ממש כאילו עשינו:

void main()

{

    int j = 2, k = 3;

    cout << j << ' ' << k << endl;

    {   int x = j;   // call to swap()

        int y = k;

        

        int t = x;

        x = y;

        y = t;

    }

    cout << j << ' ' << k << endl;

}

·                      call by value היא גישה המגינה על משתני הפונקציה הקוראת.

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

            משתנים:   sqrt(12 * 48 / a). בצורת קריאה כזו אין כלל משתנה שבו קיים הערך לפני שהוא מועבר לפונקציה.

·                      שיטת העברת הארגומנטים ב C++, באופן רגיל,  היא  call by value.

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

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

הפונקציה  swap כך שהיא תעבוד.

·                      הפתרון הוא העברת התיחסות למשתנים לתוך הפונקציה או --  call by reference.

 

חזרה להתחלה

 

טיפוס ההתיחסות  &   reference

·                        טיפוס ההתיחסות מאפשר לנו "להדביק" שם נוסף למשתנה שכבר קיים.

·                        משתנה מסוג  reference  תמיד "נדבק" למשתנה אחר והוא כמו שם חלופי לו.

·                        משתנה reference  יכול להדבק רק למשתנה אחד ואי אפשר להחליף את המשתנה אליו הוא דבוק.

 

·                        הגדרת טיפוס של התיחסות נעשית באופן הבא:

Type& name;

·                        name  הוא התייחסות לעצם מטיפוס  Type.

 

int n = 10;

int& r = n; // r is a reference to n.

int& r1 = n,  r2 = n; // r1 is a reference, r2 is an int.

int &r3 = n, &r4 = n; // r3 and r4 are references.

 

r = 2;     // n has 2.

int y = r; // y has 2.

r = y + 3; // n has 5.

 

·                        בזמן הגדרת  reference  חייבים לציין לאיזה משתנה מתיחסים ואי אפשר לשנות לאחר מכן..

 

double sum, average;

double& total = sum;

double& value;           // ERROR: nothing is referred to.

double& total = average; // ERROR: can't redefine reference.

 


ארגומנט התייחסות בפונקציה

·                      כאשר מוגדר ארגומנט של פונקציה כ-  reference הכוונה היא שהארגומנט של הפונקציה יידבק למשתנה עמו קראנו לפונקציה.

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

 

// swaps two int's

void swap(int& x, int& y)

{

    int t = x;

    x = y;

    y = t;

}

void main()

{

    int i = 1, j = 2;

    swap(i, j);

    cout << i << ' ' << j;

}

prints: 2 1

מה שקורה הוא כך:

void main()

{

    int j = 2, k = 3;

    cout << j << ' ' << k << endl;

    {   int& x = j;   // call to swap()

        int& y = k;   // x reference to j, y reference to k

        

        int t = x;

        x = y;

        y = t;

    }

    cout << j << ' ' << k << endl;

}

 

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

           על   j, k  כי בעצם הם אותם המשתנים בזכרון.

 

 

·                      ההצהרות הבאות על הפונקציה  swap()  שקולות:

void swap(int& x, int& y);

void swap(int & x, int & y);

void swap(int &x, int &y);

void swap(int&x, int&y);

void swap(int&, int&);

·                      אין זה משנה אם ה  &  צמוד לטיפוס או לשם הארגומנט.

 

דוגמאות נוספות:

#include <iostream.h>

// argument: n - out - integer entered by user

void read_int(int &n)

{

    cin >> n;

}

 

void input_int(int &n, const char prompt[])

{

    cout << prompt;

    read_int(n);

}

 

 

void main()

{

    int width, depth, hight;

    input_int (width, "Enter width of box in mm: ");

    input_int (depth, "Enter depth of box in mm: ");

    input_int (hight, "Enter hight of box in mm: ");

    cout << "The volume of the box is:"

         << (width * depth * hight)

         << " Sqare mm" << endl;

}

 

----------- Example Output --------------

Enter width of box in mm: 123

Enter depth of box in mm: 23

Enter hight of box in mm: 441

The volume of the box is:1247589 Sqare mm

 

·                      כאשר רוצים להעביר מחרוזת תווים כארגומנט לפונקציה הטיפוס הוא  const char a[]. כלומר מערך של תווים.

פונקציה המחזירה  התייחסות

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

#include <iostream.h>

// global varaibles

int g1 = 1, g2 = 2;

 

// returns a referance to g1.

int& retg1() {   return g1;  }

 

// returns th value of g2.

int retg2() {   return g2;  }

 

// returns refernce to automatic varaible, - Runtime Error

int& retauto()

{        int n = 5;

          return n;

}

 

int& refPass(int& a) { return a; }

int& valPass(int  a) { return a; }

 

 

void main()

{

          cout << retg1() << endl; // 1

          retg1() = 4;             // g1 = 4     

          cout << retg1() << endl; // 4

          cout << retg2() << endl; // 2

          retg2() = 4;             // Error: retg2() not lvalue!

          retauto() = 6;                   // Error: internal n is already "dead"

         

          int x = 7, y = 8;

          refPass(x) = 10;       // x = 10

valPass(y) = 11;       // y doesn’t change (internal a = 11)

}

 

 


בעיות בארגומנטים מסוג התיחסות

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

#include <iostream.h>

void swap(int& i, int& j)

{

    int t = i;

    i = j;

    j = t;

}

 

void print(const int& i, const int& j)

{

    cout << "i:" << i << " j:" << j << endl;

}

 

void main()

{

    int i1 = 1, i2 = 2;

    double d1 = 4.6, d2 = 5.3;

    swap(i1, i2);  // OK

    cout << i1 << ' ' << i2 << endl;

 

    swap(i1, 4);  //error: cannot convert parameter 2 from 'const

                  //        int' to 'int &'

    swap(d1, d2); //error: 'initializing' : cannot implicitly convert

                  //       a 'double' to a 'int &' that is not const.

 

    print(i2, 4); // OK

    print(d1, d2);// OK

}

 

OUTPUT:

1 2

i:1 j:4

i:4 j:5

1.      כאשר מגדירים התייחסות לטיפוס מסוים (int) ומאתחלים אותה עם משתנה מטיפוס שונה (double) יש בעיה.

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

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


ערכי ברירת מחדל  

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

 

לדוגמא, הפונקציה print() מדפיסה את המספר השלם n  לפי בסיס  base. רוב הזמן אנו מעונינים להדפיס מספר לפי בסיס 10 ורק במקרים נדירים אנו מדפיסים לפי בסיסים 8 או 16.

void print(int n, int base);

שימוש לדוגמא בפונקציה  print():

print(35, 10);  // prints 35

print(35, 8);   // prints 43

print(35, 16);  // prints 23

 

נוכל להצהיר על ערך ברירת מחדל לארגומנט  base  כך שהוא יהיה 10.

void print(int n, int base = 10);

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

print(35);      // prints 35

print(35, 8);   // prints 43

print(35, 16);  // prints 23

 

 

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

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

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

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

 

 

double f(int n, int m, int p = 0);          // OK

double f(int n, int m = 1, int p = 0);      // OK

double f(int n = 2, int m = 1, int p = 0);  // OK

double f(int n, int m = 2, int p);          // ERROR

double f(int n = 1, int m, int p = 2);      // ERROR

 

 

double f(int n = 2, int m = 1, int p = 0);  // OK

 

f();        // is f(2, 1, 0)

f(x);       // is f(x, 1, 0)

f(x, y);    // is f(x, y, 0)

f(x, y, z); // is f(x, y, z)

 

f(,x,y);  //Error: we can't skip arguments at call

f(x,,y);  //Error: we can't skip arguments at call

 

 

 

 


דוגמא:

// function declarations

void print(int n, int base = 10); 

int WeightedSum(int x1 = 0, int x2 = 0, int x3 = 0, int x4 = 0);

 

void main()

{

          print(35);               // prints: 35

          print(35, 8);                     // prints: 43

          print(35, 16);                   // prints: 23

      cout << WeightedSum() << '\n';         // print 0.

          cout << WeightedSum(1) << '\n';        // print 1.

          cout << WeightedSum(1, 2) << '\n';     // print 9.

          cout << WeightedSum(1,2,3) << '\n';    // print 36.

          cout << WeightedSum(1,2,3,4) << '\n';  // print 100.

}

 

// the default arguments do NOT appear in the function's definition!

void print(int n, int base)

{

    if (base == 8)

        cout << oct << n;

    if (base == 16)

        cout << hex << n;

    if (base == 10)

        cout << dec << n;

    cout << endl;

}

 

int WeightedSum(int x1, int x2, int x3, int x4)

{

    return x1 + 4*x2 + 9*x3 + 16*x4;

}

 

נתבונן במקרה הבא של רב משמעיות  (ambiguity)

void f(int a, int b = 10);

void f(int x);

 

void main()

{

    f(5); // ERROR: compilation ambiguity between functions.

 

}

 


function name overloading 

המהדר של  C++  יודע להבחין בין פונקציות אפילו רק לפי טיפוס הארגומנט.

#include <iostream.h>

 

void f(char i)

{        cout << "char" << endl; }

void f(short i)

{        cout << "short" << endl; }

void f(int i)

{        cout << "int" << endl; }

void f(long i)

{        cout << "long" << endl; }

void f(float i)

{        cout << "float" << endl; }

void f(double i)

{        cout << "double" << endl; }

 

void main()

{   

    char c;

    short s;   

    int n;

    long l;

    float f;   

    double d;

  

    f('a');    // prints: char

    f(c);      // prints: char

    f(1);      // prints: int

    f(n);      // prints: int

    f(1L);     // prints: long

    f(l);      // prints: long

    f(2.0f);   // prints: float

    f(f);      // prints: float

    f(3.0);    // prints: double

    f(d);      // prints: double

}

 

                                       ·                        בניית פונקציית  max  בהדרגה, קודם ל 2 ארגומנטים. ולאחר מכן מתבססים על מה שעשינו בכדי להרחיב את הפתרון ל 3 ו 4 ארגומנטים:

 

int Max(int x, int y) { return x < y ? y : x; }

 

int Max(int x, int y, int z) { return Max(Max(x, y), z); }

 

                           ·                        הפונקציה  Max(int, int)  שונה מהפונקציה  Max(int, int, int).  לכן אין בלבול בין הפונקציה בעלת שני ארגומנטים לבין הפונקציה בעלת שלושה ארגומנטים, למרות השם הזהה.

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

 

int Max(int x, int y, int z, int w)

{

    return Max(Max(x, y), Max(z, w));

}

 

חזרה להתחלה


 מערכים ופונקציות

·      מותר להעביר מערך כארגומנט לפונקציה.

·      אסור לפונקציה להחזיר מערך.

·      מערך, בניגוד ליתר הטיפוסים, עובר לפונקציה ב  call by reference. כלומר שינוי של איבר במערך בתוך הפונקציה ישאר מחוץ לפונקציה. הסיבה לכך היא שהעברת ארגומנטים בשיטה כזו חוסכת העתקת מערכים גדולים.

·      אסור לבצע השמה בין מערכים. בכדי להשם מערך למערך צריך לכתוב קוד אשר יבצע את ההשמה איבר איבר.

void swap(int a[2])

{

    int t = a[0];

    a[0] = a[1];

    a[1] = t;

}

void main()

{

    int ar[2] = {1, 2};

    swap (ar);

    cout << ar[0] << ' ' << ar[1];

}

output: 2 1

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

·                        אין חייבים להצהיר על מספר האיברים בארגומנט מערך (ממילא אין מתיחסים למספר זה).

·                        אם צריכים לדעת מהוא מספר האיברים מעבירים מידע זה בארגומנט נוסף: נתון המכיל מספר איברים במערך.

void showArr(int a[], int n)

{

    for (int i = 0; i < n; i++)

        cout << a[i] << ' ';

    cout << endl;

}

 

 

9.      אם מעבירים מערך של מערכים מותר להשמיט רק את מספר האיברים במערך הראשי (להשאיר את הסוגריים השמאליים ביותר ריקים).

void showTbl(int tbl[][3], int n)   // tbl[n][3]

{

    for (int i = 0; i < n; i++)

    {

        for (int j = 0; j < 3; j++)

            cout << tbl[i][j] << ' ';

        cout << endl;

    }

}

 


פונקציות העובדות עם מחרוזות:

 

// length of a string. a version of a similar function in <string.h>

int strlen(char s[])

{

    int i = 0;

    while (s[i] != '\0')  // '\0' ends a string!

        i++;

    return i;

}

 

// reverse a string s in place

void reverse(char s[])

{

    // we use j for improving the efficiency

    // instead of calling strlen(s) in each iteration.

    for (int i = 0, j = strlen(s) - 1; i < j; i++, j--)

    {

        int t = s[i];

        s[i] = s[j];

        s[j] = t;

    }

}

 

// convert Integer n TO Ascii characters in s.

// a version of a similar function in <stdlib.h>

void itoa(int n, char s[])

{

    int sign = n; // record sign.

    if (n < 0)    // make n positive

        n = -n;      

    int i = 0;

    do {

        s[i++] = n % 10 + '0';  // generate digits in reverse order

    } while ((n /= 10) > 0);    // get next digit

    if (sign < 0)

        s[i++] = '-';

    s[i] = '\0';

    reverse(s);

}

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

 

 


פונקציות המטפלות במערכי מספרים

חיפוש בינארי

 

// binsearch: find x in sorted array: v[0]<=v[1]<=...<=v[n-1]

// returns: if x was found      -  index of x in the array.

//          if x was not found  -  -1.

// uses divide and conquer method

int binsearch(int x, int v[], int n)

{

    int low, high, mid;

    low  = 0;

    high = n - 1;

    while (low <= high)

    {

        mid = (low + high)/2;

        if (x < v[mid])

            high = mid - 1;

        else if (x > v[mid])

            low = mid + 1;

        else

            return mid;  // found match.

    }

    return -1;

}

·                        מניחים כי מקבלים מערך ממויין בסדר עולה.

·                        בכל איטרציה חוצים את תחום החיפוש לשניים.

·                        נמצא אם  x  קיים במערך המכיל מיליון איברים תוך לכל היותר 20 איטרציות!  ולא עד מיליון במקרה שהיינו סורקים את האיברים אחד אחד ו x נמצא, או מיליון איטרציות במקרה ש  x איננו.

מיון בועות - הפשוט ביותר

// bubbleSort: sorts array of n integers in ascending order.

// uses the simplest form of bubble sort method.

void bubbleSort(int a[], int n)

{

    for (int i = 0;i < n-1; i++)

        for (int j = n - 1; j > i; j--)

            if (a[j-1] > a[j])

            {

                int temp = a[j];

                a[j] = a[j-1];

                a[j-1] = temp;

            }

}

 

 

 

 

 

 

0

1

2

3

4

5

6

 

 

i=0    i=1   i=2    i=3   i=4    i=5   i=6 לאחר סיום האיטרציה:

 
 

 

 

 

 

 

 

 

 

 

 

 


                             ·   בכל איטרציה של  i  מבטיחים כי האיבר הקטן ביותר הבא נמצא במקומו.

                             ·   השם מיון בועות בא לתאר כי בכל איטרציה של  i  המספר הקטן ביותר הבא "צף" למעלה.

פונקציות ומחלקות המכילות מערכים

1.                                        מותר להגדיר חבר מערך בתוך מחלקה או מבנה.

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

מעתיקים גם את המערך המוכל בעצם.

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

הם  public  ופונקציות constructor אינן מוגדרות (וגם אין ירושה ופונקציות וירטואליות).

 

#include <iostream.h>

struct string9 {

    char s[10];

    int  len;

};

 

void f(string9 st)

{

    cout << st.s << endl;  // prints string!

    st.s[0] = '!';

    st.s[1] = '\0';        // ends the string

    cout << st.s << endl;  // prints: !

}

 

void ff(char s[])

{

    cout << s << endl;     // prints string!

    s[0] = '!';

    s[1] = '\0';           // ends the string

    cout << s << endl;     // prints: !

}

 

void main ()

{

    string9 s1 = {"hello", 5}; // initializes a simple struct.

    cout << s1.s << endl;      // prints hello.

    string9 s2 = s1;           // array and int were copied.

    cout << s2.s << endl;      // prints hello.

    s1.s[0] = 'c';

    cout << s1.s << endl;      // prints cello.

    cout << s2.s << endl;      // prints hello.

 

    f(s2);  // prints: hello

            //         !

    cout << s2.s;  // prints: hello.

                   // no change in s2. - call by value!

 

    ff(s2.s);    // prints: hello

                 //         !

    cout << s2.s;// prints: !

                 // s2.s was changed! - array is passed by reference!

hello

hello

cello

hello

hello

!

hellohello

!

one 3!

two 3

three 5

four 4

 
 


    string9  as[] = {

        {"one",   3},

        {"two",   3},

        {"three", 5},

        {"four",  4}

    };

    for (int i = 0; i < 4; i++)

        cout << as[i].s << ' ' << as[i].len << endl;

}

חזרה להתחלה

הסבות טיפוסים

המקרים בהם מתרחשות הסבות טיפוסים:

1.       כאשר מופעל מפעיל בינארי על שני ארגומנטים מטיפוסים שונים.

2.       כאשר מאתחלים משתנה מטיפוס אחד עם ערך מטיפוס שונה.

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

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

5.       כאשר פונקציה מבצעת הוראת  return  לערך מטיפוס שונה מהטיפוס שהוצהר שהפונקציה מחזירה.

6.       כאשר המתכנת דורש הסבת טיפוס מפורשת  (cast) 

 

1. מפעיל בינארי מופעל על שני ארגומנטים מטיפוסים שונים

double d;

d = 1   / 2;    // d = 0; (int / int)

d = 1.0 / 2;    // d = 0.5 (double / int) -> (double / double)

2. אתחול משתנה מטיפוס אחד עם ערך מטיפוס שונה

double d = 3;   // d = 3.0  int -> double

int    i = 4.5; // i = 4    double -> int

3. השמה של ערך מטיפוס אחד לתוך משתנה מטיפוס שונה

int i;

double d;

i = 6 / 4.0;    // i = 1  double -> int

d = 6 / 4;      // d = 1  int -> double

4. מעבירים ארגומנט לפונקציה מטיפוס השונה מטיפוס הארגומנט המוצהר עבור הפונקציה

void f(int i)

{  cout << i << endl; }

 

void g(double d)

{  cout << d << endl; }

 

void main()

{   f(12.6); // prints: 12         double -> int

    g(12.6); // prints: 12.600000  double -> double

    g(12);   // prints: 12.000000  int    -> double

}

5. פונקציה מבצעת הוראת  return  לערך מטיפוס שונה מהטיפוס שהוצהר שהפונקציה מחזירה

int f1()

{  return 4; }

 

int f2()

{  return 4.8; }

 

double g1()

{  return 5; }

 

double g2()

{  return 5.7; }

 

void main()

{   cout << f1() << endl; // prints: 4          int    -> int

    cout << f2() << endl; // prints: 4          double -> int

    cout << g1() << endl; // prints: 5.000000   int    -> double

    cout << g2() << endl; // prints: 5.700000   double -> double

}


6. כאשר המתכנת דורש במפורש הסבת טיפוס  (cast)

void main()

{

    int    n = 2;

    double d = 3.3;

    cout << (int) d << endl;   // prints: 3          double -> int

    cout << (double) n << endl;// prints: 5          double -> int

    cout << (n * d) << endl;   // prints: 6.600000      

    cout << int(n * d) << endl;// prints: 6          double -> int

    cout << (int)(n * d) << endl; // prints: 6       double -> int

    cout << (int) 5.3 << endl; // prints: 5          double -> int

    cout << int(4.7)  << endl; // prints: 4          double -> int

    cout << (double)6 << endl; // prints: 6.000000   int    -> double

    cout << char(0x41)<< endl; // prints: A          int    -> char

    cout << int('A')  << endl; // prints: 65         char   -> int

 

    float x;   int i = 2;

    x = 1 / (float) i;  // x = 0.5f

    x = 1 / i;          // x = 0

}

 

 

ישנן שתי שיטות להסבת טיםוסים:

1. הסבת ערך רק לטיפוס בסיסי

T(expression)

  מסב את ערך הביטוי  expression  לטיפוס  T

  T  יכול להיות מסוג:  char, int, double ...

 

2.  הסבת ערך לטיפוס שאינו רק טיפוס בסיסי

(T) expression

מסב את ערך הביטוי  expression  לטיפוס   T

 

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

11.  מותר להסב טיפוס הערך של כל ביטוי. (לתשומת לב: לכל ביטוי יש טיפוס)

 

 

הערה למתקדמים:  כדאי להשתמש בהסבת טיפוסים באופרטורי ההסבה המיוחדים הבאים:

 

·    dynamic_cast   Used for conversion of polymorphic types.

·    static_cast   Used for conversion of nonpolymorphic types.

·    const_cast   Used to remove the const, volatile, and __unaligned attributes.

·    reinterpret_cast   Used for simple reinterpretation of bits.

 


 type promotion 

                           ·                        כאשר יש פעולה בינארית של שני טיפוסים שונים המהדר ממיר את הטיפוס הנמוך לטיפוס הגבוה. ממירים לטיפוס  

              הגבוה הבא בתור לפי הסדר:

תרשים 1 סדר הסבת טיפוסים


Numbers conversions

integer ® unsigned

     אם ה unsigned מכיל פחות בתים מקצצים משמאל (פעולת מודולו)

     אם ה unsigned מכיל יותר בתים:

     מורחים  '0' ים  אם ה integer גם הוא unsigned.

     מורחים סימן אם ה integer הוא  signed וערכו שלילי (נקבל מספר חיובי).

הנושא של מריחת סימן יוסבר בקורס הדן באסמבלר.

 

integer ® signed

     אם אפשר שומרים על הערך.

     אם אי אפשר לשמור על הערך  --  תלוי בסוג המהדר.

 

integer and float

float ® integer

     קוצצים את השבר. 

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

     אם אי אפשר לשמור על הערך של החלק השלם  --  תלוי בסוג המהדר.

 

integeral ® floating

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

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

 

floating ® floating

     אם מסוג נמוך לסוג גבוה יותר התוצאה ללא שינוי.

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

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

 


דוגמאות

char c1 = 'A', /*0x41*/  c2 = ' '; /* 0x20 */

unsigned char uc;

 

c = c1 + c2;   // c = '\x61' which is 'a'

 

uc = 257 + 'A' // uc = (257 + 'A') modulo 256 == 1 + 'A' == 'B'

 

int i; double d;

d = i = 100 / 3.0   //  d = i = 33.3333 --> d = 33

          double           int            double

 

int    i = 2,   y;

double d = 3.2, x;

x = (y = d / i) * 2; // y = 1   x = 2

y = (x = d / i) * 2; // x = 1.6 y = 3

 

int    quo; // quotient

double rem; // remainder

double a = 1.1, b = 7.8;

כמו מודולו למספרים ממשיים:

rem = b - (quo =  b/a ) * a;    // quo = 7  rem = 0.1

           int  double

              int

שימוש למפעיל cast

int   n = 2;

float f;

f = 1 / float(n+1);

double d;

d =(int)(( (unsigned short) 65537 + (unsigned char)258 ) / (float)2);

                         1                       2              2

                                  3                             2

// d = 1


storage class specifier - static auto register extern

שיטת אחסון - מתאר את האופן בו מוחזק המזהה.

 

עבור משתנים יש שני סוגים עיקריים:

           

סטאטי static

אוטומטי  auto

מוגדר בבלוק בפונקצי  או כגלובלי - מחוץ לכל בלוק

מוגדר רק בתוך בלוק של פונקציה

מאותחל רק פעם אחת בתוכנית (0 בברירת מחדל)

מאותחל מחדש בכל כניסה לבלוק

תמיד נשמר הערך האחרון

כאשר יוצאים מהבלוק הערך נעלם

מוקצה בזכרון בכתובת קבועה

מוקצה על המחסנית

 

register auto

מגדירים שהמשתנים יהיו אוטומטיים.

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

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

 

דוג'

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

void main()

{

    register long i;      // default is: auto long i;  

    register long sum;    // default is: auto long sum;

   

    for (sum = 0, i = 0, i < 50000, i++)

        sum += i;

}

static

·                      משתנה מחוץ לפונקציה חייב להיות סטטי -  כי הוא לא שייך לשום בלוק.

·                      משתנה בתוך פונקציה יהיה סטטי אם נוסיף static בתחילת ההגדרה שלו.

·                      משתנה סטטי שומר על ערכו כאשר יוצאים מהפונקציה ונשאר כאשר שבים אליה

Text Box: #include <iostream.h>
int inc_val()
{
    int i = 0;
    return i++;
}

void main()
{
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';

}
output: 0 0 0 0  
Text Box: #include <iostream.h>
int inc_val()
{
    static int i = 0;
    return i++;
}

void main()
{
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';
     cout << inc_val() << ' ';

}
output: 0 1 2 3

לדוגמא:

 

 

 

 

 

 

 

 


extern

     משמש  להצהיר על משתנה גלובלי שהוגדר בקובץ שלנו או בקובץ אחר   (ןגם להצהיא על פונקציה – זו ברירת המחדל)

     ההצהרה על משתנה extern אינה מקצה שטח אלא קושרת אותנו לשטח שכבר הוקצה (אם לא הוקצה טעות link). לכן גם אסור לאתחל משתנה המוצהר כ extern.

     אם משתנה מוצהר בתוך פונקציה עם extern אזי ההצהרה נקשרת להגדרה של המשתנה הגלובלי המוגדר מחוץ לפונקציה.

 

// A - compile error

// unknown identifier

void main()

{

   cout << n; // error

}

 

// C - compile error

// unknown identifier n

 

void main()

{

   cout << n; // OK!

}

 

// definition of n

int n = 12; // global

 

// D - OK

 

extern int n;

 

void main()

{

   cout << n; // OK!

}

 

int n = 12;

 

// B - link error

// unresolved external n

 

// declaring n

extern int n;

void main()

{

   cout << n;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// E - link error

// redefinition of n

void f();

int n;

 

void main()

{

   f();

   cout << n;

}

 

 

// F - link error

// redefinition of n

 

int n = 12;

 

void f()

{

   cout << n; // OK!

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// G - OK

extern int n;

void f();

 

void main()

{

   n = 2; // OK!

   f();

}

 

 

// H - OK

 

int n = 12;

 

void f()

{

   cout << n; // OK!

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#include "d.h"

 

void main()

{

   n = 2; // OK!

   f();

}

 

 

// file: d.h

extern int n;

void f();

 

 

#include "d.h"

 

void f()

{

   cout << n; // OK!

}

 

int n = 12;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

סטטי לפני שם של משתנה גלובלי או פונקציה

     משתנה גלובלי (המוגדר מחוץ לכל בלוק) הוא תמיד סטטי.  אם מצהירים על משתנה גלובלי כ static הכוונה היא שהוא בעל internal linkage.  כלומר שהמשתנה הגלובלי מוכר רק בקובץ בו הוא מוגדר ולא בקבצים אחרים (במדה והתוכנית מורכבת ממספר קבצים).

     פונקציה המוגדרת static (בדומה למשתנה גלובלי) מוכרת רק בקובץ בו היא מוגדרת.

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

 


חזרה להתחלה