המצגת נטענת. אנא המתן

המצגת נטענת. אנא המתן

תרגול מס' 8 משתנים מיוחסים מבוא ל-C++ קלט/פלט ב-C++

מצגות קשורות


מצגת בנושא: "תרגול מס' 8 משתנים מיוחסים מבוא ל-C++ קלט/פלט ב-C++"— תמליל מצגת:

1 תרגול מס' 8 משתנים מיוחסים מבוא ל-C++ קלט/פלט ב-C++
הקצאת זיכרון דינאמית מחלקות namespaces const

2 מבוא ל-C++ מבוא לתכנות מערכות

3 C++ C++ היא שפת תכנות שנבנתה מעל C
בעיקר עבור תכנות מונחה עצמים השלד הבסיסי של הקוד בשפה זהה ל-C רוב הקוד של C ניתן לקמפול ישירות כ-C++ נהוג ב-C++ לבצע דברים אחרת תוך שימוש ביתרונות השפה לא כותבים ב-C++ קוד בסגנון C! לא נהוג להשתמש ב-printf, FILE*, void* ועוד ב-C++ - יש תחליפים עדיפים #include <iostream> using namespace std; int main(int argc, char** argv) { cout << "Hello world" << endl; return 0; } דוגמאות לקוד C שלא יתקמפל כ-C++: בגלל תוספת של מילות מפתח חדשות כגון new הקוד הבא אינו חוקי ב-C++ int new = 5; אין המרה אוטומטית מ-void* לטיפוסי מצביעים אחרים. ב-C++ לא נהוג להמיר מצביעים בגלל סכנות אפשריות וקיום תחליפים עדיפים. int* ptr=malloc(sizeof(int)); // error, must explicitly cast malloc’s return value to int*. void* ptr = ...; int* ptr_int = ptr; // Again, must cast explictly. מבוא לתכנות מערכות

4 C++ כדי לקמפל קוד ++C נשתמש בקומפיילר g++
אופן השימוש זהה ל-gcc, למעשה gcc הוא g++. קבצי ה-header הסטנדרטיים של ++C הם ללא סיומת, למשל iostream קבצים הנכתבים על ידי משתמשים הם בעלי סיומת h כמו ב-C קבצי ה-source ב-C++ הם בעלי סיומת cpp, cc או C (גדולה) כדי לכלול קבצי header של C מוסיפים c בתחילת שמם, למשל: #include <cstdio> C99 הוא סטנדרט חדש של C אשר מוסיף תכונות פופולריות מ-C++: אתחול משתנים באמצע בלוק (ובתוך לולאת for או משפט if) ערכים קבועים - const שימוש בהערות של שורה אחת // כל התכונות האלה קיימות ב-C++ ובדרך כלל חשיבותן גבוהה לסגנון השפה סיומת cpp נהוגה ב-Windows. סיומת C רלוונטית רק ב-Unix מאחר ו-C שקולה ל-c ב-Windows ולא ניתן יהיה להבדיל בין קובץ מקור של C לשל C++ מבוא לתכנות מערכות

5 הקצאת זיכרון דינאמית new delete מבוא לתכנות מערכות

6 הקצאת זיכרון דינאמית הקצאות זיכרון דינאמיות ב-++C מתבצעות בעזרת האופרטורים new ו-delete ניתן להקצות משתנה חדש (ולאתחלו) על ידי new: int* ptr = new int; // No explicit initialization int* ptr = new int(7); // The new integer is initialized to 7 ניתן להקצות מערך של משתנים על ידי new[size_t]: int* array = new int[n]; שחרור של עצם שהוקצה על ידי new מתבצע בעזרת delete: delete ptr; שחרור של עצם שהוקצה על ידי new[size_t] מתבצע בעזרת delete[]: delete[] array; במקרה של הקצאת מערך לא ניתן לאתחל בצורה מפורשת את המשתנים (בהמשך נראה שהם מאותחלים על ידי בנאי חסר פרמטרים). שימו לב שבמקרה של משתנים פרימיטיביים (כמו int ו-double) לא יבוצע אתחול כלל והמערך מכיל זבל. (בדומה ל-malloc) אפשר לבצע delete ל-NULL בדיוק כמו free ב-C מבוא לתכנות מערכות

7 new ו-delete - דוגמאות C++ C אסור להתבלבל בין delete ו-delete[]
int* ptr = new int(5); int* array = new int[*ptr]; // No need to check for NULLs delete ptr; delete[] array; C++ int* ptr = malloc(sizeof(int)); if (!ptr) { // handle errors ... } *ptr = 5; int* array = malloc(sizeof(*array)* *ptr); if (!array) { // handle errors ... } free(ptr); free(array); C אין צורך לציין את גודל הטיפוס אותו מקצים. הגודל ידוע לפי שם הטיפוס שהוראת ה-new. הטיפוס המוחזר מ-new הוא של מצביע מתאים. לא מתבצעות המרות. malloc ו-free קיימות כמובן ב-C++ כדי לשמור על תאימות עם C. נא לא להשתמש בהן. הן אינן מתאימות לקוד C++. בפרט, בהמשך נראה ש-malloc ו-free לא דואגות לאתחול ושחרור עצמים כמו שדרוש ב-C++ בלבול בין delete ל-delete[] וערבוב new ו-delete עם malloc ו-free יוצר התנהגות לא מוגדרת, ולכן ייתכן שבחלק מהמקרים הוא יעבוד "בטעות". זו עדיין שגיאה. ניתן לגרום ל-new להחזיר NULL, ואכן לפעמים יש בכך צורך אך נושא זה מחוץ לחומר הקורס. אסור להתבלבל בין delete ו-delete[] אסור לערבב את new ו-delete עם malloc ו-free למשל להקצות עם new ולשחרר עם free אין צורך לבדוק את ערך המצביע המוחזר מ-new new אינה מחזירה NULL טיפול בשגיאות ב-++C מתבצע בעזרת מנגנון החריגות (תרגול 10) מבוא לתכנות מערכות

