עקרון הפרד ומשול - חלוקת בעיה לתת בעיות עקרון השימוש החוזר בקוד שכתבנו עקרון
האחריות היחידה
סגנון גרוע להעברת נתונים בין פונקציות -- משתנים גלובליים
דוגמאות נוספות לפונקציות מחזירות ערך
פונקציות שאינן מחזירות ערך (פרוצדורות)
העברת ארגומנטים, בקריאה לפונקציה, לפי ערך
העברת ארגומנטים, בקריאה לפונקציה, לפי התיחסות
פונקציות המטפלות במערכי מספרים
פונקציות ומחלקות המכילות מערכים
המקרים בהם מתרחשות הסבות טיפוסים:
storage class
specifier - static auto register extern
סטטי לפני שם של משתנה גלובלי או פונקציה
הנושאים
שנדון בהם:
·
פונקציה
כקטע קוד לשימוש חוזר.
·
פונקציה
כפעולה המקבלת קלט מבצעת חישוב ומחזירה פלט.
·
פונקציה
כאמצעי לארגון התוכנית - פירוק בעיה גדולה לתתי בעיות.
·
כיצד
מועברים נתונים בין פונקציות.
·
רשימת
הארגומנטים
·
צורות
אחסון משתנים בזיכרון auto, static
·
פונקציה
היא שם של סדרת הוראות
המקובצות כיחידה אחת. בכל פעם שמתעורר הצורך להפעיל סדרת הוראות זו אין צורך
להעתיק שוב את סדרת ההוראות אלא אפשר להסתפק רק בציון שם הפונקציה (קריאה לפונקציה
1.
ב C++ כל חישוב או פעולה נעשים בתוך
פונקציה.
2.
וכפי
שראינו, הבצוע של כל תוכנית C++ מתחיל מההוראות הכתובות בפונקציה
בשם main(). לכן, כל תוכנית C++ חייבת להכיל לפחות פונקציה אחת שהיא
הפונקציה main().
לכל
פונקציה יש ארבעה חלקים:
1. שם הפונקציה.
2. הטיפוס של הערך שהפונקציה מחזירה, כאשר היא
סיימה את פעולתה.
3.
הנתונים שהפונקציה מקבלת בכדי לבצע את
פעולתה - רשימת ארגומנטים המציינת את טיפוס הנתונים של כל ארגומנט.
4. בלוק קוד המכיל את ההוראות שעל הפונקציה
לבצע - גוף הפונקציה.

T
name( ... )
{
...
}
דוגמא:
·
קיימת
לנו הבעיה להדפיס על המסך את המלה אבא בעברית. לשם כך היינו יכולים לכתוב שורות
הדפסה רבות בתוך הפונקציה main() ולגמור את התוכנית.
·
אבלאת
שורות ההדפסה של האות 'א' אנו מעתיקים פעמיים.
·
עקרון
השימוש החוזר (Reuse) בתכנות קובע כי אם קיימת אפשרות להשתמש במשהו שכבר כתבנו הרי צריך להשתמש
בו ולא להעתיקו מחדש. עקרון זה הוא בעל חשיבות עליונה בתכנות ונפגוש אותו שוב
ושוב.
·
לפי
עקרון הפרד ומשול אנו צריכים לזהות את תתי הבעיות בבעיה של הדפסת המילה
"אבא". הבעיה מתחלקת:
1.
בעית
הדפסת האות 'א'.
2.
בעית
הדפסת האות 'ב'.
3.
בעית
הדפסת רווח בין אות לאות הבאה.
אם
נוכל לפתור את תתי הבעיות הללו נוכל לפתור את הבעיה כולה.
·
פתרון
של כל תת בעיה נכניס לפונקציה וכך נוכל להפעילה מספר פעמים שצריך.
·
אם
חושבים לטווח ארוך הרי בכדי לפתור את בעית ההדפסה של המלה "אמא" נצטרך
רק להוסיף פונקציה המדפיסה את האות
'מ' ולשנות את קוד ההדפסה הראשי. בהדפסת האות 'א' אפשר להשתמש
מחדש.
·
לכל
פונקציה צריך להיות תחום אחריות מוגדר ויחיד.
·
שם
הפונקציה נבחר בצורה כזו שהוא מביע בצורה הברורה ביותר את אותה אחריות בודדת של
הפונקציה. לדוגמא לפונקציה המדפיסה
את האות 'א' נקרא aleph() וכו'.
פרוצדורה
הוא כנוי לפונקציה שאינה מקבלת פרמטרים ואינה מחזירה ערך. כל פעולתה היא להפעיל את
שורות הקוד שבתוכה.
#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 מציין שגיאה.
·
שיטת
העברת נתונים בין פונקציות באמצעות משתנים גלובליים נחשבת כגרועה ביותר מבחינת
סגנון תכנותי נכון.
המילה
פונקציה באה מתחום המתמטיקה בה קיימת פעולה אשר מקבלת נתונ/ים כקלט מסויים ומוציאה
פלט יחיד כתוצאה.

sqrt(3.1415);
· פונקציה מתמטית פועלת באופן כמו קופסא שחורה: מכניסים מצד אחד נתונים (ארגומנטים) ומקבלים "בצד השני" תוצאה. אין אנו יודעים מה קורה בתוך הקופסא השחורה.
·
הקריאה
לפונקציה מכילה את הנתונים הבאים:
1.
שם
הפעולה, במקרה שלנו: sqrt
2.
ארגומנט
אקטואלי (actual argument) יחיד שחייב להיות אי
שלילי: 3.1415
3.
הערך
המוחזר מהפונקציה הוא מסוג double, ערכו השורש הריבועי של הארגומנט האקטואלי.
result
function(arg1, arg2, arg3, ....)
דוגמא לבעיה:
בכדי
לחשב את תצרוכת חשמל של מכשיר בוואטים אנו משתמשים בנוסחא:
#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;
}
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 יכול להדבק רק למשתנה אחד ואי אפשר
להחליף את המשתנה אליו הוא דבוק.
·
הגדרת
טיפוס של התיחסות נעשית באופן הבא:
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.
}
המהדר
של 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.
·
כאשר
יש פעולה בינארית של שני טיפוסים שונים המהדר ממיר את הטיפוס הנמוך לטיפוס הגבוה.
ממירים לטיפוס
הגבוה הבא בתור לפי הסדר:
|
תרשים 1 סדר הסבת טיפוסים |
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
שיטת
אחסון - מתאר את האופן בו מוחזק המזהה.
עבור
משתנים יש שני סוגים עיקריים:
|
סטאטי
static |
אוטומטי auto |
|
מוגדר בבלוק בפונקצי או כגלובלי - מחוץ לכל בלוק |
מוגדר רק בתוך בלוק של פונקציה |
|
מאותחל רק פעם אחת בתוכנית (0
בברירת מחדל) |
מאותחל מחדש בכל כניסה לבלוק |
|
תמיד נשמר הערך האחרון |
כאשר יוצאים מהבלוק הערך נעלם |
|
מוקצה בזכרון בכתובת קבועה |
מוקצה על המחסנית |
מגדירים
שהמשתנים יהיו אוטומטיים.
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 בתחילת ההגדרה שלו.
·
משתנה
סטטי שומר על ערכו כאשר יוצאים מהפונקציה ונשאר כאשר שבים אליה


לדוגמא:
משמש להצהיר על משתנה גלובלי שהוגדר בקובץ
שלנו או בקובץ אחר
(ןגם
להצהיא על פונקציה – זו ברירת המחדל)
ההצהרה
על משתנה 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 , כלומר
מוכרים בכל התוכנית ולא רק בקובץ בו הוגדרו.