1 / 36

תרגול מס' 10

תרגול מס' 10. עבודה עם קבצים תבניות חריגות. עבודה עם קבצים. המחלקות ofstream ו- ifstream. קלט/פלט עם קבצים ב- C++. כמו ב- C גם ב- C++ הטיפול בקבצים דומה לטיפול בערוצי הקלט/פלט הסטנדרטיים ב- C++ עבודה עם קבצים מתבצעת בעזרת מחלקות המוגדרות בקובץ fstream

delora
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 עבודה עם קבצים תבניות חריגות

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

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

  4. קלט/פלט עם קבצים - דוגמה • #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

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

  6. תכנות גנרי • נניח שברצוננו לכתוב פונקציה למציאת המקסימום במערך של עצמים - לכל טיפוס נצטרך לרשום מחדש פונקציה כמעט זהה • קל לנו לראות את התבנית הכללית של פונקציות המוצאות מקסימום במערך • רק שם הטיפוס משתנה (בדוגמאות שלנו 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

  7. תבניות • ניתן לכתוב תבניות של קוד - קוד התלוי בפרמטרים של זמן הקומפילציה • כדי להגדיר תבנית לפונקציה משתמשים במילה השמורה 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

  8. תבניות - שימוש • כדי להשתמש בפונקציה המוגדרת על ידי תבנית ניתן לקרוא לה לפי שמה ובתוספת הפרמטרים הדרושים לתבנית: • ניתן לקרוא לפונקציה ללא ציון הפרמטרים לתבנית - במקרה זה הקומפיילר יסיק אותם בעצמו • במקרה זה הפונקציה משתתפת בשלב פתרון ההעמסה יחד עם פונקציות נוספות 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

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

  10. תבניות - העמסת פונקציות • דוגמאות: 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');//  String s1 = 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); //  char c= max (’w’,’w’,’w’); //  String s1 = max (s1,s2,s3); //  int j = max (1,’a’,’a’); //  int j = max<int> (1,’a’,’a’); // מבוא לתכנות מערכות - 234122

  11. תבניות - יצירת מופעים • כאשר הקומפיילר משתמש בתבנית הוא מנסה ליצור פונקציה אשר הפרמטרים לתבנית מוחלפים בה • האם תהליך זה יכול להיכשל? • בכדי שהשימוש בתבנית יצליח על הארגומנטים המועברים לתבנית לעמוד בדרישות הלא מפורשות על הטיפוס המופיעות בקוד • אילו דרישות יש על הטיפוס 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

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

  13. מחלקות גנריות • בניגוד לפונקציות, עבור מחלקות חייבים לרשום במפורש את הארגומנטים לתבנית - הקומפיילר לא יכול להסיק אותם בעצמו • כל שימוש בתבנית עם ארגומנטים אחרים יוצר טיפוס חדש 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

  14. מחסנית גנרית ב-C++ template <classT> 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

  15. מחסנית גנרית ב-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

  16. מחסנית גנרית ב-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

  17. מחסנית גנרית ב-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

  18. מחסנית גנרית ב-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

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

  20. תבניות - פרמטרים • ניתן להגדיר תבניות המקבלותיותר מפרמטר אחד template<classT, classS> structPair { Tfirst; Ssecond; Pair(constT& t, constS& s) : t(t), s(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

  21. תבניות - פרמטרים • ניתן להגדיר תבניות גם עבור פרמטרים מטיפוס int, למשל: template<classT, intN> classVector { intdata[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

  22. תבניות - שגיאות קומפילציה • כאשר הקומפיילר עובר על תבנית הוא יכול למצוא בה רק שגיאות בסיסיות • כאשר הקומפיילר יוצר מופע של תבנית יתגלו שגיאות התלויות בארגומנט הספציפי • השגיאה המתקבלת מהקומפיילר תהיה מורכבת ממספר שורות ותכלול את רשימת התבניות והארגומנטים שלהן • בגלל השגיאות המסובכות מומלץ לכתוב תחילה קוד רגיל ורק אחרי שהוא עובד להמיר אותו לתבנית 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

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

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

  25. חריגות • מנגנון החריגות עובד בעזרת שלושת המילים השמורות 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

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

  27. חריגות 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

  28. חריגות 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

  29. חריגות - שימוש במחסנית 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

  30. חריגות בשביל להגדיר מחלקה מקוננת כך יש להכריז עליה בתוך 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

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

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

  33. קוד בטוח לחריגות • כאשר מבצעים קוד של שחרור והקצאות - קודם מבצעים את החלק הבעייתי ואח"כ את שחרור המידע הישן: • עדיין קיימת בעיה - מה קורה אם יש שתי הקצאות בבת אחת? 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

  34. הקצאת משאבים על ידי אתחול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

  35. חריגות -שימוש נכון • המנעו מכתיבת קוד אשר משתמש בחריגות כבערכי חזרה • המנעו משימוש בחריגות לשם שליטה בקוד • המנעו מלהכריח את המשתמשים בקוד שלכם מלעשות זאת 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

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

More Related