8 הקצאת זיכרון דינאמית - סיכום
ב-C++ משתמשים ב-new ו-new[size_t] כדי להקצות זיכרון חדש האופטור new מחזיר מצביע מהטיפוס המתאים אין צורך לבדוק את הצלחת ההקצאה כמו ב-C ב-C++ משתמשים ב-delete ו-delete[] כדי לשחרר זיכרון new משחררים עם delete ו-new[size_t] עם delete[] לא משתמשים ב-malloc ו-free (מלבד המקרה של חיבור קוד C ישן) מבוא לתכנות מערכות

9 Namespaces מבוא לתכנות מערכות

10 namespace ב-C++ ניתן לאגד מספר פונקציות וטיפוסים בצורה לוגית תחת namespace בדומה לארגון קבצים בתיקיות namespace math { double power(double base, double exp); double sqrt(double number); double log(double number); } לכל פונקציה (או טיפוס או משתנה) המוגדרת ב-namespace יש שם מלא מהצורה <namespace>::<item> אופרטור :: (קרוי scope) משמש להפרדה בין שמות namespace כאשר מציינים שמות מלאים כדי לגשת לפונקציה שאינה ב-namespace הנוכחי יש לרשום את שמה המלא, למשל: double root = math::sqrt(10.0); המילה scope (תחום) משמשת גם לתיאור החלק של הקוד בו משהו מוכר. כך למשל ה-scope של משתנה מקומי הוא הבלוק שהוא מוגדר בו. מבוא לתכנות מערכות

