1 / 48

תרגול מס' 10

תרגול מס' 10. המחלקה String עבודה עם קבצים תבניות חריגות. דוגמה - String. שימוש בבנאים, הורסים והעמסת אופרטורים. דוגמה - המחלקה String. השימוש במחלקות, בנאים, הורסים והעמסת אופרטורים מאפשר לנו להגדיר מחלקה עבור String כך שייחסכו מאיתנו החסרונות של השימוש ב- char*

leyna
Download Presentation

תרגול מס' 10

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תרגול מס' 10 המחלקה String עבודה עם קבצים תבניות חריגות

  2. דוגמה - String שימוש בבנאים, הורסים והעמסת אופרטורים מבוא לתכנות מערכות - 234122

  3. דוגמה - המחלקה String • השימוש במחלקות, בנאים, הורסים והעמסת אופרטורים מאפשר לנו להגדיר מחלקה עבור String כך שייחסכו מאיתנו החסרונות של השימוש ב-char* • בזכות בנאים והורסים לא נצטרך לנהל את הזיכרון ידנית • בזכות העמסת אופרטורים נוכל לשמור על כל הנוחות של char*, למשל גישה כמערך • בזכות העמסת אופרטורים נוכל לאפשר פעולות בסיסיות בצורה נוחה - למשל שרשור מבוא לתכנות מערכות - 234122

  4. String • classString { • intlength; • char* data; • staticchar* allocate_and_copy(constchar* data, int size); • voidverify_index(int index) const; • public: • String(constchar* str = ""); // String s1; or String s1 = "aa"; • String(constString& str); // String s2(s1); • ~String(); • intsize() const; • String& operator=(constString&); // s1 = s2; מבוא לתכנות מערכות - 234122

  5. String • String& operator+=(constString& str); // s1 += s2; • constchar& operator[](int index) const; // c = s1[5] • char& operator[](int index); // s1[5] = 'a' • friendostream& operator<<(ostream&,constString&); // cout << s1; • friendbooloperator==(constString&, constString&); // s1==s2 • friendbooloperator<(constString&, constString&); // s1<s2 • }; • booloperator!=(constString& str1, constString& str2); • booloperator<=(constString& str1, constString& str2); • booloperator>(constString& str1, constString& str2); • booloperator>=(constString& str1, constString& str2); • constStringoperator+(constString& str1, constString& str2); מבוא לתכנות מערכות - 234122

  6. מימוש String • voiderror(constchar* str) { • cerr << "Error: " << str << endl; • exit(0); • } • char* String::allocate_and_copy(constchar* str, int size) { • returnstrcpy(newchar[size + 1], str); • } בהמשך נחליף את error בשימוש בחריגות פונקצית עזר סטטית,אין לנו כאן צורך ב-this מבוא לתכנות מערכות - 234122

  7. מימוש String • String::String(constchar* str) : • length(strlen(str)), • data(allocate_and_copy(str, length)) { • } • String::String(constString& str) : • length(str.size()), • data(allocate_and_copy(str.data, length)) { • } • String::~String() { • delete[] data; • } • intString::size() const { • returnlength; • } מבוא לתכנות מערכות - 234122

  8. מימוש String • String& String::operator=(constString& str) { • if (this == &str) { • return *this; • } • delete[] data; • data = allocate_and_copy(str.data, str.size()); • length = str.length; • return *this; • }String& String::operator+=(constString& str) { • char* new_data = allocate_and_copy(data, str.size() + size()); • strcat(new_data, str.data); • delete[] data; • length += str.length; • data = new_data; • return *this; • } מבוא לתכנות מערכות - 234122

  9. מימוש String • voidString::verify_index(int index) const { • if (index >= size() || index < 0) { • error("Bad index"); • } • return; • } • constchar& String::operator[](int index) const { • verify_index(index); • returndata[index]; • } • char& String::operator[](int index) { • verify_index(index); • returndata[index]; • } פונקציות המחזירות &צריכות בדרך כלל שתי גרסאות - עבור עצמים רגילים ועבור קבועים מבוא לתכנות מערכות - 234122

  10. מימוש String • booloperator==(constString& str1, constString& str2) { • returnstrcmp(str1.data, str2.data) == 0; • } • ostream& operator<<(ostream& os, constString& str) { • returnos << str.data; • } • booloperator<(constString& str1, constString& str2) { • returnstrcmp(str1.data, str2.data) < 0; • } מבוא לתכנות מערכות - 234122

  11. מימוש String • booloperator!=(constString& str1, constString& str2) { • return !(str1 == str2); • } • booloperator<=(constString& str1, constString& str2) { • return !(str2 < str1); • } • booloperator>(constString& str1, constString& str2) { • return str2 < str1; • } • booloperator>=(constString& str1, constString& str2) { • return str2 <= str1; • } • constStringoperator+(constString& str1, constString& str2) { • return String(str1) += str2; • } מבוא לתכנות מערכות - 234122

  12. דוגמה - קוד המשתמש ב-String intmain(intargc, char **argv) { String s = "So long"; String s2 = "and thanks for all the fish."; String sum = s+ " " + s2; sum[sum.size() - 1] = '!'; cout << sum << endl; return 0; } כל האפשרויות הקיימות עבור char* נתמכות במחלקה החדשה רק כעת אין צורך בניהול זיכרון מפורש מבוא לתכנות מערכות - 234122

  13. std::string • קובץ ה-headerstringמהספריה הסטנדרטית של C++ מכיל מימוש של המחלקה std::stringאשר תומכת בכל הפעולות שמימשנו כאן ועוד • הקפידו להשתמש ב-std::string ולא ב-char*עבור מחרוזות ב-++C מבוא לתכנות מערכות - 234122

  14. עבודה עם קבצים המחלקות ofstream ו-ifstream מבוא לתכנות מערכות - 234122

  15. קלט/פלט עם קבצים ב-C++ • כמו ב-C גם ב-C++ הטיפול בקבצים דומה לטיפול בערוצי הקלט/פלט הסטנדרטיים • ב-C++ עבודה עם קבצים מתבצעת בעזרת מחלקות המוגדרות בקובץ fstream • המחלקה ofstreamמשמשת לכתיבה לקובץ • המחלקה ifstreamמשמשת לקריאה מקובץ • לשתי המחלקות יש בנאי המקבל את שם הקובץ לפתיחה • לשתי המחלקות יש הורס המוודא שהקובץ נסגר כאשר העצם נהרס • הדפסהוקריאה מתבצעות על ידי שימוש ב->> ו-<< כמו עבור ערוצים רגילים • שאר הפעולות הדרושות על ערוצי הפלט מבוצעות כמתודות של המחלקות • למשל המתודה eof מחזירה חיווי האם הגענו לסוף הקובץ מבוא לתכנות מערכות - 234122

  16. קלט/פלט עם קבצים - דוגמה • #include<fstream>usingstd::ifstream;usingstd::ofstream;usingstd::cerr;usingstd::endl;voidcopyFile(constchar* fromName, constchar* toName) { • ifstream from(fromName); • if (!from) { • cerr << "cannot open file " << fromName << endl; • return; } • ofstream to(toName); • if (!to) { • cerr << "cannot open file " << toName << endl; • return; } • while (!from.eof()) { • char c; • from >> c; • to << c;}} המרה ל-bool למה לא צריך לסגור את הקובץ? מבוא לתכנות מערכות - 234122

  17. תבניות תבניות של פונקציות תבניות של מחלקות מבוא לתכנות מערכות - 234122

  18. תכנות גנרי • נניח שברצוננו לכתוב פונקציה למציאת המקסימום במערך של עצמים • לכל טיפוס נצטרך לרשום מחדש פונקציה כמעט זהה • קל לנו לראות את התבנית הכללית של פונקציות המוצאות מקסימום במערך • רק שם הטיפוס משתנה (בדוגמאות שלנו int ו-string) • ברצוננו לכתוב קוד גנרי שיתאים לכל טיפוס • כיצד עשינו זאת ב-C? מה היו החסרונות בשיטה זו? intmax(constint* array, int size) { int result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i]; } } return result; } stringmax(conststring* array, int size) { string result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i]; } } return result; } מבוא לתכנות מערכות - 234122

  19. תבניות • ניתן לכתוב תבניות של קוד - קוד התלוי בפרמטרים של זמן הקומפילציה • כדי להגדיר תבנית לפונקציה משתמשים במילה השמורה templateלפני הגדרת הפונקציה ומכריזים בתוך < > על הפרמטרים של התבנית: • הקומפיילר ישתמש בתבנית כדי ליצור קוד במקרה הצורך • תהליך יצירת מופע של הקוד המוגדר על ידי התבנית מתבצע בזמן הקומפילציה וקרוי instantiation • תבניות יש להגדיר תמיד בקבצי h כך שיהיו זמינות לקומפיילר template<classT> Tmax(constT* array, int size) { T result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i];}}return result;} ניתן להשתמש במילה השמורה typename במקום ב-class בתוך קוד התבנית T מייצג שם של טיפוס וניתן להשתמש בו כמו בשם של טיפוס רגיל: להכריז על משתנים מסוג T ולבצע עליהם פעולות מבוא לתכנות מערכות - 234122

  20. תבניות - שימוש • כדי להשתמש בפונקציה המוגדרת על ידי תבנית ניתן לקרוא לה לפי שמה ובתוספת הפרמטרים הדרושים לתבנית: • ניתן לקרוא לפונקציה ללא ציון הפרמטרים לתבנית - במקרה זה הקומפיילר יסיק אותם בעצמו • במקרה זה הפונקציה משתתפת בשלב פתרון ההעמסה יחד עם פונקציות נוספות intarray[] = {4, -1 ,2}; stringarray2[] = {"Hello", "cruel", "world"}; cout<< max<int>(array, 3) << endl; cout<< max<string>(array2, 3) << endl; intarray[] = {4, -1 ,2}; stringarray2[] = {"Hello", "cruel", "world"}; cout<< max(array, 3) << endl; cout<< max(array2, 3) << endl; array הוא int* ולכן תיקרא max<int> array2 הוא string* ולכן תיקרא max<string> מבוא לתכנות מערכות - 234122

  21. תבניות והעמסת פונקציות • בפתרון העמסה אשר מעורבות בו תבניות: • הקומפיילר יעדיף פונקציה אשר מתאימה בדיוק לפרמטרים מאשר שימוש בתבנית (על ידי הסקת טיפוסים) • הקומפיילר יעדיף שימוש בתבנית על שימוש בהמרות • הקומפיילר לא יבצע המרות אוטומטיות כדי להתאים לתבנית • ניתן להכריח שימוש בפונקצית תבנית על ידי ציון הפרמטרים לתבנית במפורש מבוא לתכנות מערכות - 234122

  22. תבניות - העמסת פונקציות • דוגמאות: intmax(int a , intb , int c ) { if (a > b && a > c) return a ; if (b > c) return b ; return c; } // ... int j = max (1,3,3); //  char c = max ('w','w','w');//  Strings = max (s1,s2,s3); //  int j = max (1,'a','a'); //  template<classT> Tmax (T a , T b , T c ) { if (a > b && a > c) return a ; if (b > c) return b ; return c; } // ... int j = max(1,3,3); //  charc = max ('w','w','w'); //  Strings = max (s1,s2,s3); //  int j = max (1,'a','a'); //  int j = max<int> (1,'a','a'); // מבוא לתכנות מערכות - 234122

  23. תבניות - יצירת מופעים • כאשר הקומפיילר משתמש בתבנית הוא מנסה ליצור פונקציה לפי התבנית על ידי החלפת הפרמטרים לתבניתבערכים שהועברו לה. • האם תהליך זה יכול להיכשל? • בכדי שהשימוש בתבנית יצליח על הארגומנטים המועברים לתבנית לעמוד בדרישות הלא מפורשות על הטיפוס המופיעות בקוד • אילו דרישות יש על הטיפוס T ב-max? template<classT> Tmax(constT* array, int size) { T result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i];} } return result;} Stack array[] = {Stack(50), Stack(60), Stack(70)}; cout<< max(array, 3) << endl; מבוא לתכנות מערכות - 234122

  24. מחלקות גנריות • ניתן להגדיר תבנית עבור מחלקה ובכך לממש מחלקות גנריות בדומה ל-C • בניגוד ל-Cלא נצטרך לשלוח מצביעים לפונקציות והקומפיילר יוודא את נכונות הטיפוסים בעצמו template <classT> classArray { T* data; intsize; public: explicitArray(intsize); Array(constArray& a); ~Array(); Array& operator=(constArray& a); intsize() const; T& operator[](int index); constT& operator[](int index) const; }; בתוך ההכרזה על המחלקה ניתן לרשום Array במקום Array<T> מבוא לתכנות מערכות - 234122

  25. מחלקות גנריות • בניגוד לפונקציות, עבור מחלקות חייבים לרשום במפורש את הארגומנטים לתבנית - הקומפיילר לא יכול להסיק אותם בעצמו • כל שימוש בתבנית עם ארגומנטים אחרים יוצר טיפוס חדש voidf(Array<string>& words) { Array<int> numbers(100); for(int i = 0; i < numbers.size(); ++i) { numbers[i] = i; } words = numbers; // error, type mismatch } מבוא לתכנות מערכות - 234122

  26. מחסנית גנרית ב-C++ template <classT = int> classStack { T* data; intsize; intnextIndex;public: Stack(int size = 100); Stack(constStack& s); ~Stack(); Stack& operator=(constStack&); voidpush(constT& t); voidpop(); T& top(); constT& top() const; intgetSize()const; }; • נשפר את המחסנית שלנו כך שתהיה גנרית: ניתן לתת ערך ברירת מחדל, כמו בערכי ברירת מחדל של פונקציות מבוא לתכנות מערכות - 234122

  27. מחסנית גנרית ב-C++ template<classT> Stack<T>::Stack(int size) : data(newT[size]), size(size), nextIndex(0) { } template <classT> Stack<T>::Stack(constStack<T>& s) : data(newT[s.size]), size(s.size), nextIndex(s.nextIndex) { for (int i = 0; i < nextIndex; ++i) { data[i] = s.data[i]; } } template <classT> Stack<T>::~Stack() { delete[] data; } מבוא לתכנות מערכות - 234122

  28. מחסנית גנרית ב-C++ template<classT> Stack<T>& Stack<T>::operator=(constStack<T>& s) { if (this == &s) { return *this; } delete[] data; data = newT[s.size]; size = s.size; nextIndex = s.nextIndex; for (int i = 0; i < nextIndex; ++i) { data[i] = s.data[i]; } return *this; } מבוא לתכנות מערכות - 234122

  29. מחסנית גנרית ב-C++ template<classT> voidStack<T>::push(constT& t) { if (nextIndex >= size) { error("Stack full"); } data[nextIndex++] = t; } template <classT> voidStack<T>::pop() { if (nextIndex <= 0) { error("Stack empty"); } nextIndex--; } מבוא לתכנות מערכות - 234122

  30. מחסנית גנרית ב-C++ template<classT> T& Stack<T>::top() { if (nextIndex <= 0) { error("Stack empty");} returndata[nextIndex - 1]; } template <classT> constT& Stack<T>::top() const{ if (nextIndex <= 0) { error("Stack empty"); } returndata[nextIndex - 1]; } template <classT> intStack<T>::getSize()const{ returnsize; } מבוא לתכנות מערכות - 234122

  31. שימוש במחסנית הגנרית intmain() { Stack<int> s; Stack<string> s2; s.push(20); s2.push("Hello"); Stacks3(s); s3 = s2; // error cout << s.top() << s2.top() << endl; return 0; } למה מתקבלת שגיאה? מבוא לתכנות מערכות - 234122

  32. תבניות - פרמטרים • ניתן להגדיר תבניות המקבלותיותר מפרמטר אחד template<classT, classS> structPair { Tfirst; Ssecond; Pair(constT& t, constS& s) : first(t), second(s) {} }; Pair<char, double> p('a', 15.0); cout<< "(" << p.first << "," << p.second << ")"; הרווח הזה הוא חובה List<Pair<string, int> > phonebook; phonebook.add(Pair<string, int>("Dani",8343434)); phonebook.add(Pair<string, int>("Rami",8252525)); מבוא לתכנות מערכות - 234122

  33. תבניות - פרמטרים • ניתן להגדיר תבניות גם עבור פרמטרים מטיפוס int, למשל: template<classT, intN> classVector { T data[N]; public: Vector(); Vector& operator+=(constVector& v); //... }; voidf(Vector<int, 3>& v) { Vector<int, 5> v5; Vector<int, 3> v3; v3 += v; // o.k. v5 += v; // error } N נקבע בזמן הקומפילציה, לכן אנו מגדירים למעשה מערך בגודל קבוע כשדה במחלקה המספר הוא חלק מהטיפוס, לכן Vector<int, 3> ו-Vector<int, 5> הם טיפוסים שונים נסיון לחבר וקטורים ממימדים שונים לא יתקמפל מבוא לתכנות מערכות - 234122

  34. תבניות - שגיאות קומפילציה • כאשר הקומפיילר עובר על תבנית הוא יכול למצוא בה רק שגיאות בסיסיות • כאשר הקומפיילר יוצר מופע של תבנית יתגלו שגיאות התלויות בארגומנט הספציפי • השגיאה המתקבלת מהקומפיילר תהיה מורכבת ממספר שורות ותכלול את רשימת התבניות והארגומנטים שלהן • בגלל השגיאות המסובכות מומלץ לכתוב תחילה קוד רגיל ורק אחרי שהוא עובד להמיר אותו לתבנית voidf() { Stack<Pair<int, int> > ranges(10); } ..\main.cpp: In constructor `Stack<T>::Stack(int) [with T = Pair<int, int>]': ..\main.cpp:241: instantiated from here ..\main.cpp:108: error: no matching function for call to `Pair<int, int>::Pair()' ..\main.cpp:233: note: candidates are: Pair<int, int>::Pair(const Pair<int, int>&) ..\main.cpp:237: note: Pair<T, S>::Pair(T, S) [with T = int, S = int] מבוא לתכנות מערכות - 234122

  35. תבניות - סיכום • ניתן להגדיר תבנית לפונקציה • ניתן להגדיר מחלקות גנריות בעזרת שימוש ב-template • בניגוד ל-C כל בדיקות הטיפוסים נעשות בזמן קומפילציה • כדי שהשימוש בתבנית יתקמפל על הארגומנטים המועברים לתבנית להיות בעלי כל התכונות הדרושות לפי התבנית • ניתן להגדיר תבניות שהפרמטרים שלהן הם מספרים • ניתן להגדיר תבניות עם מספר פרמטרים • מומלץ לכתוב תחילה קוד ללא תבניות ולהמירו אח"כ כדי למנוע התמודדות עם פלט מסובך מהקומפיילר מבוא לתכנות מערכות - 234122

  36. חריגות שימוש ב-try, catch ו-throw הקצאה על ידי אתחול מבוא לתכנות מערכות - 234122

  37. חריגות • מנגנון החריגות עובד בעזרת שלושת המילים השמורות throw, try ו-:catch • throw: מקבלת כפרמטר את העצם אותו יש לזרוק כחריגה • try: מציינת תחילה של בלוק אשר ייתכן ותיזרק ממנו חריגה שנרצה לתפוס • catch: מציינת סוג חריגה לתפוס ואת הקוד המתאים לטיפול בחריגה • כאשר נזרקת חריגה הקוד הרגילמפסיק להתבצע ומתחילה יציאהמכל פונקציה בתכנית עד אשרהחריגה מטופלת • אם חריגה לא מטופלת בכלל(נזרקת מ-main) התכנית תסתיים voidf(int n) { if (n < 0) { throw -1;}} intmain(intargc, char **argv) { try { f(-7); } catch (int e) { cerr << "Error: " << e << endl; }} מבוא לתכנות מערכות - 234122

  38. חריגות • כאשר חריגה נזרקת מתבצעים הדברים הבאים: • כל עוד אנחנו לא בתוך בלוק tryקוראים להורסים של המשתנים המקומיים ויוצאים מהפונקציה • אם אנחנו בבלוק try בודקים האם קיים catch מתאים לסוג החריגה הקיים • אם לא, קוראים להורסים וממשיכים לצאת • אם כן, מתבצע הקוד בבלוק ה-catch ולאחריו הפונקציה ממשיכה מהשורה שאחרי בלוק ה-catch האחרון • ייתכנו מספר בלוקים של catch עבור טיפוסים שונים • תהליך היציאה מפונקציות עד לתפיסת שגיאה נקרא התרת המחסנית(stack unwinding) • בין זריקת החריגה ועד לתפיסתה הקוד היחיד שמתבצע הוא של הורסים של המשתנים המקומיים המשוחררים מבוא לתכנות מערכות - 234122

  39. חריגות voidf(int n) { if (n < 0) { throw -1;}}voidg(int n) { f(-n);}voidh(int n) { g(2*n);}intmain() { try { h(7); } catch (int e) { cerr << "Error: " << e;}} • השימוש בחריגות מונע כתיבה מיותרת של קוד עבור טיפול בשגיאות בכל פונקציה בדרך • מי שיודע למצוא את השגיאה זורק חריגה • רק מי שיודע לטפל בשגיאה מתייחס אליה • בשימוש בקודי שגיאה כמו ב-C הטיפול במקרי השגיאה נהפך למרבית הקוד • ב-C++ ניתן לזרוק כל עצם שהוא אבל נהוג לזרוק רק מחלקות שנוצרו במיוחד כדי לציין שגיאות ספציפיות throw call call call main h g f return return return מבוא לתכנות מערכות - 234122

  40. חריגות template<typenameT> classStack { T * data; intsize; intnextIndex;public: Stack(int size = 100); Stack(constStack& stack); ~Stack(); Stack& operator=(constStack& s); voidpush(constT& t); voidpop(); T& top(); constT& top()const; intgetSize()const; classFull {}; classEmpty {}; }; • נוסיף חריגות למחסנית הגנרית שלנו: template<typenameT> voidStack::push(constT& t) { if (nextIndex >= size) { throw Full(); } data[nextIndex++] = t; } template<typenameT> voidStack::pop() { if (nextIndex <= 0) { throw Empty(); } nextIndex--; } ניתן להגדיר מחלקות בתוך מחלקות מבוא לתכנות מערכות - 234122

  41. חריגות - שימוש במחסנית voidf(Stack<int>& s) { try { s.pop(); for (int i = 0; i < 10; ++i) { s.push(i); } } catch (Stack<int>::Empty& e) { cerr << "No numbers"; } catch (Stack<int>::Full& e) { cerr << "Not enough room"; } catch(...) { cerr << "Oops"; throw; } } • ניתן להשתמש במספר משפטי catch עבור try יחיד • ניתן להשתמש ב-... כדי לתפוס כל חריגה • במקרה זה לא ניתן לגשת לחריגה שנתפסה • אם כמה מקרים מתאימים לתפיסת החריגה ייבחר הראשון שמופיע • לכן ... תמיד מופיע אחרון • ניתן לזרוק חריגה מחדש מ-catch על ידי throwללא פרמטרים מבוא לתכנות מערכות - 234122

  42. חריגות בשביל להגדיר מחלקה מקוננת כך יש להכריז עליה בתוך String classString::BadIndex { public: intindex; BadIndex(int index) : index(index) {}};voidString::verify_index(int index) const { if (index >= size() || index < 0) { throwBadIndex(index);}return;}constchar& String::operator[](int index) const { verify_index(index); returndata[index];}char& String::operator[](int index) { verify_index(index); returndata[index];} • חריגות יכולות להיות יותר מורכבות • למשל ניתן לשלוח בהן מידע מדויק על השגיאה voidf(String& s) { try { cout << s[50]; } catch (String::BadIndex& e) { cerr << "Bad Index: " << e.index;}} מבוא לתכנות מערכות - 234122

  43. חריגות • ניתן לזרוק חריגות מבנאי: • מסוכן לזרוק חריגות מהורס: • יכולה להיות רק חריגה אחת בו זמנית • בזמן זריקת חריגה הורסים ממשיכים להתבצע • אם נזרקת חריגה נוספת התכנית תתרסק • כאשר הקצאת זיכרון על ידי newנכשלתנזרקת חריגה מסוג std::bad_alloc Rational::Rational(intn, intd) : num(num), denom(denom) { if (denom == 0) { throwDivideByZero();}} intmain() { try { while (true) { newint[1000000]; } } catch (std::bad_alloc& e) { cerr << "Out of memory"; } return 0;|} מבוא לתכנות מערכות - 234122

  44. קוד בטוח לחריגות • לחריגות יש חיסרון חשוב - כל קריאה בקוד שלנו עלולה לגרום לזריקת חריגה (בצורה ישירה או עקיפה) ולכן הקוד צריך להיות מוכן לזריקת חריגה בכל שלב • לדוגמה, מה הבעיה באופרטור ההשמה של String שלנו? char* String::allocate_and_copy(constchar* str, int size) { returnstrcpy(newchar[size+1], str);} String& String::operator=(constString& str) { if (this == &str) { return *this; } delete[] data; data = allocate_and_copy(str.data, str.size()); length = str.length; return *this; } מבוא לתכנות מערכות - 234122

  45. קוד בטוח לחריגות • כאשר מבצעים קוד של שחרור והקצאות - קודם מבצעים את החלק הבעייתי ואח"כ את שחרור המידע הישן: • עדיין קיימת בעיה - מה קורה אם יש שתי הקצאות בבת אחת? char* String::allocate_and_copy(constchar* str, int size) { returnstrcpy(newchar[size+1], str);} String& String::operator=(constString& str) { char* temp = allocate_and_copy(str.data, str.size()); delete[] data; data= temp; length = str.length; return *this; } למה לא צריך בדיקת השמה עצמית? מבוא לתכנות מערכות - 234122

  46. הקצאת משאבים על ידי אתחולResource Acquisition is Initialization • כדי להתמודד עם הסכנות הלא צפויות של חריגות נהוג לעטוף את כל הקצאות המשאבים במחלקות • בשיטה זו ההורס של המחלקה אחראי לנקות • הורסים נקראים אוטומטית, בפרט בזמן התרת המחסנית • השימוש בשיטה זו חוסך למתכנת התעסקות מייגעת בהקצאות ושחרורים voidgood() { string str1; string str2; // ... code ... } voidbad() { char* str1 = newchar[N+1]; try { char* str2 = newchar[N+1]; // ... code ... } catch (bad_alloc& e) { delete[] str1; } } מבוא לתכנות מערכות - 234122

  47. חריגות -שימוש נכון • המנעו מכתיבת קוד אשר משתמש בחריגות כבערכי חזרה • המנעו משימוש בחריגות לשם שליטה בקוד • המנעו מלהכריח את המשתמשים בקוד שלכם לעשות זאת voidgood(Stack<int>& s) { while(s.getSize() > 0) { cout << s.top() << endl; s.pop(); } } voidbad(Stack<int>& s) { while(true) { try { cout << s.top() << endl; s.pop(); } catch (Stack<int>::Empty& e) { break; } } } מבוא לתכנות מערכות - 234122

  48. חריגות - סיכום • טיפול בשגיאות מתבצע ב-C++ בעזרת throw, try ו-catch • אפשר לזרוק כל דבר אבל נהוג לזרוק רק עצמים ממחלקות ייעודיות • השתמשו בחריגות כדי לאפשר לבנאי להיכשל • שגיאות מהספריה הסטנדרטית, כמו כשלון של new מטופלות על ידי חריגות • כדי לשמור על קוד בטוח לחריגות עטפו הקצאות משאבים במחלקות • המנעו מכתיבת קוד אשר משתמש בחריגות כבערכי חזרה מבוא לתכנות מערכות - 234122

More Related