Transcript Document
פונקציות
קרן כליף
ביחידה זו נלמד:
מהי פונקציה ,פרמטרים וערך מוחזר
כיצד נראה הזיכרון
ספריות של פונקציות
מערכים כפרמטר לפונקציה
יצירת מספרים אקראיים
פונקציות מmath.h -
תהליך הפיכת תוכנית ב C++ -לשפת מכונה
סוגי משתנים וטווח הכרתם :לוקאליים ,גלובליים ,סטטיים
2
© Keren Kalif
מהי פונקציה
פונקציה היא אוסף הוראות לביצוע שמממשות רעיון
משותף
למשל sizeofהיא פונקציה
גם ה main -שאנחנו כותבים הוא פונקציה
עבורנו פונקציות אלו הן "קופסא שחורה" שקיבלנו
מספריה מסוימת ,ואנו רק יודעים מה הן עושות ,אבל לא
יודעים איך ,כלומר מהו אוסף הפעולות המבוצעות
3
© Keren Kalif
בשיעור זה נלמד לכתוב פונקציות בעצמנו!
דוגמא לשימוש בפונקציה
#include <iostream>
using namespace std;
void main()
{
int num, size;
הערך המוחזר מהפונקציה
שם הפונקציה
כל נתון נקרא ארגומנט.הנתונים המועברים
size = sizeof(num);
cout << "the variable 'num' uses “ << size << “ bytes\n”;
}
4
© Keren Kalif
log חישוב חלוקת:דוגמא לשכפול קוד
const int BASIS =10;
void main()
{
int num1=100, num2=1000, res;
int log1, log2;
רק,רצף הפעולות לחישוב לוג זהה
כל פעם ביצענו את הפעולות על
! שכפול קוד ערכים שונים
צריךmain -מי שקורא את ה
...להבין מה תפקידה של כל לולאה
res = BASIS;
for (log1=1 ; res < num1 ; log1++)
log10100 חישוב
res *= BASIS;
cout << "log(" << num1 << ") = " << log1 << endl;
res = BASIS;
for (log2=1 ; res < num2 ; log2++)
log101000 חישוב
res *= BASIS;
cout << "log(" << num2 << ") = " << log2 << endl;
cout << "log(" << num1 << "/" << num2 << ") = "
<< log1-log2 << endl;
{
5
© Keren Kalif
הדוגמא ללא שכפול קוד ,קריאות
טובה ומודלריות
>#include <iostream
שם הפונקציה.
יעיד מה היא עושה ;using namespace std
טיפוס המידע
שהפונקציה מחזירה
;const int BASIS=10
)int log(int num
הנתון שהפונקציה צריכה לקבל
}
;int res = BASIS, i
עם השימוש בפונקציה הקוד קצר
)for (i=1 ; res < num ; i++
יותר וקריא יותר!
;res *= BASIS
ה main -כתוב ב"ראשי פרקים"
איך הפונקציה
;return i
וניתן להבין בקלות מה הוא מבצע
מבצעת את העבודה
{
הערך שהפונקציה מחזירה
שימוש בערך שהפונקציה מחזירה
)(void main
}
קריאה לפונקציה שיודעת לחשב ,log
;int num1=100, num2=1000, res
פעם עם num1ופעם עם num2
;)res = log(num1) - log(num2
;cout << "log(“ << num1 << “/” << num2 << “) = “ << res << endl
6
{
© Keren Kalif
פונקציות -מוטיבציה
מודולריות :חלוקת התוכנית לקטעי קוד יותר
קטנים ,כאשר כל קטע עושה משהו נקודתי
יתרונות:
קוד קריא יותר
יותר קל לבדוק את התוכנית
חלוקת עבודה
שימוש חוזר בקוד
חיסכון בבדיקות
פחות שגיאות
7
© Keren Kalif
!תזכרו
http://www.thedistractionnetwork.com/images/programming-fail-007.jpg
8
© Keren Kalif
החלקים בשילוב פונקציה שלנו בקוד
.1
הצהרה על הפונקציה
.2
.3
9
© Keren Kalif
כאשר כותבים פונקציה ראשית צריך להחליט מה הפונקציה
עושה
לאחר שהחלטנו על ה"מה" ,יש להחליט אילו נתונים הפונקציה
צריכה לקבל כדי לבצע את העבודה
לבסוף נגדיר מה הטיפוס שהפונקציה מחזירה לנו
שימוש בפונקציה
מימוש הפונקציה
איך כותבים הצהרה של פונקציה
דוגמא :פונקציה המחשבת חזקה
מה הפונקציה עושה :הפונקציה מחשבת חזקה של שני מספרים
מה הפונקציה מקבלת 2 :מספרים ,הראשון בסיס והשני מעריך
מה הפונקציה מחזירה :את תוצאת חישוב הבסיס במעריך
לבחור שם משמעותי לפונקציה
int base
int exponent
תחביר:
ובאופן כללי:
baseexponent
int
)int power(int base, int exponent
10
© Keren Kalif
)><function_name> (<parameters list
><returned value type
בחירת שם לפונקציה
שם הפונקציה צריך להעיד מה היא עושה
למשלpower, printf :
מבחינת סגנון כתיבה ,ההמלצה היא:
שם הפונקציה יתחיל באות קטנה
אם יש יותר ממילה אחת בשם הפונקציה כל מילה נוספת
תתחיל באות גדולה
11
© Keren Kalif
למשלcalcRectangleArea :
ניתן לאמץ סגנון אחר ,אבל הקפידו להיות עקביים בסגנון לאורך
כל התוכנית .כנ"ל גם לגבי שמות משתנים
הצהרת פונקציה -הערך המוחזר
()1
הערך המוחזר יכול להיות כל אחד מהטיפוסים שאנחנו
מכירים
12
© Keren Kalif
למשל ,פונקציה המקבלת אורך צלע ריבוע ומחזירה את שטחו
;)int calcSquareArea(int length
למשל ,פונקציה המקבלת מספר ,ומחזירה את השורש שלו
;)double sqrt(int num
הצהרת פונקציה -הערך המוחזר void
אם הפונקציה אינה מחזירה ערך ,נרשום כערך המוחזר
void
החזרה = ניתן לאכסן ערך במשתנה כלשהו
שימו לב :הדפסה למסך אינה החזרה של ערך!
למשל ,פונקציה המדפיסה למסך
)void printRectangle(int base
13
© Keren Kalif
הצהרת פונקציה –
הפרמטרים המועברים
רשימת הנתונים אשר הפונקציה מקבלת מופרדת ע"י
פסיקים
דוגמא :פונקציה המקבלת אורך ורוחב של מלבן ומחזירה את
שטחו
;)int calcRectangleArea(int height, int width
למשל ,פונקציה המקבלת מספר nומחזירה !n
;)int factorial(int num
אם פונקציה אינה מקבלת נתונים ,רשימת הפרמטרים
תהייה ריקה
14
© Keren Kalif
למשל ,פונקציה הקוראת מהמשתמש את גילו ומחזירה אותו
;)(int readAge
מימוש פונקציה
דוגמא למימוש פונקציה המקבלת בסיס ומעריך ,ומחזירה
את החזקה
)int power(int a, int b
{
;int result=1
)for (int i=0 ; i < b ; i++
;result *= a
;return result
הפקודה returnמציינת הפסקת ביצוע
הפקודות בפונקציה והחזרת ערך המשתנה
}
15
© Keren Kalif
פונקציה שהערך המוחזר שלה הוא void
אינה חייבת להכיל את הפקודה .return
באם מופיעה הפקודה returnבפונקציה
המחזירה ,voidרצף הפעולות יפסק מיד.
!אבל זהירות
http://funnypictures4u.files.wordpress.com/2013/06/people_v
s_programmers.jpg
16
© Keren Kalif
תוכנית שלמה עם פונקציה
הפונקציה מוכרת ממקום הגדרתה ומטה,
ולכן הצהרת הפונקציה תופיע לפני השימוש!
הצהרה+מימוש
יש להעביר לפונקציה משתנים
כמספר הפרמטרים שהיא דורשת,
מאותו הסוג ובאותו הסדר!
נשים לב כי בקריאה לפונקציה מעבירים רק
את שמות המשתנים ,ולא את הטיפוסים,
בניגוד להצהרת ומימוש הפונקציה!
איחסון הערך המוחזר מהפונקציה
) int power(int a , int b
{
;int i, result=1
)for (i=0 ; i < b ; i++
;result *= a
;return result
}
)(void main
{
;int base, exponent, result
;“ cout << "Please enter base and exponent:
;cin >> base >> exponent
שימוש
;) result = power( base , exponent
;cout << base << “^” << exponent << “=“ << result << endl
}
17
© Keren Kalif
max תוכנית שלמה עם הפונקציה:דוגמא
int max (int x, int y)
{
if (x > y)
return x;
else
return y;
}
void main()
{
int num1, num2, maximum;
בשורה זו מוערך הביטוי מימין וערכו נכנס
cout << "Enter 2 numbers: ";
. maximum לתוך המשתנה
cin >> num1 >> num2;
!לא חייבים לאחסן את הערך המוחזר
maximum = max(num1, num2);
cout << "The max is “ << maximum << endl;
}
18
© Keren Kalif
..ואפשר גם כך
int max (int x, int y)
{
if (x > y)
return x;
return y; // witout the ‘else’…
}
void main()
{
int num1, num2, maximum;
cout << "Enter 2 numbers: “;
cin >> num1 >> num2;
maximum = max(num1, num2);
cout << "The max is “ << maximum << endl;
}
19
© Keren Kalif
מחסנית הקריאות
כאשר קוראים לפונקציה ,למעשה "קופצים" לאוסף
הפקודות שלה ,ושומרים את מקום החזרה
כאשר נתקלים בפקודה returnבתוך גוף הפונקציה,
חוזרים לביצוע הפעולות מהמקום בו קראנו לה
בפונקציה שהטיפוס המוחזר שלה הוא voidיתכן ולא תהייה
הפקודה return
20
© Keren Kalif
במקרה זה ,לאחר סיום הפונקציה ,חוזרים לביצוע הפעולות מהמקום בו
קראנו לפונקציה
הרצה- תוכנית שלמה עם פונקציה
#include <iostream>
using namespace std;
int power(int a, int b)
{
int i, result=1;
for (i=0 ; i < b ; i++)
result *= a;
return result;
}
void main()
{
int base, exponent, result;
cout << "Please enter base and exponent: “;
cin >> base >> exponent;
}
power
main
( line 4)
מחסנית הקריאות
result = power(base, exponent);
cout << base << “^” << exponent << “=“ << result << endl;
21
© Keren Kalif
סדר ביצוע פעולות
#include <iostream>
using namespace std;
int power(int a, int b)
{
cout << "debuging.. in 'power'\n”;
int i, result=1;
for (i=0 ; i < b ; i++)
result *= a;
return result;
}
void main()
קודם תבוצע הפונקציה
{
cout << "2^3=“ << power(2, 3) << endl;
}
22
© Keren Kalif
)2( סדר ביצוע פעולות
#include <iostream>
using namespace std;
int power(int a, int b)
{
cout << "debuging.. in 'power': a=“ << a << “ b=“ << b < endl;
int i, result=1;
for (i=0 ; i < b ; i++)
result *= a;
return result;
}
void main()
ביצוע הפונקציות מתבצע מימין לשמאל
{
cout << "2^4=" << power(2, 4)
<< " 2^5=" << power(2, 5) << endl;
23
© Keren Kalif
}
כיצד נראה הזיכרון
ראינו את הזיכרון של הmain -
תזכורת :ה main -הוא פונקציה
לכל פונקציה יש שטח נפרד בזיכרון ה stack -בו מוגדרים
משתניה
פונקציה יכולה לגשת לשטח הזיכרון שלה בלבד ,ולכן אינה
יכולה לגשת למשתנים שהוגדרו בפונקציות אחרות
הפרמטרים המועברים לפונקציה הם גם משתנים של הפונקציה
הפרמטרים המועברים לפונקציה הם העתקים של המשתנים
שנשלחו ,ולכן העברת פרמטרים כזו נקראת by value
כאשר יוצאים מפונקציה ,התאים במחסנית שבהם נשמרו
ערכיה נמחקים ,ולכן קריאה נוספת לפונקציה תייצר אותם
מחדש
24
© Keren Kalif
הזיכרון- תוכנית שלמה עם פונקציה
#include <iostream>
using namespace std;
int power(int a, int b)
{
int i, result=1;
for (i=0 ; i < b ; i++)
result *= a;
return result;
}
void main()
{
int base, exponent, result;
int:base
???
2
1000
int: exponent
???
3
1004
int: result
???
8
1008
main -הזיכרון של ה
וזהmain - שבresult
המשתנה
2
2000
,שבפונקציה אינם אותו משתנה
int: b
2004
!יהיו עם שם זהה3הכרח שהם
ואין
int: i
???
…
2008
int: a
int: result
cout << "Please enter base and exponent: “;
cin >> base >> exponent;
}
???
1
8
2012
power הזיכרון של
power
main
( line 4)
מחסנית הקריאות
result = power(base, exponent);
cout << base << “^” << exponent << “=“ << result << endl;
25
© Keren Kalif
הפרדה בין הצהרות למימוש
#include <iostream>
using namespace std;
// prototypes
int power(int,
power(int base,
int); int exponent);
בשורת ההצהרה לא חייבים
לציין את שמות המשתנים
void main()
{
int base, exponent, result;
cout << "Please enter base and exponent: “;
cin >> base >> exponent;
result = power(base, exponent);
; ובסיום כל הצהרה,main -• את ההצהרות נרשום לפני ה
cout << base << “^” << exponent << “=“ << result << endl;
)טיפוס- (אבprototype • חלק זה נקרא
}
main -• את המימושים נכתוב מתחת ל
int power(int base, int exponent)
• ההפרדה היא מאחר ומעניין אותנו איזה פונקציות יש
{
ופחות איך הן מבצעות את העבודה,בתוכנית
int i, result=1;
• חתימת הפונקציה בהגדרה בראש הקובץ ובמימוש חייבות
for (i=0 ; i < exponent ; i++)
result *= base;
להיות זהות
return result;
26
© Keren Kalif
}
תכנון TOP-DOWN
כאשר ניגשים לכתוב תוכנית ,יש לחשוב מהי הבעיה
הגדולה שעלינו לפתור ונחלק אותה לחלקים
לכל חלק "קשה" נתייחס כאל "קופסא שחורה" (שתמומש
בהמשך)
כאשר נממש כל חלק "קשה" אנו מתמודדים עם בעיה
יותר קטנה שננסה גם אותה לפרק לבעיות יותר קטנות,
עד אשר נגדיר בעיות מספיק קטנות
27
© Keren Kalif
TOP-DOWN תכנות
ציור משולש:דוגמא
#include <iostream>
using namespace std;
// prototypes
void printLine(int, char);
void printTriangle(int, char);
int:
void main()
{
int base;
char ch;
length
char: ch
int: i
2
1
3
2000
‘$’
2004
???
1
2
3
4
2005
printLine הזיכרון של
cout << "Enter base and char: “;
cin >> base >> ch;
printTriangle(base, ch);
}
int:
int:
base
char: ch
base
???
3
3000
char: ch
???
‘$’
3004
int: i
main -הזיכרון של ה
void printLine(int length, char c)
{
int i;
for ( i=1 ; i<=length ; i++ )
cout << c;
cout << endl;
}
void printTriangle(int base, char ch)
{
int i;
print Line
for( i=1 ; i<=base ; i++ )
printLine(i, ch); print Triangle
3}
1000
‘$’
1004
???
1
2
3
4
1005
printTriangle הזיכרון של
(line 3)
main
( line 5)
מחסנית הקריאות
28
© Keren Kalif
דוגמאת סיכום :חישוב מרחק בין 2
נקודות
הנוסחא לחישוב מרחק בין 2הנקודות ) (x1,y1ו:(x2,y2) -
2
נרצה לכתוב פונקציה המקבלת 4קואורדינטות המייצגות 2נקודות,
ומחזירה את המרחק בניהן:
x1
y1
x2
y2
)d (x 2 x 1) (y 2 y1
2
int
int
int
int
distance
float dist
לכן ההצהרה של הפונקציה תראה כך:
;)float distance(int x1, int y1, int x2, int y2
29
© Keren Kalif
דוגמא :חישוב מרחק בין 2נקודות
()2
הנוסחא לחישוב מרחק בין 2הנקודות ) (x1,y1ו:(x2,y2) -
d (x 2 x 1) 2 (y 2 y1) 2
ניתן לראות שכדי לחשב מרחק בין שתי נקודות צריך לדעת לחשב
חזקה ושורש ,לכן נגדיר ונשתמש ב 2 -פונקציות עזר:
30
© Keren Kalif
sqrtהמקבלת מספר floatומחזירה את השורש שלו .פונקציה זו כבר
קיימת בספריה math.h
powerשאותה נכתוב בעצמנו לצורך התרגול (למרות שהיא גם קיימת ב-
)math.h
float distance(int x1, int y1, int x2, int y2)
#include <iostream>
לספריהinclude
{ שנשתמש הצהרות
using namespace std; בפונקציהעלממנה
פונקציות שנממש
float xDiffPower, yDiffPower;
#include <math.h>
float result;
int power(int base, int exponent);
xDiffPower = power(x2-x1, 2);
float distance(int x1, int y1, int x2, int y2);
yDiffPower = power(y2-y1, 2);
void main()
result = sqrt(xDiffPower+yDiffPower);
{
return result;
int x1, y1, x2, y2;
???
0
int: x1
1000
}
float dist;
int: y1
???
0
1004
cout << "Please enter 2 points (x1, y1, x2, y2): “;
יש מרחבpower -גם ל
cin >> x1 >> y1 >> x2 >> y2;
אך לא נראה אותו פה,זיכרון
מימוש הפונקציות שלנו
)+
;
מקום
קריאהחוסר
מפאת
לפונקציות
dist = distance(x1, y1, x2, y2);
}
cout << "The distance is “ << dist << endl;
int power(int base, int exponent)
{
int i, result=1;
for (i=0 ; i < exponent ; i++)
result *= base;
return result;
}
31
© Keren Kalif
power
sqrt
distance
(line 5)
3)
4)
main
( line 5)
מחסנית הקריאות
int: x2
???
4
1008
int: y2
???
4
1012
float: dist
5.6
???
1016
1016
main -הזיכרון של ה
int: x1
0
2000
int: y1
0
2004
int: x2
4
2008
int: y2
4
2012
float: xDiffPower
16
???
2016
float: yDiffPower
16
???
2024
float: result
5.6
???
2032
distance הזיכרון של
פונקציות ספריה
כל תוכנית בשפת תכנות עילית ,ובפרט שפת ,C++
מורכבת מאוסף של פונקציות
קיימות פונקציות ספריה בשפה אותן ניתן להכליל בתוכנית
ולקרוא להן בכל מקום בו נרצה ולהשתמש בהן כמה
פעמים שנרצה
למשל כאשר עושים:
>#include <iostream
ניתן להשתמש בפונקציות המוגדרות בספריה זו בכל מיני
מקומות בקוד (למשל )cin, cout
גם אנו יכולים לכתוב פונקציות או ספריות של פונקציות,
שאותן ניתן להכליל ולהשתמש בהן כרצוננו
32
© Keren Kalif
יצירת קובץ ספריה
עד כה השתמשנו בפונקציות שקיבלנו משפת C
כל פונקציה כזו נכתבה בספריה שאליה ביצענו include
למשל כדי להשתמש בפונקציות של מחרוזות ,עשינו
>#include <string.h
כדי לייצר ספריה משלנו נייצר 2קבצים חדשים:
<file_name>.hהוא קובץ אשר יכלול את ה-
prototypesשל הפונקציות ,ואליו אח"כ נעשה include
מהקובץ שירצה להשתמש בפונקציות המוגדרות בו
<file_name>.cppהוא קובץ שיכיל includeלקובץ ה-
headerהתואם ויממש את הפונקציות המוגדרות בו
include לספריה שכתבנו יהיו בתוך "" (גרשיים) ולא
בתוך <>
33
© Keren Kalif
)1( דוגמא – ספריה המטפלת בתווים
character.h הקובץ
// prototypes
int isSmallLetter(char);
int isCapitalLetter(char);
int isAlpha(char);
int isDigit(char);
34
© Keren Kalif
)2( דוגמא – ספריה המטפלת בתווים
#include "charachter.h"
character.cpp הקובץ
int isSmallLetter(char ch)
{
return (ch >= 'a' && ch <= 'z');
}
int isCapitalLetter(char ch)
{
return (ch >= 'A' && ch <= 'Z');
}
int isAlpha(char ch)
{
return (isSmallLetter(ch) || isCapitalLetter(ch));
}
int isDigit(char ch)
{
return (ch >= '0' && ch <= '9');
}
35
© Keren Kalif
)3( דוגמא – ספריה המטפלת בתווים
#include <iostream>
using namespace std;
#include "charachter.h"
main.cpp הקובץ
void main()
{
char ch=0;
printf("Please enter a charachter: ");
ch = getchar();
printf("Is '%c' a digit? %d\n", ch, isDigit(ch));
printf("Is '%c' a small letter? %d\n", ch, isSmallLetter(ch));
printf("Is '%c' a capital letter? %d\n", ch, isCapitalLetter(ch));
printf("Is '%c' a letter? %d\n", ch, isAlpha(ch));
36
}
© Keren Kalif
ספריות שלנו -סיכום
ניתן לכתוב את כל הקוד בקובץ אחד ,אבל אם אנחנו
כותבים קוד כללי ,שיתכן ויהיה שימושי גם במקומות
אחרים ,נעדיף לחלק את הקוד לקובץ ספריה נפרד
מי שירצה להשתמש בספריה שלנו ,יתעניין מה יש לה
להציע ,ולא איך היא מבצעת את הפעולות ,וכך הוא יוכל
להסתכל בקובץ headerבלבד בו יש ריכוז של כל
הפונקציות
מכירת הספריה ללקוח תסתכם בקובץ ה h -ובקובץ
בינארי שהוא תוצר של הקומפילציה ,וכך לא נחשוף את
ה"איך"
37
© Keren Kalif
דוגמא איך לא כותבים פונקציה
#include <iostream>
using namespace std;
void power()
{
int base, exponent, i, result=1;
cout << "Please enter base and exponent: ";
cin >> base >> exponent;
for (i=0 ; i < exponent ; i++)
result *= base;
cout << "The result is “ << result << endl;
}
void main()
{
power();
}
38
© Keren Kalif
מדוע לא לכתוב קוד כמו הדוגמא הקודמת?
ניתן לעקוף חלקית את הרעיון של העברת ארגומנטים והחזרת ערך
מפונקציות ע"י כך שפונקציה תבקש מהמשתמש את הנתונים
ותדפיס בסוף את התוצאה
לא נעשה זאת!
נמנע מלקלוט קלט בתוך פונקציה ,אלא אם זה יעוד הפונקציה
הסיבה :יתכן ופונקציה אחרת צריכה להשתמש בנתון המתקבל או
להעביר נתון ,ולא ניתן לצפות מי ישתמש בפונקציה שלנו
למשל ,כמו בדוגמא הקודמת ,הפונקציה המחשבת חזקה לא קיבלה
פרמטרים ולא החזירה את התוצאה
דוגמא :כמו בפונקציה distanceהקוראת לpower -
כדי לבדוק פונקציה ,נקלוט את הנתונים ב main -ונעבירם
לפונקציה .את התוצאה נדפיס ב.main -
39
© Keren Kalif
העברת פרמטר לפונקציה – by reference
ראינו:
כאשר מעבירים משתנה לפונקציה עותק שלו מועבר למחסנית
של הפונקציה (העברה )by value
אם בפונקציה משנים את הפרמטר זה לא משפיע על המשתנה
המקורי
מה יקרה כאשר נרצה שהפונקציה תשנה דווקא את
הפרמטרים שקיבלה?
40
© Keren Kalif
נעביר הפניה למשתנה המקורי...
1.
2.
3.
4.
void changeTo4byVal(int x)
{
x = 4;
{
int: x
5.
6.
7.
8.
9.
by ref שליחת פרמטר
by val לעומת
void changeTo4byRef(int& x)
}
x = 4;
}
10.
void main()
12. }
13.
int num = 10;
11.
10
4
2000
chagneTo4ByVal -הזכרון של ה
int: x
???
chagneTo4ByRef -הזכרון של ה
int: num
10
4
1000
main -הזכרון של ה
14.
cout << "orig num = " << num << endl;
15.
16.
changeTo4byVal(num);
cout << "after changeTo4byVal: num = " << num << endl;
17.
18.
19.
changeTo4byRef(num);
cout << "after changeTo4byRef: num = " << num << endl;
20.
21.
22.
{
41
© Keren Kalif
העברת פרמטר לפונקציה – by reference
בהגדרת הפרמטרים שהשיטה מקבלת מציינים ליד
הפרמטר &
זהו למעשה מתן שם נוסף לארגומנט המקורי שנשלח הנגיש
מתוך הפונקציה
אין הבדל סינטקטי בשליחת המשתנה בעבור משתנה
שעובר by valueאו by reference
42
© Keren Kalif
swap : – דוגמאby value העברה
מספרים ומחליפה בינהם2 פונקציה המקבלת:המטרה
void swap(int a, int b)
{
cout << "In function, before swap: a=“ << a << “ b=“ << b << endl;
int temp = b;
b = a;
a = temp;
cout << "In function, after swap: a=“ << a << “ b=“ << b << endl;
}
3
2
int: a
2000
...הפונקציה לא באמת החליפה בין הערכים
void main()
{
int num1=2, num2=3;
}
int: b
int: temp
2
3
2004
3
???
2008
swap הזיכרון של
cout << "In main, before swap: num1=“ << num1 << “, num2=“ << num2;
swap(num1, num2);
cout << "In main, after swap: num1=“ << num1 << “, num2=“ << num2;
int: num1
2
???
1000
3
???
1004
main -הזיכרון של ה
int: num2
43
© Keren Kalif
swap הדוגמא
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
{
void main()
}
int x=3, y=5;
int: a
???
int: b
???
int: temp
???
3
2000
swap -הזכרון של ה
int: x
???
3
5
1000
int: y
???
5
3
1004
main -הזכרון של ה
cout << "Before swap: x = " << x << ", y = " << y << "\n";
swap(x, y);
cout << "After swap: x = " << x << ", y = " << y << "\n";
{
44
© Keren Kalif
החזרת יותר מערך יחיד מפונקציה
למשל נרצה לכתוב פונקציה המקבלת מערך ,וצריכה
להחזיר מהו המספר המקסימאלי ומה המינימאלי
הפתרון :העברה לפונקציה פרמטר שעתיד להחזיק את
התוצאה הסופית
45
© Keren Kalif
מציאת מינימום ומקסימום
void minMax(int arr[], int size, int& min, int& max)
{
int i;
min = max = arr[0];
for ) i=1 ; i < size ; i++ (
}
if (min > arr[i])
min = arr[i];
if (max < arr[i])
max = arr[i];
}
}
כאשר מעבירים:תזכורת
מערך לפונקציה מתייחסים
! ולא לעותק שלו,למערך המקורי
void main()
{
int arr[] = {5,2,8};
int minimum, maximum;
}
int: size
3
2000
int: min
int: max
int:i
???
1
2
3
2004
minMax הזיכרון של
int[]: arr
int:minimum
5
1000
2
1004
8
1008
5
2
???
1012
5
???
8
1016
main -הזיכרון של ה
int:maximum
minMax(arr, sizeof(arr)/sizeof(arr[0]), minimum, maximum);
cout << "max is “ << maximum << “ and min is “ << minimum << endl;
46
© Keren Kalif
הארות
ניתן היה להעביר לפונקציה רק את minאו רק את max
ואת הערך השני להחזיר ע"י return
אבל :כאשר הפונקציה מחזירה יותר מערך אחד והם כולם בעלי
אותה תפקיד ,נעדיף שכולם יוחזרו (by refאחידות בסגנון)
אם מעבירים לפונקציה פרמטר by refשהפונקציה
מתבססת על ערכו ,חובה לאתחלו בפונקציה ,ולא
להתבסס על אתחול (שאולי) בוצע בפונקציה שקראה
47
© Keren Kalif
by ref אתחול פרמטר המועבר
void countPositive(int arr[], int size, int& count)
{
int i;
count = 0; // it is our responsibility to
// initialize the value!
for (i=0 ; i < size ; i++ )
{
if (arr[i] > 0)
count++;
}
}
int: size
3
2000
0
1
2
???
3
2008
2004
int: count
int: i
countPositive הזיכרון של
int[]: arr
-4
1000
2
1004
-8
1008
void main()
1
0
? 1012
int: numOfPositive
{
main -הזיכרון של ה
int arr[] = {-4,2,-8};
int numOfPositive; // we can’t assume that who wrote
// the main initialized that variable!!
}
countPositive(arr, sizeof(arr)/sizeof(arr[0]), numOfPositive);
cout << "There are “ << numOfPositive << “ positive numbers in the array\n”;
48
© Keren Kalif
by value תזכורת למשמעות של העברה
void incNumber(int x)
{
cout << "In function: number before: “ << x << endl;
x++;
cout << "In function: number after: “ << x << endl;
{
int:
void main()
}
int num = 3;
3
4
x
2000
incNumber הזיכרון של
int:
num
??
3
1000
main הזיכרון של
cout << "In main: number before function: “ << num << endl;
incNumber(num);
cout << "In main: number after function: “ << num << endl;
{
49
© Keren Kalif
דוגמא- מערכים כפרמטר לפונקציה
void incArray(int arr[], int size)
{
for (int i=0 ; i < size ; i++)
arr[i]++;
}
void printArray(int arr[], int size)
{
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
}
void main()
{
int arr[] = {4,3,8};
int size = sizeof(arr)/sizeof(arr[0]);
cout << "Orig array: “;
printArray(arr, size);
incArray(arr, size);
}
cout << "Array after increment: “;
printArray(arr, size);
50
© Keren Kalif
מערכים כפרמטר לפונקציה
ראינו כי מערך מתנהג באופן שונה מאשר משתנה מטיפוס
בסיסי כאשר מעבירים אותו לפונקציה
כאשר מעבירים מערך לפונקציה ,לא מועבר עותק של
המערך ) ,(by valueאלא מועברת רק כתובת ההתחלה
של המערך
לכן כאשר מעבירים מערך לפונקציה ומשנים אותו ,השינוי
משפיע על המערך המקורי ,ולא על עותק – בניגוד לכל
משתנה אחר שאנחנו מכירים!
נסביר לעומק כאשר נלמד את השיעור על מצביעים
51
© Keren Kalif
העברת מספר האיברים במערך
כפרמטר לפונקציה
ראינו כי כאשר שלחנו מערך לפונקציה העברנו גם
את מספר האיברים שבו ,ולא התבססנו על ערך
קבוע
זאת כדי שהפונקציה תהיה מספיק כללית על-מנת
שתבצע את העבודה על מערכים בגדלים שונים
52
© Keren Kalif
דוגמא איך פונקציה המקבלת מערך
לא צריכה להיות
שימו לב :הקבוע מוגדר באותיות גדולות .דגש בהמשך...
;const int SIZE = 3
הפונקציה יודעת לטפל אך ורק במערכים בגודל הקבוע
)][void printArray(int arr
,SIZEולא תעשה את המתבקש עבור מערכים בגודל שונה
{
)for (int i=0 ; i < SIZE ; i++
;" " << ]cout << arr[i
;cout << endl
}
)(void main
{
;}int arr1[SIZE] = {1,2,3}, arr2[5]={10,20,30,40,50
;" cout << "arr1:
;)printArray(arr1
;" cout << "arr2:
;)printArray(arr2
53
© Keren Kalif
}
דוגמא איך פונקציה המקבלת מערך
כן צריכה להיות
const int SIZE = 3;
void printArray(int arr[], int size)
{
for (int i=0 ; i < size ; i++)
cout << arr[i] << " ";
cout << endl;
}
שוניםSIZE והקבועsize הפרמטר:שימו לב
.)case sensitive (הקומפיילר הוא
הפונקציה מקבלת כפרמטר נוסף את כמות האיברים
ולכן יודעת לטפל במערך בכל גודל,שעליה להדפיס
void main()
{
int arr1[SIZE] = {1,2,3}, arr2[5]={10,20,30,40,50};
cout << "arr1: ";
printArray(arr1, SIZE);
cout << "arr2: ";
printArray(arr2, 5);
}
54
© Keren Kalif
– העברת מטריצה לפונקציה
הפונקציה מספיק כללית כדי לבצע את
דוגמא
const int COLS = 5; העבודה עבור מטריצה עם כל מספר שורות
void setMatrix(int mat[][COLS], int rows)
}
for (int i=0 ; i < rows ; i++)
}
for (int j=0 ; j < COLS ; j++)
mat[i][j] = i*10+j;
{
{
void printMatrix(int mat[][COLS], int rows)
}
for (int i=0 ; i < rows ; i++)
}
for (int j=0 ; j < COLS ; j++)
cout << mat[i][j] << “\t“;
cout << endl;
{
{
void main()
}
int mat1[3][COLS];
int mat2[4][COLS];
setMatrix(mat1, 3);
setMatrix(mat2, 4);
cout << "Matrix 1:\n";
printMatrix(mat1, 3);
cout << "Matrix 2:\n”;
printMatrix(mat2, 4);
{
55
© Keren Kalif
העברת מטריצה לפונקציה –
דוגמא (פלט)
56
© Keren Kalif
העברת מטריצה לפונקציה
גם כאשר מעבירים מטריצה לפונקציה ,עוברת כתובת
ההתחלה בלבד ,ולכן שינוי המטריצה בפונקציה משנה את
המטריצה המקורית
כאשר מעבירים מטריצה לפונקציה ,ניתן לציין רק את
כמות העמודות ולהשאיר את הסוגריים של השורות ריקים
(במקום להעביר כפרמטר את מספר השורות)
57
© Keren Kalif
נרצה להעביר את כמות השורות כדי שהפונקציה תהיה מספיק
כללית לכל מטריצה עם אותו מספר עמודות
בהמשך נראה שאפשר גם להעביר מטריצה שמספר העמודות
בה שונה
יצירת מספרים אקראיים
אשר מגרילה מספרrand() בשפה קיימת הפונקציה
MAX_RAND לקבוע0 טווח הערכים שיוגרלו הינו בין
stdlib.h קבוע זה מוגדר בספריה
#include <iostream>
using namespace std;
#include <stdlib.h> // for ‘RAND_MAX’
void main()
{
cout <<"rand gives value between 0-" << RAND_MAX << ":\n";
for (int i=0 ; i < 5 ; i++)
cout << rand() << " ";
cout << endl;
}
58
© Keren Kalif
יצירת מספרים אקראיים ()2
rand פועלת עפ"י אלגוריתם קבוע המתבסס על מספר
התחלתי כלשהו (נקרא ,)seed numberולכן תמיד
בכל הרצה תחזיר ערכים זהים
כדי ש rand -באמת תחזיר מספרים אקראיים ,כלומר
תתבסס על מספר התחלתי שונה בכל פעם ,יש להפעיל
את הפונקציה srandעם ערך התחלתי המייצג את
הזמן הנוכחי (והוא אכן יחודי בכל הרצה)
59
© Keren Kalif
)3( יצירת מספרים אקראיים
#include <iostream>
using namespace std;
#include <stdlib.h> // for ‘RAND_MAX’
#include <time.h> // for 'time'
מחזירה מספר המייצג את הזמן הנוכחיtime(NULL)
-)1.1.1970 -(מספר השניות שעברו מאז ה
void main()
{
srand ( time(NULL) ); // initialize random seed
cout <<"rand gives value between 0-" << RAND_MAX << ":\n";
for (int i=0 ; i < 5 ; i++)
cout << rand() << " ";
cout << endl;
}
60
© Keren Kalif
יצירת מספרים אקראיים בטווח מסוים
#include <iostream>
using namespace std;
#include <stdlib.h> // for ‘RAND_MAX’
#include <time.h> // for 'time'
void main()
{
srand ( time(NULL) ); // initialize random seed
cout <<"rand gives value between 1-6 \n";
for (int i=0 ; i < 5 ; i++)
cout << rand()%6+1 << " ";
ומאחר,0-5 תחזיר לנו ערכים בין%6 פעולת
..1 הוספנו1-6 ואנחנו רוצים בטווח בין
cout << endl;
}
61
© Keren Kalif
)2( מסוים
יצירת מספרים אקראיים בטווח
#include <iostream>
using namespace std;
#include <stdlib.h> // for ‘RAND_MAX’
#include <time.h> // for 'time'
void main()
{
srand ( time(NULL) ); // initialize random seed
cout <<"rand gives value between -10 to 10 \n";
for (int i=0 ; i < 5 ; i++)
תחזיר לנו ערכים בין%21 פעולת
cout << rand()%21-10 << " ";
ומאחר ואנחנו רוצים בטווח,0-20
10 החסרנו10 - ל-10 בין
cout << endl;
62
}
© Keren Kalif
)-; טובהrand זו לא פונקציית,אגב
https://sslimgs.xkcd.com/comics/random_number.png
63
© Keren Kalif
פונקציות נפוצות שיש בספריה math.h
:cos, sin מחשבות סינוס וקוסינוס בהתאמה (לשים לב
שמקבלות זוית ברדיאנים ,ולא במעלות)
:ceil מעגלת מספר כלפי מעלה ,מחזירה float
:floor מעגלת מספר כלפי מטה ,מחזירה float
:abs נותנת ערך מוחלט של מספר ועושה floorלשלם
:pow מחשבת חזקה
:sqrt מחשבת שורש
64
© Keren Kalif
דוגמאות חישוב
#include <iostream>
using namespace std;
#include <math.h>
void main()
{
cout << "cos(90)=" << cos(90.0) << endl;
cout << "sin(90)=" << sin(90.0) << endl;
cout << "ceil(4.2)=" << ceil(4.2) << endl;
cout << "floor(4.2)=" << floor(4.2) << endl;
cout << "abs(-5.8)=" << abs(-5.8) << endl;
cout << "abs(5)=" << abs(5) << endl;
cout << "pow(2,3)=" << pow(2.0, 3) << endl;
cout << "pow(8,0.5)=" << pow(8.0, 0.5) << endl;
cout << "sqrt(16)=" << sqrt(16.0) << endl;
cout << "sqrt(18)=" << sqrt(18.0) << endl;
}
65
© Keren Kalif
תהליך הפיכת תוכנית cppלשפת מכונה
Disk
Editor
Disk
Preprocessor
Disk
Disk
Compiler
Linker
התוכנית נכתבת בעורך טקסטואלי ונכתבת לדיסק
קדם המעבד עובר על הקוד ומבצע החלפות נחוצות
(כל הפקודות המתחילות ב ,# -כמו include
עבור כל קובץ ,cppהקומפילר יוצר קובץ objבשפת מכונה ושומר
אותו על הדיסק .בשלב זה רק נבדקת תקינות הסינטקס בפונקציות.
ה linker -קושר את קובץ ה obj -עם הספריות ,ויוצר קובץ exeהמוכן
להרצה ונשמר בדיסק .כלומר ,מקשר בין קריאה לפונקציה ,למימוש
שלה.
Primary Memory
Loader
..
..
..
Disk
Primary Memory
CPU
..
..
..
בעת ההרצה ,טוענים את התוכנית מהדיסק לזיכרון הראשי
עם הרצת התוכנית ,ה cpu -עובר על כל פקודה ומריץ אותה
66
© Keren Kalif
דוגמא לשגיאת קומפילציה
#include <iostream>
using namespace std;
void foo(int x)
}
cout >> “x=“ >> x >> endl;
{
void main()
:נקבל את שגיאת הקומפילציה הבאה
{
error C2065: 'i' : undeclared identifier
for (i=0 ; i < 5 ; i++)
}
:goo הקומפיילר רק נותן אזהרה שהוא לא מוצא את
cout << i;
warning C4013: 'goo' undefined; assuming
goo(i);
extern returning int
{
}
הקומפיילר אינו ממשיך לתהליך לינקר,במידה ויש שגיאות קומפילציה
67
© Keren Kalif
דוגמא לשגיאת לינקר
#include <iostream>
using namespace std;
void foo(int x)
}
cout >> “x=“ >> x >> endl;
{
void main()
{
for (int i=0 ; i < 5 ; i++)
}
cout << i;
goo(i);
{
}
תוכנית זו מתקמפלת (אין שגיאות סינטקטיות בתוך
,הפונקציות) ולכן הקומפיילר עובר לתהליך הלינקר
:ומוציא את השגיאה הבאה
error LNK2019: unresolved external symbol
_goo referenced in function _main
68
© Keren Kalif
!נכון שזה נכון?? אז די
http://programmingpalace.files.wordpress.com/2011/12/smokingwarningforsoftwareengineers.j
pg
69
© Keren Kalif
סוגי משתנים
משתנים מקומיים
משתנים סטטיים
משתנים גלובליים
70
© Keren Kalif
משתנים מקומיים
עד כה ראינו שכל הקוד שלנו מורכב מפונקציות
המשתנים שבתוך כל פונקציה (גם אלו שהועברו
כפרמטרים) נקראים משתנים מקומיים (לוקאליים) וטווח
ההכרה שלהם הוא רק בפונקציה בה הם מוגדרים
ערכו של משתנה לוקאלי נשמר כל עוד אנחנו בפונקציה,
והוא נמחק ביציאה ממנה
משתנה לוקאלי מאוחסן במחסנית של הפונקציה
בכל קריאה חדשה לפונקציה המשתנים מאותחלים מחדש
ונמחקים בסיום ביצוע הפונקציה
ערכו זבל במידה ולא אותחל
71
© Keren Kalif
משתנה סטטי הוא משתנה שערכו
נשמר בין הקריאות השונות לפונקציה
#include <iostream>
using namespace std;
משתנים סטטיים
כדי להגדיר משתנה סטטי
נשתמש במילת המפתח
לפני טיפוס המשתנהstatic
int counter()
משתנה סטטי מאותחל רק
{
בקריאה הראשונה לפונקציה
counter הזיכרון של
static int count = 0;
count++;
return count;
3
2
1
0
counter::count = ?
}
main -הזיכרון של ה
data segment -זיכרון ה
void main()
{
cout << "'counter' was called “ << counter() << “ times\n” ;
cout << "'counter' was called “ << counter() << “ times\n” ;
counter
cout << "'counter' was called “ << counter() << “ times\n” ;
main
}
( line 3)
1)
2)
מחסנית הקריאות
72
© Keren Kalif
משתנים סטטיים
בדומה למשתנים מקומיים ,המשתנים הסטטיים מוכרים
אך ורק בתוך הפונקציה אשר בה הם הוגדרו
משך חייהם מרגע תחילת הריצה של התוכנית ועד סיום
ריצת התוכנית
מאחר ושטח הזיכרון של כל פונקציה נמחק עם היציאה
ממנה ,משתנים סטטיים נשמרים באזור זיכרון אחר
הנקרא ,data-segmentהקיים לאורך כל חיי התוכנית,
והם משתחררים רק עם היציאה מהתוכנית
73
© Keren Kalif
משתנה גלובלי מוגדר בראש התוכנית
)(אינו משויך לאף פונקציה
כל הפונקציות שכתובות
בקובץ בו הוגדר יכולות
לגשת אליו ולשנות את ערכו
משתנים גלובליים
int global = 3;
void incGlobal()
{
global++;
cout << "In function: global=“ << global << endl;
}
data -זיכרון ה
11
3
4
incGlobal הזיכרון של
global = 10
segment
void main()
{
cout << "At first, global=“ << global << endl;
main -הזיכרון של ה
incGlobal();
cout <<"After function (1), global=“ << global << endl;
global = 10;
incGlobal
cout << "In main after change global, global=“ << global << endl;
incGlobal();
main
cout << "After function (2), global=“ << global << endl;
מחסנית הקריאות
}
74
© Keren Kalif
משתנים גלובליים
במידה ולא אותחל ערכו ,0ולא זבל
משתנה גלובלי קיים לאורך כל חיי התוכנית
השימוש בו הוא בעייתי ולכן נשמר למצבים מיוחדים בלבד
כל אחד יכול לשנות אותו זה פוגע ברעיון של הסתרת המידע
ושכל פונקציה מסתמכת רק על נתונים שהם פנימיים לה
מאחר ואינו משויך לאף פונקציה ,הוא אינו נמצא על
המחסנית ,אלא על חלקת הזיכרון המשותפת לכל
הפונקציות הdata-segment -
75
© Keren Kalif
השוואה בין סוגי המשתנים השונים
משתנה מקומי (רגיל) משתנה גלובלי
משתנה סטטי
היכן מוגדר
בתוך הפונקציה
מחוץ לפונקציות
בתוך הפונקציה
טווח
ההכרה
בפונקציה בה הוא
מוגדר
מנקודת הגדרתו ומטה
בפונקציה בה הוא
מוגדר
מי יכול
לגשת אליו
רק הפונקציה בה הוא
מוגדר
כל מקום בקוד הנמצא מתחת רק הפונקציה בה
הוא מוגדר
להגדרתו
מתי
מאותחל
בכל פעם כשנכנסים
לפונקציה
76
© Keren Kalif
בתחילת ריצת התוכנית
רק בפעם הראשונה
שמגיעים אליו
השוואה בין סוגי המשתנים השונים
משתנה מקומי (רגיל) משתנה גלובלי
משתנה סטטי
היכן מוגדר
בתוך הפונקציה
מחוץ לפונקציות
בתוך הפונקציה
טווח
ההכרה
בפונקציה בה הוא
מוגדר
מנקודת הגדרתו ומטה
בפונקציה בה הוא
מוגדר
מי יכול
לגשת אליו
רק הפונקציה בה הוא
מוגדר
כל מקום בקוד הנמצא מתחת רק הפונקציה בה
הוא מוגדר
להגדרתו
מתי
מאותחל
בכל פעם כשנכנסים
לפונקציה
77
© Keren Kalif
בתחילת ריצת התוכנית
רק בפעם הראשונה
שמגיעים אליו
ביחידה זו למדנו:
מהי פונקציה ,פרמטרים וערך מוחזר
כיצד נראה הזיכרון
ספריות של פונקציות
מערכים כפרמטר לפונקציה
יצירת מספרים אקראיים
פונקציות מmath.h -
תהליך הפיכת תוכנית ב C++ -לשפת מכונה
סוגי משתנים וטווח הכרתם :לוקאליים ,גלובליים ,סטטיים
78
© Keren Kalif
תרגיל :1
כתוב פונקציה המקבלת 2מספרים
במידה ושני המספרים בעלי אורך זהה ,היא תחזיר בכמה
מיקומים זהים במספרים יש ספרה זהה
דוגמאות:
עבור 1594ו 7599 -יוחזר 2מאחר וספרת העשרות וספרת
המאות זהה בשני המספרים
עבור 9287ו 9487 -יוחזר ,3כי גם ספרות האחדות ,עשרות
ואלפים זהות בשני המספרים
במידה והמספרים שונים באורכם הפונקציה תחזיר -1
79
© Keren Kalif
תרגיל :2
כתוב פונקציה המקבלת 2מחרוזות .הפוקנציה תחזיר 1
האם המחרוזת השניה היא סופה של המחרוזת הראשונה
ו 0 -אחרת
דוגמאות:
80
© Keren Kalif
עבור abcdeו cde -יוחזר 1
עבור abcdeו cfe -יוחזר 0
עבור abcdeו zabcde -יוחזר 0
תרגיל :3
כתוב פונקציה המקבלת מערך של מספרים ,גודלו וספרה
הפונקציה תחזיר כמה ערכים במערך מכילים את הספרה
דוגמאות:
81
© Keren Kalif
עבור המערך 123, 456, 817, 991והספרה 1יוחזר ,3
מאחר והספרה 1מופיעה ב 3 -איברים במערך
עבור המערך 9988, 458, 751, 654, 888והספרה 8יוחזר
3מאחר והספרה 8מופיעה ב 3 -מספרים .שימו לב שאין לספור
את הספרה פעמיים עבור מספר מסויים!
תרגיל :4
כתוב פונקציה המקבלת מטריצה של מספרים ואת כמות
השורות במטריצה .הפונקציה תחזיר 1אם ערכי מסגרת
המטריצה שווים ל 1 -ו 0 -אחרת .אין חשיבות לערכם של
האיברים שאינם על המסגרת
דוגמא:
82
© Keren Kalif