11 namespace ניתן להשתמש בהוראת using כדי לחסוך את כתיבת השם המלא בכל פעם
הוראת using "תעתיק" את הגדרת הפונקציה ל-namespace הנוכחי using math::power; ניתן להשתמש בהוראות using namespace <name> כדי לבצע using לכל תוכן ה-namespace בבת אחת using namespace math; ניתן לקנן namespace אחד בתוך השני: namespace mtm { namespace ex4 { int f(int n); } כל ההגדרות מהספריה הסטנדרטית מופיעות תחת ה-namespace הסטנדרטי הקרוי std מומלץ לא לבצע using namespace std אלא להשתמש רק בחלקים מהספריה הסטנדרטית הדרושים ה-namespace הראשי מצוין על ידי :: מה ההבדל בין f ל-::f? כדי לקרוא לפונקציות בתוך namespace מקונן פשוט רושמים את השם המלא בעזרת אופרטור :: למשל: mtm::ex4::f(5); אם הקוד שממנו מבצעים קריאה לפונקציה נמצא בתוך mtm ניתן לכתוב רק ex4::f. using של כל std יגרום לתפיסת שמות רבים וקצרים שיוכלים להתנגש עם קוד שלנו. ההבדל בין ::f ל-f הוא ש-f מתייחס ל-f שהוגדרה ב-namespace הנוכחי (או הובאה בעזרת using) ואילו ::f תמיד מתייחס ל-f שנמצאת ב-namespace הראשי. אם שני namespaces שונים מכילים את אותה חתימה של פונקציה, אי אפשר לעשות using namespace לשניהם באותו קטע קוד וגם להשתמש באותה הפונקציה, אחרת הקומפיילר יזהה שיש יותר מפונקציה אחת מתאימה (שגיאת קומפילציה) מבוא לתכנות מערכות

12 namespace C C++ יתרונות: ניתן לקבץ בצורה לוגית נוחה פונקציות דומות
ניתן להשתמש בשמות קצרים וברורים יותר ללא חשש להתנגשויות מאפשר החלפה, מימוש ומיזוג של מימושים שונים בקלות typedef enum { MTM_OUT_OF_MEMORY, /*...*/ } MtmErrorCode; void mtmPrintErrorMessage(MtmErrorCode error); דוגמה לשימוש בקוד ה-C++ (ללא using): mtm::printErrorMessage(mtm::OUT_OF_MEMORY); שימו לב שגם ערכי ה-enum מוגדרים תחת ה-namespace. ב-C++ אין צורך ב-typedef ל-enum או struct. הוא "מתבצע" אוטומטית כך שאין צורך לרשום enum type או struct type אח"כ בשימוש. בעזרת using ניתן להחליף מימושים בקלות, למשל גרסות שונות של פונקציות מתמטיות אם יש לנו שתי פונקציות ב-namespace שונים: ::n1::f ו-::n2::f ניתן לבצע using לזו שמשתמשים בה הרבה ולקרוא לשניה בשמה המלא. כך נקבל קוד קצר וקריא יותר ללא בעיית התנגשויות. C namespace mtm { enum ErrorCode { OUT_OF_MEMORY, /*...*/ }; void printErrorMessage(ErrorCode error); } C++ מבוא לתכנות מערכות

13 namespace - סיכום ניתן לקבץ קוד בצורה לוגית בעזרת חלוקתו ל-namespace שונים ניתן להשתמש ב-using כדי להימנע מכתיבת שמות מלאים בקוד בזכות namespace ניתן לשמור על שמות קצרים וברורים ללא צורך בתחיליות כמו ב-C הקוד מהספריה הסטנדרטית מופיע תחת ::std מבוא לתכנות מערכות

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

15 קבועים - const בהכרזה על משתנה ניתן להוסיף לשם הטיפוס את המילה השמורה const לא ניתן לשנות את ערכו של משתנה אשר מוגדר כ-const (קבוע) לאחר אתחולו לכן חובה לאתחל משתנים קבועים const int n = 5; n = 7; // error: assignment of read-only variable `n' const int m; // error: uninitialized const `m' ניתן להכריז על פרמטרים וערכי חזרה של פונקציה כ-const: char* strcpy(char* destination, const char* source); כותב הפונקציה מתחייב לא לשנות את ערך הארגומנט const char* studentGetName(Student s); ניתן להחזיר משתנה פרטי ללא חשש לגרימת נזק מצד המשתמש נכונות השימוש במשתנים קבועים נאכפת על ידי הקומפיילר const הוא קיצור של constant כמובן ערכים מטיפוס X יומרו אוטומטית לטיפוס const X כאשר צריך. נסיון להשתמש ב-const X עבור מקום בו דרוש הטיפוס X יגרום לשגיאת קומפילציה מבוא לתכנות מערכות

16 קבועים - const כאשר מוסיפים const להגדרת מצביע ייתכנו מספר אפשרויות:
הכתובת הנשמרת במצביע קבועה הערך הנשמר במשתנה המוצבע קבוע ניתן למקם את המילה const במיקום שונה בהגדרת הטיפוס כדי לקבל כל אחת מהתוצאות הרצויות אם ה-const מופיע ראשון הוא מתייחס לשם הטיפוס הבא אחריו const int* cptr; בכל שאר המקרים const מתייחס לשמאלו int const* cptr2; int* const ptr = NULL; // Must be initialized, why? ניתן לרשום יותר מ-const אחד const int* const ptr = NULL; int* const ptr מציין שהכתובת השמורה במצביע קבועה ולא יכולה להשתנות, לכן חובה לאתחל את המשתנה הזה בערך כמו למשתנים קבועים אחרים. עבור const int* ו-int const* המשמעות היא שהערך המוצבע הוא מטיפוס const int, ולכן ניתן לשנות את המצביע כל עוד הוא מקבל כתובת של משתנה קבוע. מבוא לתכנות מערכות

17 קבועים - דוגמאות אילו מהשורות הבאות יגרמו לשגיאת קומפילציה?
1) int i = 7; 2) const int ci = 17; 3) const int* pci = &ci; 4) *pci = 7; 5) pci = &i; 6) int* pi = &ci; 7) pci = pi; 8) pi = pci; 9) int* const cpi = &i; 10) *cpi = 17; 11) cpi = &i; 12) int* const cpi2 = &ci; 13) const int* const ccpi = &ci; 14) ccpi = &ci; שגיאות הקומפילציה: עבור *pci = 7; מתקבלת שגיאה כי *pci הוא const int ולא ניתן לכתוב לתוכו ערך חדש עבור int* pi = &ci; מתקבלת שגיאה כי &ci הוא מטיפוס const int*. אם היה ניתן לבצע השמה זאת היה ניתן להשתמש ב-pi כדי לשנות ערך של משתנה קבוע. עבור pi = pci; מתקבלת שגיאה מאותה סיבה. עבור cpi = &i; מתקבלת שגיאה כי ערך המצביע קבוע, לא ניתן לגרום לו להצביע לעצם אחר לאחר אתחולו. עבור int* const cpi2 = &ci; מתקבלת שגיאה כי אמנם ניתן לאתחל את cpi2 אך הוא אינו יכול להחזיק כתובת של איבר קבוע מאחר וניתן יהיה לשנות את ערך המשתנה דרך המצביע. עבור ccpi = &ci; מתקבלת שגיאה כי המצביע קבוע (לערך קבוע) ולא ניתן לשנותו לאחר האתחול. מבוא לתכנות מערכות

18 קבועים - הערות נוספות משתנה אשר קבוע בהקשר אחד אינו בהכרח קבוע:
ב-C++ מגדירים קבועים בעזרת משתנים קבועים (גלובליים אם צריך) במקום בעזרת #define מאפשר את בדיקת הטיפוס על יד הקומפיילר מאפשר הגדרת קבועים מטיפוסים מורכבים const int MAX_SIZE = 100; const Rational ZERO = rationalCreate(0,1); static const char* const MONTHS[] = {"JAN","FEB","MAR","APR","MAY","JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; חשוב להקפיד על שימוש נכון בקבועים (const correctness) כאשר ניתן ב-C++ הקפידו על const מההתחלה, הוספה מאוחרת של const יוצרת כדור שלג של שינויים void h() { X val; // val can be modified g(&val); } void g(const X* p) { // can't modify *p here } כמובן שקבועים עבור קובץ יחיד יש להגדיר כמשתנה גלובלי סטטי קבוע. נסיון להוסיף const באיחור בד"כ גורם לעוד כמה מקומות לדרוש הוספת const וכך חוזר חלילה על חלקים נרחבים מהקוד. לכן חשוב לנסות להקפיד על נכונות השימוש בקבועים מהתחלה. מבוא לתכנות מערכות

