490 likes | 795 Views
תרגול מס' 8. מבוא ל- C++ הקצאת זיכרון דינאמית namespaces const משתנים מיוחסים קלט/פלט ב- C++ מחלקות. מבוא ל- C++. C++. C++ היא שפת תכנות שנבנתה מעל C C++ מוסיפה מגוון רב של תכונות מתקדמות בעיקר עבור תכנות מונחה עצמים השלד הבסיסי של הקוד בשפה זהה ל- C
E N D
תרגול מס' 8 מבוא ל-C++ הקצאת זיכרון דינאמית namespaces const משתנים מיוחסים קלט/פלט ב-C++ מחלקות
מבוא ל-C++ מבוא לתכנות מערכות - 234122
C++ • C++היא שפת תכנות שנבנתה מעל C • C++ מוסיפה מגוון רב של תכונות מתקדמות • בעיקר עבור תכנות מונחה עצמים • השלד הבסיסי של הקוד בשפה זהה ל-C • רוב הקוד של C ניתן לקמפול ישירות כ-C++ • נהוג ב-C++ לבצע דברים אחרת תוך שימוש ביתרונות השפה • לא כותבים ב-C++קוד בסגנון C! • לא נהוג להשתמש ב-printf, FILE*, void*ועוד ב-C++ - יש תחליפים עדיפים #include<iostream> usingnamespacestd; intmain(intargc, char** argv) { cout << "Hello world" << endl; return 0; } מבוא לתכנות מערכות - 234122
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++ ובדרך כלל חשיבותן גבוהה לסגנון השפה מבוא לתכנות מערכות - 234122
הקצאת זיכרון דינאמית new delete מבוא לתכנות מערכות - 234122
הקצאת זיכרון דינאמית • הקצאות זיכרון דינאמיות ב-++C מתבצעות בעזרת האופרטורים newו-delete • ניתן להקצות משתנה חדש (ולאתחלו) על ידי new: int* ptr = newint; // No explicit initialization int* ptr = newint(7); // The new integer is initialized to 7 • ניתן להקצות מערך של משתנים על ידי new[size_t]: int* array = newint[n]; • שחרור של עצם שהוקצה על ידי new מתבצע בעזרת delete: deleteptr; • שחרור של עצם שהוקצה על ידי new[size_t] מתבצע בעזרת delete[]: delete[] array; מבוא לתכנות מערכות - 234122
new ו-delete - דוגמאות int* ptr = newint(5); int* array = newint[*ptr]; // No need to check for NULLs deleteptr; 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 • אסורלהתבלבלבין delete ו-delete[] • אסור לערבב את new ו-delete עם malloc ו-free • למשל להקצות עם new ולשחרר עם free • אין צורך לבדוק את ערך המצביע המוחזר מ-new • new אינה מחזירה NULL • טיפול בשגיאות ב-++C מתבצע בעזרת מנגנון החריגות (תרגול 10) מבוא לתכנות מערכות - 234122
הקצאת זיכרון דינאמית - סיכום • ב-C++ משתמשים ב-new ו-new[size_t] כדי להקצות זיכרון חדש • האופטור new מחזיר מצביע מהטיפוס המתאים • אין צורך לבדוק את הצלחת ההקצאה כמו ב-C • ב-C++ משתמשים ב-delete ו-delete[] כדי לשחרר זיכרון • new משחררים עם delete ו-new[size_t] עם delete[] • לא משתמשים ב-malloc ו-free (מלבד המקרה של חיבור קוד C ישן) מבוא לתכנות מערכות - 234122
Namespaces מבוא לתכנות מערכות - 234122
namespace • ב-C++ניתן לאגד מספר פונקציות וטיפוסים בצורה לוגית תחת namespace • בדומה לארגון קבצים בתיקיות namespace math { doublepower(double base, doubleexp); doublesqrt(double number); doublelog(double number); } • לכל פונקציה (או טיפוס או משתנה) המוגדרת ב-namespace יש שם מלא מהצורה <namespace>::<item> • אופרטור :: (קרוי scope) משמש להפרדה בין שמות namespace כאשר מציינים שמות מלאים • כדי לגשת לפונקציה שאינה ב-namespace הנוכחי יש לרשום את שמה המלא, למשל: doubleroot = math::sqrt(10.0); מבוא לתכנות מערכות - 234122
namespace • ניתן להשתמש בהוראת usingכדי לחסוך את כתיבת השם המלא בכל פעם • הוראת using "תעתיק" את הגדרת הפונקציה ל-namespace הנוכחי using math::power; • ניתן להשתמש בהוראות using namespace <name>כדי לבצע using לכל תוכןה-namespace בבת אחת usingnamespace math; • ניתן לקנן namespace אחד בתוך השני: namespacemtm { namespace ex4 { intf(int n); } } • כל ההגדרות מהספריה הסטנדרטית מופיעות תחת ה-namespace הסטנדרטי הקרוי std • מומלץ לא לבצע using namespace stdאלא להשתמש רק בחלקים מהספריה הסטנדרטית הדרושים • ה-namespace הראשי מצוין על ידי :: • מה ההבדל בין f ל-::f? מבוא לתכנות מערכות - 234122
namespace • יתרונות: • ניתן לקבץ בצורה לוגית נוחה פונקציות דומות • ניתן להשתמש בשמות קצרים וברורים יותר ללא חשש להתנגשויות • מאפשר החלפה, מימוש ומיזוג של מימושים שונים בקלות typedefenum { MTM_OUT_OF_MEMORY, /*...*/ } MtmErrorCode; voidmtmPrintErrorMessage(MtmErrorCode error); C C++ namespacemtm { enumErrorCode{ OUT_OF_MEMORY, /*...*/ }; voidprintErrorMessage(ErrorCode error); } מבוא לתכנות מערכות - 234122
namespace - סיכום • ניתן לקבץ קוד בצורה לוגית בעזרת חלוקתו ל-namespace שונים • ניתן להשתמש ב-using כדי להימנע מכתיבת שמות מלאים בקוד • בזכות namespace ניתן לשמור על שמות קצרים וברורים ללא צורך בתחיליות כמו ב-C • הקוד מהספריה הסטנדרטית מופיע תחת ::std מבוא לתכנות מערכות - 234122
קבועים הגדרת משתנים קבועים מצביעים קבועים מבוא לתכנות מערכות - 234122
קבועים - const • בהכרזה על משתנה ניתן להוסיף לשם הטיפוס את המילה השמורה const • לא ניתן לשנות את ערכו של משתנה אשר מוגדר כ-const(קבוע)לאחר אתחולו • לכן חובה לאתחל משתנים קבועים constint n = 5; n = 7; // error: assignment of read-only variable `n' constint m; // error: uninitialized const `m' • ניתן להכריז על פרמטרים וערכי חזרה של פונקציה כ-const: char* strcpy(char* destination, constchar* source); • כותב הפונקציה מתחייב לא לשנות את ערך הארגומנט constchar* studentGetName(Student s); • ניתן להחזיר משתנה פרטי ללא חשש לגרימת נזק מצד המשתמש • נכונות השימוש במשתנים קבועים נאכפת על ידי הקומפיילר מבוא לתכנות מערכות - 234122
קבועים - const • כאשר מוסיפים const להגדרת מצביע ייתכנו מספר אפשרויות: • הכתובתהנשמרת במצביע קבועה • הערךהנשמר במשתנה המוצבע קבוע • ניתן למקם את המילה const במיקום שונה בהגדרת הטיפוס כדי לקבל כל אחת מהתוצאות הרצויות • אם ה-const מופיע ראשוןהוא מתייחס לשם הטיפוס הבא אחריו constint* cptr; • בכל שאר המקרים constמתייחס לשמאלו intconst* cptr2; int* constptr = NULL; // Must be initialized, why? • ניתן לרשום יותר מ-const אחד constint* constptr = NULL; מבוא לתכנות מערכות - 234122
קבועים - דוגמאות • אילו מהשורות הבאות יגרמו לשגיאת קומפילציה? 1) inti = 7; 2) constint ci = 17; 3) constint* pci = &ci; 4) *pci = 7; 5) pci= &i; 6) int* pi = &ci; 7) pci= pi; 8) pi = pci; 9) int* constcpi = &i; 10) *cpi = 17; 11) cpi= &i; 12) int* const cpi2 = &ci; 13) constint* constccpi = &ci; 14) ccpi= &ci; מבוא לתכנות מערכות - 234122
קבועים - הערות נוספות • משתנה אשר קבוע בהקשר אחד אינו בהכרח קבוע: • ב-C++ מגדירים קבועים בעזרת משתנים קבועים (גלובליים אם צריך) במקום בעזרת #define • מאפשר את בדיקת הטיפוס על יד הקומפיילר • מאפשר הגדרת קבועים מטיפוסים מורכבים constint MAX_SIZE = 100; constRational ZERO = rationalCreate(0,1); static constchar* const MONTHS[] = {"JAN","FEB","MAR","APR","MAY","JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; • חשוב להקפיד על שימוש נכון בקבועים (const correctness) כאשר ניתן ב-C++ • הקפידו על const מההתחלה, הוספה מאוחרת של const יוצרת כדור שלג של שינויים voidh() { Xval; // val can be modified g(&val); } voidg(constX* p) { // can't modify *p here } מבוא לתכנות מערכות - 234122
const - סיכום • ניתן להגדיר משתנים ב-C++ כקבועים כך שלא ניתן לשנות את ערכם • ניתן להגדיר פרמטרים וערכי חזרה של פונקציות כקבועים • עבור מצביעים ניתן להגדיר את הערך המוצבע כקבוע ואת ערך המצביע כקבוע • יש להשתמש ב-const כאשר ניתן כדי למנוע באגים • הגדרת קבועים ב-C++ מתבצעת בעזרת משתנים קבועים מבוא לתכנות מערכות - 234122
משתנים מיוחסים משתנים מיוחסים משתנים מיוחסים קבועים מבוא לתכנות מערכות - 234122
משתנים מיוחסים - Reference • משתנה מיוחס (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 • לאחר אתחול הרפרנס לא ניתן להפעיל פעולות "על הרפרנס" אלא רק על העצם אליו הוא מתייחס intnn = 0; int& rr = nn; rr++; // nnis incremented to 1 • לא ניתן לשנות את העצם אליו מתייחסים לאחר האתחול מבוא לתכנות מערכות - 234122
משתנים מיוחסים - Reference • אתחול של רפרנס יכול להתבצע רק על ידי lvalue int& x = 1; // error: invalid initialization from temporary value int& y = a+b; // same thing • עבור טיפוס X ניתן להגדיר רפרנס לקבוע - const X& • ניתן לאתחל רפרנס לקבוע גם על ידי ערכים זמניים constint& cx = 1; constint& cy = a+b; • פרמטרים(וערכי חזרה) של פונקציה יכולים להיות רפרנס • הרפרנס יאותחל כך שיתייחס לעצם המועבר לפונקציה (מוחזר מהפונקציה) voidswap(int& a, int& b) { int temp = a; a = b; b = temp; } מבוא לתכנות מערכות - 234122
משתנים מיוחסים - 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? אמנם הקוד הזה נראה מוזר, אך בהמשך נראה שימושים טבעיים עבור החזרת רפרנסים מבוא לתכנות מערכות - 234122
משתנים מיוחסים - Reference • קיים דמיון רב בין X&ל-X* const, עם זאת קיימים גם הבדלים חשובים: • מצביע יכול להצביע ל-NULLבעוד משתנה מיוחס תמיד מתייחס לעצם חוקי • עבור מצביעים יש צורך להשתמש ב-* ו-& כדי לבצע פעולות • ניתן להשתמש בחשבון מצביעים כדי להסתכל על הזיכרון "ליד" המצביע • מומלץ לשלוח פרמטרים כ-const X& כל עוד זה מתאים • עבור משתנים פרימיטיביים (int, floatוכו'...) אין הבדל ולכן אפשר להעביר by value (יותר קצר וקריא) מבוא לתכנות מערכות - 234122
משתנים מיוחסים - סיכום • עבור טיפוס X ניתן הטיפוס X& מאפשר נתינת שם חדש לעצם • לאחר האתחול של X& לא ניתן לשנות את העצם אליו הוא מתייחס • const X& יכול להתייחס לערכים זמניים (X& לא יכול) • ניתן להעביר פרמטרים ולהחזיר ערכים מפונקציות כרפרנסים • אסור להחזיר רפרנס למשתנה מקומי • מומלץ להעביר פרמטרים שאינם פרימיטיביים לפונקציות כ-const X& מבוא לתכנות מערכות - 234122
העמסת פונקציות העמסת פונקציות בחירת פונקציה מתאימה ערכי ברירת מחדל מבוא לתכנות מערכות - 234122
העמסת פונקציות - Function Overloading • ב-C++ פונקציה מזוהה על ידי שמהוגם על ידי מספר וסוג הפרמטרים שלה • בניגוד ל-C בה פונקציה מזוהה על ידי שמה בלבד • מבחינת C++ הפונקציות voidprint(int)ו-voidprint(double)שונות • ניתן ב-C++ לכתוב מספר פונקציות בעלות אותו שם ו"להעמיס" על אותו שם פונקציות שונות עבור טיפוסים שונים • הקומפיילריבחר את הפונקציה המתאימה לפי הארגומנטים בקריאה לה • voidprint(int); • voidprint(double); • voidprint(constchar*); • // ... • print(5); // print(int) called • print(3.14); // print(double) called • print("Hello"); // print(const char*) C++ voidprint_int(int); voidprint_double(double); voidprint_string(constchar*); // ... print_int(5); print_double(3.14); print_string("Hello"); C מבוא לתכנות מערכות - 234122
העמסת פונקציות - בחירת הפונקציה • כדי לבחור את הפונקציה המתאימה הקומפיילר מחפש פונקציה בעלת שם מתאים המקבלת את מספר הפרמטרים המתאים • מבין כל הפונקציות המתאימות הקומפיילר מחפש את הפונקציה שהפרמטרים שלה הם המתאימים ביותר לפי הדירוג הבא • התאמה מדויקת או המרות טריוויאליות (T⇐const T, מערך למצביע) • התאמה בעזרת קידום (bool⇐int, char⇐int, float⇐double וכו'...) • התאמה בעזרת המרות מובנות (int⇐double, double⇐int, T*⇐void* • התאמה בעזרת המרות שהוגדרו על ידי המשתמש (תרגול הבא) • התאמה לפונקציה עם מספר ארגומנטים משתנה (ellipsis ...) (למשל printf) • עבור יותר מפרמטר אחד הקומפיילר מחפש פונקציה שיש לה את ההתאמה הטובה ביותר עבור פרמטר אחד והתאמה שווה או טובה יותר לשאר הפרמטרים • אם קיימות שתי התאמות ברמה הגבוהה ביותר בה נמצאו התאמות אז הקריאה מכילה דו-משמעות והיא לא תתקמפל • בחירת הפונקציה אינה תלויה בערך ההחזרה, לא ניתן לכתוב שתי פונקציות השונות רק בערך ההחזרה שלהן מבוא לתכנות מערכות - 234122
העמסת פונקציות - דוגמאות • אילו מהפונקציות הבאות תיקרא בכל אחת מהקריאות? voidprint(int); voidprint(double); voidprint(constchar*); voidprint(char); voidh(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(int(l)); print(double(l)); העמסות יכולות לסבך את הקוד, השתמשו בהן רק אם קורא הקוד יוכל להבין בקלות מה קורה מהסתכלות על הקריאה לפונקציה מבוא לתכנות מערכות - 234122
ערכי ברירת מחדל • קיימות פונקציות אשר אחד הארגומנטים שלהן הוא ברוב המקרים אותו ערך voidprint(int n, int base); print(6, 10); print(5, 10); print(5, 8); // Print in octal print(17,10); print(14,2); // binary • נוכל לנצל את מנגנון העמסת הפונקציות כדי לפתור בעיה זו: voidprint(int n) { print(n, 10); } • לשם הקלה על מטלה זו ניתן להגדיר ערך ברירת מחדל לפרמטרים עבור פונקציה, ולכן נוכל להגדיר רק פונקציה אחת: voidprint(int n, intbase = 10); מבוא לתכנות מערכות - 234122
ערכי ברירת מחדל • ניתן לתת ערך ברירת מחדל רק לפרמטרים האחרונים של הפונקציה: intf(int n, int m = 0, char* str = 0); // ok intg(int n = 0, int m = 0, char* str); // error inth(int n = 0, int m , char* str = NULL); // error • את ערך ברירת המחדל יש לכתוב פעם אחת בלבד בהכרזת הפונקציה intf(int n = 8); //... intf(int n) { // writing n = 8 again (or any other value) is an error return n; } מבוא לתכנות מערכות - 234122
העמסת פונקציות - סיכום • ב-C++ פונקציה מזוהה לפי שמה והפרמטרים אותם היא מקבלת • הקומפיילר אחראי לבחירת הפונקציה המתאימה מבין כל הפונקצוית המועמסות על אותו שם • אם אין פונקציה "מנצחת" מתאימה צריך לפתור את דו-המשמעות ידנית • ניתן להגדיר ערך ברירת מחדל פרמטרים האחרונים של פונקציה • הקפידו להשתמש בהעמסת פונקציות רק כאשר היא קלה להבנה על ידי קורא הקוד מבוא לתכנות מערכות - 234122
קלט/פלט ב-C++ האופרטורים >> ו-<< הערוצים הסטנדרטיים ב-C++ מבוא לתכנות מערכות - 234122
קלט/פלט ב-C++ • ערוצי הקלט ופלט הסטנדרטיים מיוצגים ב-C++ על ידי המשתנים הגלובליים הבאים (מוגדרים בקובץ iostream): • cout: ערוץ הפלט הסטנדרטי • :cinערוץ הקלט הסטנדרטי • cerr: ערוץ השגיאות הסטנדרטי • כדי להדפיס משתנה כלשהו לערוץ פלט משתמשים באופרטור >> • ניתן להדפיס ירידת שורה ולבצע flush לחוצץ על ידי הדפסת endl • כדי לקלוט ערך למשתנה מערוץ קלט משתמשים באופרטור << • ניתן לשרשרמספר הדפסות/קריאות בבת אחת intmain() { int n; std::cin>> n; // Reads an int from user std::cout<< "You entered " << n << std::endl; return 0; } מבוא לתכנות מערכות - 234122
קלט/פלט ב-C++ • יתרונות: • לא צריך לזכור קודים מוזרים כדי להדפיס • לא ניתן להתבלבל בסוג הטיפוס המודפס • ניתן להרחיב את השימוש בקלט/פלט עבור טיפוסים שיצרנו (בתרגול הבא) #include<iostream> usingstd::cout; usingstd::cerr; usingstd::cin; usingstd::endl; intmain() { int n; constchar* str = "Hello"; int* ptr = &n; cin >> n; cout << str << endl; cerr << n << " at "<< ptr << endl; return 0; } C++ #include<stdio.h> intmain() { int n; constchar* 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 מבוא לתכנות מערכות - 234122
קלט/פלט ב-C++ - סיכום • הערוצים הסטנדרטיים מיוצגים ב-C++ על ידי המשתנים cout, cin ו-cerr • מדפיסים לערוצי פלט בעזרת אופרטור >> • קולטים ערכים מערוצי קלט בעזרת << • ההדפסה או הקריאה נקבעים לפי סוג הטיפוס • ניתן להרחיב את שיטה זו עבור טיפוסים שיצרנו בעצמנו (תרגול הבא) מבוא לתכנות מערכות - 234122
מחלקות - Classes מתודות this public ו-private בנאים והורסים משתנים ופונקציות סטטיות מחלקות מחלקת מחסנית מבוא לתכנות מערכות - 234122
טיפוסי נתונים ב-C++ structPoint { intx, y; doubledistance(constPoint& p) { int dx = this->x - p.x; intdy= this->y - p.y; returnsqrt(dx*dx+dy*dy); } }; intmain() { Point p = { 3, 4 }; Point p2 = { 2, 8 }; double d = p.distance(p2); return 0; } • יצירת טיפוסי נתונים הוטמעה בשפת C++ • ב-C++ ניתן להגדיר את הפונקציות עבור טיפוס הנתונים ישירות בתוך המבנה • פונקציות המוגדרות כחלק מהמבנה נקראות מתודות(methods) אופונקציות חברות (member functions) • כדי להפעיל מתודה יש צורך בעצם מהטיפוס המתאים להפעיל אותה עליו • לכל מתודה יש פרמטר נסתר בשם thisוהוא מצביע לעצם עליו היא הופעלה מבוא לתכנות מערכות - 234122
מתודות - Methods structPoint { intx, y; doubledistance(constPoint& p); }; doublePoint::distance(constPoint& p) { int dx = this->x- p.x; intdy = this->y- p.y; returnsqrt(dx*dx+dy*dy); } • מתודות ניתן לממש ישירות בתוך המבנה (כמו בשקף הקודם) או להכריז עליהן במבנה ולממשן מחוץ לו: • הגדרות טיפוסים ב-C++ יופיעו בדרך כלל בקבצי h • אם מימוש הפונקציה נעשה בתוך המבנהאז הוא יופיע בקובץ ה-h • אם מימוש הפונקציה חיצונינשים אותו בקובץ ה-cpp/cc/C המתאים מבוא לתכנות מערכות - 234122
המצביע this structPoint { intx, y; doubledistance(constPoint& p)const; voidset(int x, int y); }; voidPoint::set(int x, int y) { this->x = x; this->y = y; } doublePoint::distance(constPoint& p) const{ int dx = x - p.x; intdy = y - p.y; returnsqrt(dx*dx+dy*dy); } • לכל מתודה של עצם מטיפוס X נשלח מצביע מטיפוס X* constששמו this • ניתןלהשמיטאת ה-thisכל עוד איןדו-משמעות • ניתן להגדיר מתודה כך שתפעל על עצמים שהינם constעל ידי הוספת const בסוף חתימת הפונקציה • עבור מתודה לעצם קבוע thisיהיה מטיפוס const X* const • אם this קבוע עבור מתודה מסוימת גם כל השדות שלו קבועים מדוע לא ניתן להגדיר את set כ-const? מבוא לתכנות מערכות - 234122
בקרת גישה - Access Control structPoint { private: intx, y; public: doubledistance(constPoint& p); voidset(int x, int y); }; // ... intmain() { Point p; p.x = 5; // error: 'intPoint::x' // is private p.set(3, 4); double d = p.distance(p); return 0; } • כדי לשמור על הסתרת המימוש מהמשתמש ניתן להגדיר חלקים פרטיים וחלקים פומביים • קוד אשר כתוב בתוך ה-namespace של הטיפוס רשאי לגשת לחלקים פרטיים • קוד אשר כתוב מחוץ למבנה אינו יכול לגשת לחלקים אלו • ניתן להגדיר פונקציות, שדות וטיפוסים כפרטיים או פומביים • למשל, פונקציות עזר של הטיפוס יוגדרוכ-private מבוא לתכנות מערכות - 234122
מחלקות - Classes • בדרך כלל מגדירים טיפוסים ב-C++ עם המילה השמורה class • ההבדל בין class ל-struct הוא בברירת המחדל עבור בקרת הגישה - public עבור struct ו-private עבור class • נהוג להשתמש ב-struct עבור טיפוסים פשוטים שכל שדותיהם הינם public classPoint { intx, y; public: doubledistance(constPoint& p) const; voidset(int x, int y); }; structPoint{ private: intx, y; public: doubledistance(constPoint& p) const; voidset(int x, int y); }; מבוא לתכנות מערכות - 234122
בנאים - Constructors classPoint { intx, y; public: Point(int x, int y); doubledistance(constPoint& p) const; }; Point::Point(int x, int y) { this->x = x; this->y = y; } intmain() { Point p1(3, 4); constPoint p2(2, 8); cout << p1.distance(p2) << endl; return 0; } • לכל מחלקה ניתן להגדיר סוג מיוחד של מתודות הנקראות בנאים(Constructorsאו C’torבקיצור) ששמן כשם המחלקה • בנאים משמשים לאתחול של עצם חדש מהמחלקה מבוא לתכנות מערכות - 234122
הורסים - Destructors classArray { int* data; intsize;public: Array(int size); ~Array(); // More methods ... }; Array::Array(int size) { data = newint[size]; this->size = size; } Array::~Array() { delete[] data; }intmain() { Array array(50); // code ... return 0; } // d'tor called • לכל מחלקה ניתן להגדיר סוג נוסף של מתודה הקרויה הורס(Destructorאו D’tor) ושמה כשם המחלקה ותחילית ~ • ההורס של המחלקה נקרא אוטומטיתבזמן שחרור עצם של המחלקה • עוד על בנאים והורסים בתרגולים הבאים מבוא לתכנות מערכות - 234122
פונקציות ושדות סטטיים classPoint { intx, y; staticPointorigin;public: Point(int x, int y); doubledistanceFromOrigin() const; staticvoidsetOrigin(int x, int y);};PointPoint::origin(0,0);doublePoint::distanceFromOrigin() const { int dx = x- origin.x; intdy = y- origin.y; returnsqrt(dx*dx + dy*dy);}voidPoint::setOrigin(int x, int y) { origin.x = x; origin.y = y;} • ניתן להגדיר משתנים סטטיים במחלקה, משתנים אלו אינם שייכים לעצם ספציפי • ניתן להגדיר מתודות סטטיות, מתודה סטטית אינה מקבלת thisולא דרוש עצם כדי להפעילה • מתודה סטטית רשאית לגשת לחלקים פרטיים של המחלקה • משתנים ומתודות סטטיים מצייתים לחוקי בקרת הגישה • אם למשל נגדיר משתנה סטטי כפרטי הוא יהיה נגיש רק מתוך המחלקה מבוא לתכנות מערכות - 234122
מחלקת מחסנית • נמיר כעת את המחסנית שלנו מתרגול 5 למחלקה ב-C++ • ב-C++ ערכי שגיאה מטופלים על ידי חריגות(תרגול 10) ולכן לא נהוג להחזיר קודי שגיאה • כדי לכתוב קוד גנרי ב-C++ משתמשים בתבניות(תרגול 10) ולכן נסתפק במחסנית של מספריםשלמים בינתיים classStack { int* data; intsize; intnextIndex; public: Stack(int size = 100); ~Stack(); intgetSize()const; voidpush(int n); voidpop(); int& top(); constint& top() const; }; nextIndex 5 2 17 3 מבוא לתכנות מערכות - 234122
מימוש המחסנית Stack::Stack(int size) { data = newint[size]; this->size = size; nextIndex = 0; } Stack::~Stack() { delete[] data; } voidStack::push(int n) { if (nextIndex >= size) { error("Stack full"); } data[nextIndex++] = n;} intStack::getSize()const{ returnnextIndex;} voidStack::pop() { if (nextIndex <= 0) { error("Stack empty"); } nextIndex--;}int& Stack::top() { if (nextIndex <= 0) { error("Stack empty"); } returndata[nextIndex - 1]; }constint& Stack::top() const { if (nextIndex <= 0) { error("Stack empty");} returndata[nextIndex - 1];} מה ההבדל? מבוא לתכנות מערכות - 234122
שימוש במחסנית • #include"stack.h" • #include<iostream> • usingstd::cout; • usingstd::endl; • intmain() { • Stackstack; • Stack stack2(50); • stack.push(1); • stack.push(213); • stack.pop(); • cout << stack.top() << endl; • return0; • } • #include"stack.h" • #include<stdio.h> • intmain() { • Stackstack = stackCreate(100); • Stackstack2 = stackCreate(50); • stackPush(stack, 1); • stackPush(stack, 213); • stackPop(stack); • printf("%d\n",stackTop(stack)); stackDestroy(stack); • stackDestroy(stack2); • return 0; • } C C++ מבוא לתכנות מערכות - 234122
מחלקות - סיכום • ב-C++ ניתן להגדיר מתודות כחלק מהטיפוסים • מתודות מקבלות פרמטר נסתר, this, אשר מאותחל להצביע לעצם עליו הופעלה המתודה • כדי למנוע גישה מהמשתמש למימוש ניתן להגדיר חלקים מהמחלקה כפרטיים ואת המנשק כפומבי • ניתן להגיד לכל מחלקה בנאים אשר ישמשו לאתחול משתנים חדשים • ניתן להגדיר לכל מחלקה הורס אשר ישמש לשחרור משתנה • ניתן להגדיר משתנים ומתודות סטטיים אשר אינם שייכים לעצם ספציפי מבוא לתכנות מערכות - 234122