19 const - סיכום ניתן להגדיר משתנים ב-C++ כקבועים כך שלא ניתן לשנות את ערכם ניתן להגדיר פרמטרים וערכי חזרה של פונקציות כקבועים עבור מצביעים ניתן להגדיר את הערך המוצבע כקבוע ואת ערך המצביע כקבוע יש להשתמש ב-const כאשר ניתן כדי למנוע באגים הגדרת קבועים ב-C++ מתבצעת בעזרת משתנים קבועים הערות למעוניינים: מבוא לתכנות מערכות

20 משתנים מיוחסים משתנים מיוחסים קבועים
מבוא לתכנות מערכות

21 משתנים מיוחסים - Reference
עבור שם טיפוס X, X& הוא רפרנס ל-X int n = 1; int& r = n; // r and n now refer to the same int r = 2; // n = 2 העצם אליו מתייחס הרפרנס נקבע בהכרזה, לכן חובה לאתחל רפרנס int& r; // error: initializer missing אתחול הרפרנס שונה מהשמה אליו r = n; // a normal assignment between two ints לאחר אתחול הרפרנס לא ניתן להפעיל פעולות "על הרפרנס" אלא רק על העצם אליו הוא מתייחס int nn = 0; int& rr = nn; rr++; // nn is incremented to 1 לא ניתן לשנות את העצם אליו מתייחסים לאחר האתחול מבוא לתכנות מערכות

22 משתנים מיוחסים - Reference
אתחול של רפרנס יכול להתבצע רק על ידי lvalue int& x = 1; // error: invalid initialization from temporary value int& y = a+b; // same thing עבור טיפוס X ניתן להגדיר רפרנס לקבוע - const X& ניתן לאתחל רפרנס לקבוע גם על ידי ערכים זמניים const int& cx = 1; const int& cy = a+b; פרמטרים (וערכי חזרה) של פונקציה יכולים להיות רפרנס הרפרנס יאותחל כך שיתייחס לעצם המועבר לפונקציה (מוחזר מהפונקציה) void swap(int& a, int& b) { int temp = a; a = b; b = temp; } lvalue - תזכורת: ביטוי שיכול להופיע בצד השמאלי של השמה. לא ניתן לבצע אתחול לרפרנס מערך זמני מאחר וביטוי זה יהיה מקור לשגיאות. למשל כתיבה לתוך המשתנה הזמני בהמשך. נימוקים אלו זהים לנימוקים מדוע לא ניתן לקבל את הכתובת של ביטויים זמניים בעזרת אופרטור &. ניתן כמובן לאתחל const X& בעזרת X& אבל לא להיפך. רפרנסים מאפשרים תחביר הרבה יותר נוח לפונקציות כמו swap כדאי להימנע מהגדרת פרמטרים כרפרנסים (לא קבועים) אלא אם משתמע במפורש משם הפונקציה שהיא הולכת לשנות את ערכי הארגומנטים שלה. עדיף אם ניתן להשתמש בערכי חזרה או מצביעים שימוש חשוב לרפרנסים הוא בהעמסת אופרטורים (תרגול 9) שם המשתמש מצפה לשינוי הארגומנטים מבוא לתכנות מערכות

23 משתנים מיוחסים - Reference
הפונקציה הבאה מחזירה את האיבר המקסימלי במערך כרפרנס: int& getMax(int* array, int size) { int max = 0; for (int i = 0; i < size; ++i) { if (array[i] > array[max]) { max = i; } return array[max]; רפרנס הוא lvalue בעצמו, לכן ניתן למשל לרשום קוד מהצורה הבאה int array[5] = { 1, 2, 3, 4, 5 }; getMax(array,5) = 0 ; // array[4] = 0 אסור להחזיר רפרנס למשתנה מקומי מה יקרה אם נשנה את getMax כך שתחזיר את max? max הוא משתנה מקומי של getMax ולכן לאחר סיום ריצתה הוא משוחרר. החזרתו כרפרנס תגרום למשתמש לקרוא ערכי זבל ולכתוב לזיכרון שאינו מוקצה. קומפיילרים יכולים להתריע עבור מקרים פשוטים של החזרת רפרנס למצביע מקומי. אמנם הקוד הזה נראה מוזר, אך בהמשך נראה שימושים טבעיים עבור החזרת רפרנסים מבוא לתכנות מערכות

24 משתנים מיוחסים - Reference
קיים דמיון רב בין X& ל-X* const, עם זאת קיימים גם הבדלים חשובים: מצביע יכול להצביע ל-NULL בעוד משתנה מיוחס תמיד מתייחס לעצם חוקי עבור מצביעים יש צורך להשתמש ב-* ו-& כדי לבצע פעולות ניתן להשתמש בחשבון מצביעים כדי להסתכל על הזיכרון "ליד" המצביע מומלץ לשלוח פרמטרים כ-const X& כל עוד זה מתאים עבור משתנים פרימיטיביים (int, float וכו'...) אין הבדל ולכן אפשר להעביר by value (יותר קצר וקריא) מבוא לתכנות מערכות

25 משתנים מיוחסים - סיכום עבור טיפוס X ניתן הטיפוס X& מאפשר נתינת שם חדש לעצם לאחר האתחול של X& לא ניתן לשנות את העצם אליו הוא מתייחס const X& יכול להתייחס לערכים זמניים (X& לא יכול) ניתן להעביר פרמטרים ולהחזיר ערכים מפונקציות כרפרנסים אסור להחזיר רפרנס למשתנה מקומי מומלץ להעביר פרמטרים שאינם פרימיטיביים לפונקציות כ-const X& מבוא לתכנות מערכות

26 העמסת פונקציות בחירת פונקציה מתאימה ערכי ברירת מחדל
מבוא לתכנות מערכות

27 העמסת פונקציות - Function Overloading
מבחינת C++ הפונקציות void print(int) ו-void print(double) שונות ניתן ב-C++ לכתוב מספר פונקציות בעלות אותו שם ו"להעמיס" על אותו שם פונקציות שונות עבור טיפוסים שונים הקומפיילר יבחר את הפונקציה המתאימה לפי הארגומנטים בקריאה לה void print(int); void print(double); void print(const char*); // ... print(5); // print(int) called print(3.14); // print(double) called print("Hello"); // print(const char*) C++ void print_int(int); void print_double(double); void print_string(const char*); // ... print_int(5); print_double(3.14); print_string("Hello"); C מבוא לתכנות מערכות

28 העמסת פונקציות - בחירת הפונקציה
כדי לבחור את הפונקציה המתאימה הקומפיילר מחפש פונקציה בעלת שם מתאים המקבלת את מספר הפרמטרים המתאים מבין כל הפונקציות המתאימות הקומפיילר מחפש את הפונקציה שהפרמטרים שלה הם המתאימים ביותר לפי הדירוג הבא התאמה מדויקת או המרות טריוויאליות (T ⇐ const T, מערך למצביע) התאמה בעזרת קידום (bool ⇐ int, char ⇐ int, float ⇐ double וכו'...) התאמה בעזרת המרות מובנות (int ⇐ double, double ⇐ int, T* ⇐ void* התאמה בעזרת המרות שהוגדרו על ידי המשתמש (תרגול הבא) התאמה לפונקציה עם מספר ארגומנטים משתנה (ellipsis ...) (למשל printf) עבור יותר מפרמטר אחד הקומפיילר מחפש פונקציה שיש לה את ההתאמה הטובה ביותר עבור פרמטר אחד והתאמה שווה או טובה יותר לשאר הפרמטרים אם קיימות שתי התאמות ברמה הגבוהה ביותר בה נמצאו התאמות אז הקריאה מכילה דו-משמעות והיא לא תתקמפל בחירת הפונקציה אינה תלויה בערך ההחזרה, לא ניתן לכתוב שתי פונקציות השונות רק בערך ההחזרה שלהן כתיבת פונקציות עם מספר ארגומנטים משתנה בעזרת ... מחוץ לחומר הקורס. שימוש בכאלו פונקציות לא מומלץ מאחר וקל לגרום בעזרתן לשגיאות זמן ריצה בגלל קריאה לא נכונה לפונקציה. הקומפיילר לא יכול לוודא את נכונות הקריאה לפונקציות כאלו. (למעשה, לכל הקומפיילרים יש מנגנונים מיוחדים כדי להתמודד עם printf ומוציאים אזהרות מיוחדות רק עבור הבעייתיות שלה) בחירת הפונקציה אינה תלויה בערך ההחזרה כדי לאפשר קריאות של הקוד, אחרת לא היה ניתן לגלות איזו פונקציה רצה בלי לדעת את ההקשר בו היא נקראת. הערה למתקדמים: פונקציות אשר נמצאות ב-namespace שונים אינן מועמסות אחת על השניה. למשל במקרה של הגדרת שתי פונקציות עם אותו שם, אחת ב-namespace הנוכחי ואחת בראשי. תיבחר זאת שב-namespace הנוכחי ללא קשר להעמסה (וייתכנו שגיאות קומפילציה אם היא אינה מתאימה). כדי לפתור בעיות כתוצאה מכך ניתן לקרוא לפונקציה הדרושה בשמה המלא כאשר צריך. אל תעמיסו מבוא לתכנות מערכות

29 העמסת פונקציות - דוגמאות
אילו מהפונקציות הבאות תיקרא בכל אחת מהקריאות? void print(int); void print(double); void print(const char*); void print(char); void h(char c, int i, float f, double d, long l) { print(c); print(f); print(d); print(i); print(l); print('a'); print(49); print(0); print("Hello"); } print(c); // print(char) by exact match [1] print(f); // print(double) by promotion [2] print(d); // print(double) by exact match [1] print(i); // print(int) by exact match [1] print(l); // Ambiguous both print(long) and print(double) can be called by using built-in conversions [3] print('a'); // 'a' is of type char so print(char) is called by exact match [1] print(49); // 49 is of type int so print(int) is called by exact match [1] print(0); // 0 is considered an int (And not some kind of a pointer) so print(int) is called by exact match [1] print("Hello"); // "Hello" is of type const char* so print(const char*) is called by exact match [1] במקרה של דו-משמעות יש להוסיף המרות מפורשות כדי להבהיר לקומפיילר באיזו פונקציה לבחור, למשל: print(int(l)); print(double(l)); העמסות יכולות לסבך את הקוד, השתמשו בהן רק אם קורא הקוד יוכל להבין בקלות מה קורה מהסתכלות על הקריאה לפונקציה מבוא לתכנות מערכות

30 ערכי ברירת מחדל קיימות פונקציות אשר אחד הארגומנטים שלהן הוא ברוב המקרים אותו ערך void print(int n, int base); print(6, 10); print(5, 10); print(5, 8); // Print in octal print(17,10); print(14,2); // binary נוכל לנצל את מנגנון העמסת הפונקציות כדי לפתור בעיה זו: void print(int n) { print(n, 10); } לשם הקלה על מטלה זו ניתן להגדיר ערך ברירת מחדל לפרמטרים עבור פונקציה, ולכן נוכל להגדיר רק פונקציה אחת: void print(int n, int base = 10); מבוא לתכנות מערכות

31 ערכי ברירת מחדל ניתן לתת ערך ברירת מחדל רק לפרמטרים האחרונים של הפונקציה: int f(int n, int m = 0, char* str = 0); // ok int g(int n = 0, int m = 0, char* str); // error int h(int n = 0, int m , char* str = NULL); // error את ערך ברירת המחדל יש לכתוב פעם אחת בלבד בהכרזת הפונקציה int f(int n = 8); //... int f(int n) { // writing n = 8 again (or any other value) is an error return n; } ערכי ברירת מחדל פרמטר שאינו אחרון יכולים ליצור דו משמעות בקריאה, למשל int f(int = 0, int, int = 0) - עבור קריאה ל-f(1,1) לאילו פרמטרים נציב ערכים? מבוא לתכנות מערכות

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

33 האופרטורים >> ו-<< הערוצים הסטנדרטיים ב-C++
מבוא לתכנות מערכות

34 קלט/פלט ב-C++ ערוצי הקלט ופלט הסטנדרטיים מיוצגים ב-C++ על ידי המשתנים הגלובליים הבאים (מוגדרים בקובץ iostream): cout: ערוץ הפלט הסטנדרטי :cin ערוץ הקלט הסטנדרטי cerr: ערוץ השגיאות הסטנדרטי כדי להדפיס משתנה כלשהו לערוץ פלט משתמשים באופרטור >> ניתן להדפיס ירידת שורה ולבצע flush לחוצץ על ידי הדפסת endl כדי לקלוט ערך למשתנה מערוץ קלט משתמשים באופרטור << ניתן לשרשר מספר הדפסות/קריאות בבת אחת int main() { int n; std::cin >> n; // Reads an int from user std::cout << "You entered " << n << std::endl; return 0; } כדי להשתמש בערוצים וב-endl (ללא std:: לפני) יש לבצע using לכולם: using std::cout; using std::cin; using std::cerr; using std::endl; האופרטורים >> ו- << משמשים בלביצוע bit shift (לכיוונים שונים) עבור int (גם ב-C) למי שתוהה: endl הוא שם של פונקציה (מחוץ לחומר הקורס) מבוא לתכנות מערכות

35 קלט/פלט ב-C++ C++ C יתרונות: לא צריך לזכור קודים מוזרים כדי להדפיס
לא ניתן להתבלבל בסוג הטיפוס המודפס ניתן להרחיב את השימוש בקלט/פלט עבור טיפוסים שיצרנו (בתרגול הבא) #include <iostream> using std::cout; using std::cerr; using std::cin; using std::endl; int main() { int n; const char* str = "Hello"; int* ptr = &n; cin >> n; cout << str << endl; cerr << n << " at "<< ptr << endl; return 0;   } C++ #include <stdio.h> int main() { int n; const char* str = "Hello"; int* ptr = &n; fscanf(stdin, "%d", &n); fprintf(stdout, "%s\n", str); fprintf(stderr, "%d at %x\n", n, ptr); return 0;   } C מבוא לתכנות מערכות

36 קלט/פלט ב-C++ - סיכום הערוצים הסטנדרטיים מיוצגים ב-C++ על ידי המשתנים cout, cin ו-cerr מדפיסים לערוצי פלט בעזרת אופרטור >> קולטים ערכים מערוצי קלט בעזרת << ההדפסה או הקריאה נקבעים לפי סוג הטיפוס ניתן להרחיב את שיטה זו עבור טיפוסים שיצרנו בעצמנו (תרגול הבא) מבוא לתכנות מערכות

37 מחלקות - Classes משתנים ופונקציות סטטיות מתודות מחלקות this
מחלקת מחסנית public ו-private בנאים והורסים מבוא לתכנות מערכות

38 טיפוסי נתונים ב-C++ יצירת טיפוסי נתונים הוטמעה בשפת C++
struct Point { int x, y; double distance(const Point& p) { int dx = this->x - p.x; int dy = this->y - p.y; return sqrt(dx*dx+dy*dy); } }; int main() { Point p = { 3, 4 }; Point p2 = { 2, 8 }; double d = p.distance(p2); return 0; יצירת טיפוסי נתונים הוטמעה בשפת C++ ב-C++ ניתן להגדיר את הפונקציות עבור טיפוס הנתונים ישירות בתוך המבנה פונקציות המוגדרות כחלק מהמבנה נקראות מתודות (methods) או פונקציות חברות (member functions) כדי להפעיל מתודה יש צורך בעצם מהטיפוס המתאים להפעיל אותה עליו לכל מתודה יש פרמטר נסתר בשם this והוא מצביע לעצם עליו היא הופעלה ב-C++ נהוג לקרוא לפונקציות המוגדרות בתוך המבנה member functions, אך הגלל תרגום צולע יחסית לעברית רשום בשקפים השימוש במונח מתודה (ניתן לתרגם לעברית צחה יותר כ"שיטה"). המונח מתודה נהוג יותר בשפות אחרות, כדוגמת Java מבוא לתכנות מערכות

39 מתודות - Methods struct Point { int x, y; double distance(const Point& p); }; double Point::distance(const Point& p) { int dx = this->x - p.x; int dy = this->y - p.y; return sqrt(dx*dx+dy*dy); } מתודות ניתן לממש ישירות בתוך המבנה (כמו בשקף הקודם) או להכריז עליהן במבנה ולממשן מחוץ לו: הגדרות טיפוסים ב-C++ יופיעו בדרך כלל בקבצי h אם מימוש הפונקציה נעשה בתוך המבנה אז הוא יופיע בקובץ ה-h אם מימוש הפונקציה חיצוני נשים אותו בקובץ ה-cpp/cc/C המתאים מתודות הממומשות ישירות בתוך המבנה מוגדרות כ-inline (כלומר, הקומפיילר רשאי לא להגדרין כשגרה נפרדת בקוד המכונה אלא להדביק את הקוד בתוך הפונקציה הקוראת. הקומפיילר רשאי להחליט בצורה שונה עבור קריאות שונות לפונקציה) מתודות הממומשות מחוץ למחלקה אינן inline. שימו לב שפונקציות שהינן inline צריכות להופיע בקובץ ה-h (כדי להדביק את קוד הפונקציות בתוך הפונקציה הקוראת הקומפיילר צריך גישה למימוש הפונקציה מבוא לתכנות מערכות

40 מדוע לא ניתן להגדיר את set כ-const?
המצביע this struct Point { int x, y; double distance(const Point& p) const; void set(int x, int y); };   void Point::set(int x, int y) { this->x = x; this->y = y; } double Point::distance(const Point& p) const { int dx = x - p.x; int dy = y - p.y; return sqrt(dx*dx+dy*dy); לכל מתודה של עצם מטיפוס X נשלח מצביע מטיפוס X* const ששמו this ניתן להשמיט את ה-this כל עוד אין דו-משמעות ניתן להגדיר מתודה כך שתפעל על עצמים שהינם const על ידי הוספת const בסוף חתימת הפונקציה עבור מתודה לעצם קבוע this יהיה מטיפוס const X* const אם this קבוע עבור מתודה מסוימת גם כל השדות שלו קבועים מדוע לא ניתן להגדיר את set כ-const? מבוא לתכנות מערכות

41 בקרת גישה - Access Control
struct Point { private: int x, y; public: double distance(const Point& p); void set(int x, int y); }; // ... int main() { Point p; p.x = 5; // error: 'int Point::x' // is private p.set(3, 4); double d = p.distance(p); return 0; } כדי לשמור על הסתרת המימוש מהמשתמש ניתן להגדיר חלקים פרטיים וחלקים פומביים קוד אשר כתוב בתוך ה-namespace של הטיפוס רשאי לגשת לחלקים פרטיים קוד אשר כתוב מחוץ למבנה אינו יכול לגשת לחלקים אלו ניתן להגדיר פונקציות, שדות וטיפוסים כפרטיים או פומביים למשל, פונקציות עזר של הטיפוס יוגדרו כ-private כל טיפוס מגדיר namespace בשמו, כך למשל הפונקציה distance המופיעה בשקף היא למעשה Point::distance. מבוא לתכנות מערכות

42 מחלקות - Classes בדרך כלל מגדירים טיפוסים ב-C++ עם המילה השמורה class
ההבדל בין class ל-struct הוא בברירת המחדל עבור בקרת הגישה - public עבור struct ו-private עבור class נהוג להשתמש ב-struct עבור טיפוסים פשוטים שכל שדותיהם הינם public למרות שההבדל בין class ל-struct הוא רק בברירת המחדל לרמת הגישה נהוג להשתמש ב-struct עבור מבנים פשוטים אשר השדות שלהם הם public. class Point { int x, y; public: double distance(const Point& p) const; void set(int x, int y); }; struct Point { private: int x, y; public: double distance(const Point& p) const; void set(int x, int y); }; מבוא לתכנות מערכות

43 בנאים - Constructors class Point { int x, y; public: Point(int x, int y); double distance(const Point& p) const; }; Point::Point(int x, int y) { this->x = x; this->y = y; } int main() { Point p1(3, 4); const Point p2(2, 8); cout << p1.distance(p2) << endl; return 0; לכל מחלקה ניתן להגדיר סוג מיוחד של מתודות הנקראות בנאים (Constructors או C’tor בקיצור) ששמן כשם המחלקה בנאים משמשים לאתחול של עצם חדש מהמחלקה מבוא לתכנות מערכות

44 הורסים - Destructors class Array { int* data; int size; public: Array(int size); ~Array(); // More methods ... }; Array::Array(int size) { data = new int[size]; this->size = size; }   Array::~Array() { delete[] data; } int main() { Array array(50); // code ... return 0; } // d'tor called לכל מחלקה ניתן להגדיר סוג נוסף של מתודה הקרויה הורס (Destructor או D’tor) ושמה כשם המחלקה ותחילית ~ ההורס של המחלקה נקרא אוטומטית בזמן שחרור עצם של המחלקה עוד על בנאים והורסים בתרגולים הבאים התו ~ נבחר בגלל שהוא מסמן bitwise not. מבוא לתכנות מערכות

45 פונקציות ושדות סטטיים class Point { int x, y; static Point origin; public: Point(int x, int y); double distanceFromOrigin() const; static void setOrigin(int x, int y); }; Point Point::origin(0,0); double Point::distanceFromOrigin() const { int dx = x - origin.x; int dy = y - origin.y; return sqrt(dx*dx + dy*dy); } void Point::setOrigin(int x, int y) { origin.x = x; origin.y = y; } ניתן להגדיר משתנים סטטיים במחלקה, משתנים אלו אינם שייכים לעצם ספציפי ניתן להגדיר מתודות סטטיות, מתודה סטטית אינה מקבלת this ולא דרוש עצם כדי להפעילה מתודה סטטית רשאית לגשת לחלקים פרטיים של המחלקה משתנים ומתודות סטטיים מצייתים לחוקי בקרת הגישה אם למשל נגדיר משתנה סטטי כפרטי הוא יהיה נגיש רק מתוך המחלקה קריאה למשתנים ומתודות סטטיות יכולה להתבצע בעזרת אופרטור ה-. על עצם ספציפי או בעזרת אופרטור :: על שם הטיפוס (שם ה-namespace המתאים( את מימוש המשתנים הסטטיים יש לשים בקובץ ה-cpp. משתנים סטטיים משמשים לקביעת קבועים למחלקה או כמשתנים גלובליים של המחלקה כאשר יש בהם צורך. מומלץ להימנע ממשתנים סטטיים לא קבועים מאחר והם למעשה משתנים גלובליים. מתודות סטטיות הן בדרך כלל מתודות עזר עבור המחלקה שאינן צריכות עצם ספציפי לפעולתן. מבוא לתכנות מערכות

46 מחלקת מחסנית נמיר כעת את המחסנית שלנו מתרגול 5 למחלקה ב-C++
class Stack { int* data; int size; int nextIndex; public: Stack(int size = 100); ~Stack(); int getSize() const; void push(int n); void pop(); int& top(); const int& top() const; }; 2 17 3 nextIndex 5 מבוא לתכנות מערכות

47 מימוש המחסנית מה ההבדל? Stack::Stack(int size) { data = new int[size];
this->size = size; nextIndex = 0; } Stack::~Stack() { delete[] data; void Stack::push(int n) { if (nextIndex >= size) { error("Stack full"); } data[nextIndex++] = n; } int Stack::getSize() const { return nextIndex; } void Stack::pop() { if (nextIndex <= 0) { error("Stack empty"); } nextIndex--; } int& Stack::top() { error("Stack empty"); } return data[nextIndex - 1]; } const int& Stack::top() const { return data[nextIndex - 1]; } ההבדל בין שתי מתודות ה-top הוא בטיפוס של ה-this שלהן: א. המתודה הראשונה נועדה לגישה למחסנית שאינה מוגדרת כקבועה ולכן מחסנית זו מחזירה רפרנס לאיבר הראש - שימו לב שניתן להשתמש במתודה הזו כך למשל: myStack.top() = 5; ב. המתודה השניה נועדה לגישה עבור מחסנית שמוגדרת כקבועה - לכן היא גם חייבת להחזיר רפרנס למשתנה קבוע. הקומפיילר לא יאפשר לקמפל קוד אשר מנסה להחזיר רפרנס שאינו קבוע בגלל שלא ניתן להמיר משתנה קבוע ללא קבוע (לפחות לא בצורה לא מפורשת) ותתקבל שגיאת קומפילציה. הקומפיילר כמובן צודק במקרה זה, אם היה ניתן להחזיר רפרנס שאינו קבוע היינו פותחים חור לשינוי ערכים במחסנית שמוגדרת כקבועה. מה ההבדל? מבוא לתכנות מערכות

48 שימוש במחסנית C++ C #include "stack.h" #include <iostream>
using std::cout; using std::endl; int main() { Stack stack; Stack stack2(50); stack.push(1); stack.push(213); stack.pop(); cout << stack.top() << endl; return 0; } C++ #include "stack.h" #include <stdio.h> int main() { Stack stack = stackCreate(100); Stack stack2 = stackCreate(50); stackPush(stack, 1); stackPush(stack, 213); stackPop(stack); printf("%d\n",stackTop(stack)); stackDestroy(stack); stackDestroy(stack2); return 0; } C שימו לב שבהפעלה של מתודה חסרת פמטרים כמו pop צריך לזכור לשים את אופרטור ההפעלה () אחרת הביטוי יפורש על ידי הקומפיילר כמצביע למתודה (מצביעים למתודות מחוץ לחומר הקורס) מבוא לתכנות מערכות

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


הורד את "ppt "תרגול מס' 8 משתנים מיוחסים מבוא ל-C++ קלט/פלט ב-C++

מצגות קשורות


מודעות Google