c++: יוצרים, הורסים ואופרטורים. המחלקה stack - תזכורת class stack...

Post on 21-Dec-2015

230 Views

Category:

Documents

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

C++יוצרים, הורסים ואופרטורים :

- תזכורתStackהמחלקה

class Stack {private:

int* array; int size;int topIndex;

public:Result init (int size);void destroy();Result push (int element);Result pop ();Result top(int& element) const; Result print() const;

};

מה קורה אם המשתמש שוכח ?destroy או ל-initלקרוא ל-

מה קורה אם המשתמש שוכח ?destroy או ל-initלקרוא ל-

2

יוצרים והורסים - מוטיבציה

פתח - בהגדרת הטיפוס אנו משאירים הבעיההבעיה• של המשתמש:לטעויות

( – initניתן לשכוח לאתחל עצמים )לא לקרוא ל-–התכנית תתנהג בצורה לא צפויה.

ניתן לשכוח להרוס אובייקט וכך לגרום לדליפת –זיכרון.

.עצמים זמנייםיתר על כן, פעמים רבות נוצרים •

למשל, בהעברת והחזרת פרמטרים מפונקציות–

אין למשתמש יכולות לאתחל עצמים אלו בצורה –מפורשת.

3

יוצרים והורסיםפונקציות אתחול והריסה - הפתרון•

שנקראות אוטומטית כאשר האובייקט נוצר וכאשר הוא אמור להיהרס.

בכל פעם שנגדיר משתנה מטיפוס מעתה,•Stack נידרש להעביר ליוצר את הפרמטרים

הדרושים לצורך אתחול האובייקט

בכל פעם שהאובייקט לא יהיה נגיש יותר •הקומפיילר יקרא להורס אשר ישחרר את

האובייקט.

( כשם constructorsשמות היוצרים )•( כשם destructorהמחלקה. שם ההורס )

המחלקה ולפניה ~.

class Stack {private:

int* array; int size;int topIndex;

public:Stack (int size); ~Stack();...

};

class Stack {private:

int* array; int size;int topIndex;

public:Stack (int size); ~Stack();...

};

הכרזה במחלקה

4

Destructors ו-Consturctorsמימוש

Stack::Stack(int initSize) {array = new int[initSize];topIndex = 0; size = initSize ;

}

Stack::~Stack() { delete[] array;}

Stack::Stack(int initSize) {array = new int[initSize];topIndex = 0; size = initSize ;

}

Stack::~Stack() { delete[] array;}

מימוש הבנאים וההורסים:

(cpp.)בקובץ

)להלן Constructorשימו לב כי •"C’tor-ו )"Destructor להלן( "D’tor!לא מחזירים ערכים )"

הערות:•אם איננו מגדירים יוצרים –

והורסים, מוגדרים אוטומטית יוצרים והורסים ברירת מחדל

(default C’tor and D’tor.)

כמעט לכל מחלקה נדרש –להגדיר מפורשות יוצרים

לאתחול שדותיה.

הגדרת הורסים תתבצע תמיד –עבור מחלקות המקצות זיכרון דינמי בעצמן. במקרה זה על

ההורס לשחררו )לכן עליו להיות מוגדר מפורשות(.

, בהתאמה.d’tor וה-c’tor נקראים ה-delete ו-newבקריאה ל-•

5

שימוש במחסנית שיש לה יוצרים והורסים

#include “Stack.h”int main() {

Stack s(100); // the c’tor is called with size 100Stack* ps = new Stack(100); // same heres.push(1);s.push(213);s.pop ();int i=0;s.top(i); ps->push(1);ps->push(2);ps->pop();delete ps; // the d’tor is called for psreturn 0;

} // the d’tor is called for s

6

זמני קריאה של יוצרים והורסים דרכים להקצאת משתנים.4קיימות •

זמני הקריאה של היוצרים וההורסים תלויים באופן הקצאת –העצם.

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

שהתוכנית מגיעה להכרזת המשתנה.

בכל פעם שהתוכנית ההורסיוצאת מהתחום בו הוגדר

המשתנה.

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

שהתוכנית מגיעה להכרזת המשתנה.

בכל פעם שהתוכנית ההורסיוצאת מהתחום בו הוגדר

המשתנה.

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

(.mainהתוכנית )לפני ה- עם סיום התוכנית ההורס

(.main)לאחר סיום

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

(.mainהתוכנית )לפני ה- עם סיום התוכנית ההורס

(.main)לאחר סיום

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

. newשמוקצה אובייקט ע”י בכל פעם שאובייקט ההורס

.deleteמשוחרר ע”י

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

. newשמוקצה אובייקט ע”י בכל פעם שאובייקט ההורס

.deleteמשוחרר ע”י

משתנים סטאטייםבפעם הראשונה נקראהיוצר

שהתוכנית מגיע לתחום בו מוגדר המשתנה.

עם סיום התוכנית.ההורס

משתנים סטאטייםבפעם הראשונה נקראהיוצר

שהתוכנית מגיע לתחום בו מוגדר המשתנה.

עם סיום התוכנית.ההורס7

דוגמה לזמני קריאה#include “Stack.h”Stack globals(100); // globals c’tor is called int main() {

Stack locals(50); // locals c’tor is called

Stack* ps = new Stack(600); //ps c’tor is called

Stack* ps2 = new Stack(600); //ps2 c’tor is called

delete ps; // ps destructor is called

return 0; // locals destructor is called

} // globals destructor is called// ps2 destructor is never called!

8

רשימות אתחול

בעת יצירת עצם יש לאתחל •את כל השדות שלו

אתחול השדות נעשה לפני •C’torהכניסה לגוף הקוד של ה-

אתחול השדות יכול להיות לא • חסר C’torמפורש -קריאה ל-

פרמטרים של השדה

אתחול השדות יכול להיות •מפורש - הוספת קריאה

ברשימת ברשימת של השדה C’torל-האתחולהאתחול

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

רשימת אתחול: אתחול שדות C’torלפני גוף ה-

9

רשימות אתחול

מייד אחרי הצהרת היוצר ולפני •גוף היוצר )החלק שבתוך ה-}{( מופיעות נקודתיים ואז רשימה של השדות הפנימיים, כשהם

מופרדים על ידי פסיקים.

כל מופע של שדה ברשימת •האתחול הוא למעשה קריאה

לאחד מהיוצרים של השדה.

לכל שדה כותבים בסוגריים •את הערכים שמעבירים ליוצר

שלוערכים אלו יכולים להיות –

תלויים בפרמטרים שהועברו ליוצר הראשי

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

רשימת אתחול: אתחול שדות C’torלפני גוף ה-

10

רשימות אתחול

סדר הפעלת היוצרים •אינו הסדר בו הם מופיעים ברשימת

האתחול אלא בו השדות מופיעים בהגדרת

המחלקה.

רשימת אתחול עדיפה על •פני השמה בתוך

הפונקציה מאחר ונחסך האתחול המיותר

השדות יאותחלו ע"י – C’torהקומפיילר בעזרת

חסר פרמטרים אוטומטית

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

class TwoStack {Stack s1, s2;

public :TwoStack(int size);

...}; TwoStack::TwoStack(int size)

: s1(100), s2(size*5) {...

}

רשימת אתחול: אתחול שדות C’torלפני גוף ה-

11

מערכים

בעת יצירת מערך לא ניתן •לשלוח פרמטרים ליצירת

כל אחד מהעצמים

לכן על מנת ליצור מערך •של עצם מסוג מסוים חובה שיהיה יוצר חסר פרמטרים

תזכורת: הקומפיילר מספק •יוצר שכזה רק אם לא

כתבנו אף יוצר בעצמנו!

class Stack {...

public: Stack(int size = 100); ...};

...

Stack array[10];

...

class Stack {...

public: Stack(int size = 100); ...};

...

Stack array[10];

...

12

Operatorהעמסת אופרטורים – overloading

לאפשר עבודה טבעית ככל האפשר עם מחלקות שהגדיר המטרה: המטרה: •המשתמש.

ע”י שימוש באופרטורים, המחלקות שהמשתמש מגדיר יוכלו לעבוד עם •אופרטורים כמו טיפוסים פנימיים. אם האופרטורים מממשים פעולה

טבעית של המחלקה, הדבר יכול לשפר את הקריאות של הקוד.

הגדרת אופרטורים מתבצעת באופן הבא:•<return type> operator<name>(<arguments>);

של המחלקה או פונקציות חיצוניות.methodsאופרטורים יכולים להיות •

אזי הארגומנט הראשון הנו תמיד methodאם האופרטור מוגדר כ-•האובייקט של המחלקה בו הוא מוגדר ואין צורך להעבירו.

13

Operator overloading

operatorפונקציות עם שם מיוחד: =+ •Stack& Stack::operator+=(int element) {

push(element);

return *this;

}

צורות הקריאה לפונקציה: • s.operator+=(5) ; s += 5 ;

14

The matrix exampleclass M {

double matrix[5][5];

public:

...

M& operator+=(const M&);

M operator*(const M &) const;

...

};

 

M operator+(const M& m1,const M& m2){

...

}

int main() {M m1 , m2, m3 ;//...m1+= m2 ;//calls m1.operator+=(m2);m2 = m1 + m3 ; //calls operator+(m1,m3);m3 = m2 * m2; //calls m2.operator*(m2);

}

15

Operator overloading – what can we overload?

+ - * / % ^ & | != < > += -= *= /= %=^= &= |= << >> <<= >>= ==!=<= >= && || ++ -- ,->* -> () [ ]

What not? What not? . .* :: ? : sizeof

post & pre

16

Operator overloadingמגבלות רק אופרטורים שכבר קיימים )ולא למשל אופרטור $%$(•

האופרטורים מקבלים את אותו מספר משתנים•

אותו סדר עדיפות ואותה אסוציאטיביות•

: :post & preהערות בנוגע ל- •ניתן להגדיר את שני סוגי הגדלה/הקטנה עצמית ע”י שימוש –

בפרמטר דמה:

• x.operator++() (pre: ++num)• x.operator++(int) (post: num++)

17

ערכי החזרה של אופרטורים

יחזיר בדרך כלל איבר Tאופרטור עבור המחלקה •.&T או רפרנס לאובייקט שעליו נקרא: Tזמני מסוג

יוצרת יוצרת כאשר הפעולה איבר זמניאיבר זמניהאופרטור יחזיר •.a+bשאותו אנו רוצים לקבל, למשל איבר חדשאיבר חדש

רוצים לקבל רוצים לקבל כאשר אנו רפרנסהאופרטור יחזיר • 3(+=a+=2). למשל, את האובייקט לאחר השינויאת האובייקט לאחר השינוי

תחזיר רפרנס )ואז ניתן יהיה +=דורש שהפעולה להפעיל אותה מספר פעמים ברצף על אותו

אובייקט(.

a++ אמורה להחזיר איבר זמני, ואילו ++aהפעולה •אמורה להחזיר רפרנס )מדוע?(

18

- תזכורתCקלט/פלט ב-++#include <stdio.h>  

int i = 17, j; double d; fprintf(stdout,"%s %d", "A string", i);fscanf(stdin, "%d %lf", &j, &d);fprintf(stderr, "Error!\n");

#include <stdio.h>  

int i = 17, j; double d; fprintf(stdout,"%s %d", "A string", i);fscanf(stdin, "%d %lf", &j, &d);fprintf(stderr, "Error!\n");

#include <iostream>using std::cin;using std::cout;using std::cerr;using std::endl;

int i = 17, j;double d; cout << "A string " << i;cin >> j >> d;cerr << "Error!" << endl;

#include <iostream>using std::cin;using std::cout;using std::cerr;using std::endl;

int i = 17, j;double d; cout << "A string " << i;cin >> j >> d;cerr << "Error!" << endl;

19

I/Oהעמסת אופרטורים של של אופרטורי הקלט והפלט בכדי overloadניתן לבצע •

לאפשר לבצע קלט אל ופלט מאובייקטים מטיפוסים שהוגדרו ע"י המשתמש בדומה לצורה שבה הדבר אפשרי

עבור טיפוסים פנימיים

int main() {int x;someclass y;otherclass z;cin >> x >> y >> z;cout << x << y << z;

}

20

I/Oהעמסת אופרטורים של class someclass {

int k , j ;//...friend ostream& operator<<(ostream& os, const someclass& s1);friend istream& operator>>(istream& is, someclass& s1);

}; ostream& operator<<(ostream& os,const someclass& s1) {

os << "(" << s1.k << "," << s1.j << ")";return os ;

}istream& operator>> (istream& is, someclass& s1) {

is >> s1.k >> s1.j ;return is ;

}

האם זה מתחייב? )רמז: לא(. ?friendלמה האופרטורים הוגדרו כ-•? כיצד ניתן לשנות זאת

21

I/Oהעמסת אופרטורים של int main() { someclass s; int i; cin >> i >> s ;

cout << s << i;// assume the user typed 1, 2 and 3 // What does the program print?

}

כפונקצית • להגדיר ניתן לא האלה האופרטורים את כי לב שימו member מכיוון שהארגומנט הראשון שלו צריך עצם השייך למחלקה

אחרת )ערוץ(כ-• להחזיר האופרטור את המחזירה הפונקציה את referenceעל

הערוץ כדי שיהיה אפשר להמשיך ולהזרים לו נתונים

22

class String {

int length;

char* data;

char* allocate_and_copy(const char* data, int size);

void verify_index(int index) const;

public:

String(const char* str = ""); // String s1; or String s1("aa");

String(const String& str); // String s2(s1);

~String();int size() const;

String& operator=(const String& str); // s1 = s2;

String& operator+=(const String& str); // s1 += s2;

const char& operator[](int index) const; // c = s1[5]

char& operator[](int index); // s1[5] = 'a'

friend ostream& operator<<(ostream&,const String&); // cout << s1;

friend bool operator==(const String&, const String&); // s1==s2

friend bool operator<(const String&, const String&); // s1<s2

};

bool operator!=(const String& str1, const String& str2);

bool operator<=(const String& str1, const String& str2);

bool operator>(const String& str1, const String& str2);

bool operator>=(const String& str1, const String& str2);

String operator+(const String& str1, const String& str2);

String - String.hהמחלקה

23

String.cpp

void error(const char* str) {

cerr << "Error: " << str << endl;

exit(0);

}

char* String::allocate_and_copy(const char* str, int size) {

return strcpy(new char[size+1], str);

}

נעשה בעזרתCטיפול בשגיאות ב-++ שיילמד בהמשך.Exceptionsמנגנון ה-

בינתיים נסתפק בכך.

פונקצית עזר פשוטהלביצוע כל ההקצאות

24

String.cppString::String(const char* str) :

length(strlen(str)),data(allocate_and_copy(str, length)) {

}

String::String(const String& str) :length(str.size()),data(allocate_and_copy(str.data, length)) {

} String::~String() {

delete[] data;}

int String::size() const {return length;

}

שימו לב לכך שביוצרים אין קוד

מלבד רשימת האתחול

lengthקל להחליף את השדה אם יש צורךstrlenבקריאה ל-

25

String.cppString& String::operator=(const String& str) {

if (this == &str) {

return *this;

}

delete[] data;

data = allocate_and_copy(str.data, str.size());

length = str.length;

return *this;

}

String& String::operator+=(const String& 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;

}

בדיקת הצבה עצמית לולא שורה ;s=sמה קורה עבור

זו?)עוד על כך בתרגול הבא(

26

String.cppvoid String::verify_index(int index) const {

if (index >= size() || index < 0) {

error("Bad index");

}

return;

}

const char& String::operator[](int index) const {

verify_index(index);

return data[index];

}

 

char& String::operator[](int index) {

verify_index(index);

return data[index];

}

"פעמיים זו פעם אחת יותר מדי"ולכן כדאי לשים את בדיקה זו

בפונקציה נפרדת )פרטית כמובן(

char by valueהאם החזרת משנה משהו?

27

String.cppbool operator==(const String& str1, const String& str2) {

return strcmp(str1.data, str2.data) == 0;

}

 

ostream& operator<<(ostream& os, const String& str) {

return os << str.data;

}

bool operator<(const String& str1, const String& str2) {

return strcmp(str1.data, str2.data) < 0;

}

פונקציות אלו friendהוגדרו כ-

28

String.cppbool operator!=(const String& str1, const String& str2) {

return !(str1 == str2);

}

bool operator<=(const String& str1, const String& str2) {

return !(str2 < str1);

}

bool operator>(const String& str1, const String& str2) {

return str2 < str1;

}

bool operator>=(const String& str1, const String& str2) {

return str2 <= str1;

}

String operator+(const String& str1, const String& str2) {

return String(str1) += str2;

}

מדוע פונקציות אלו אינן מוגדרות

?friendכ-

29

Stringהמחלקה

הקלה משמעותית בכתיבת קוד המטפל במחרוזות:•מחרוזות מתנהגות כטיפוס מובנה–

שרשור מחרוזות מתבצע בקלות–

אין בעיות של ניהול זיכרון יותר!–

בפועל, המחלקה הזו מיותרת:•.string המוגדרת בקובץ std::string יש מחלקה Cב-++–

דוגמה זו מראה כיצד יש לכתוב ולממש אופרטורים –שונים. הרעיון הכללי זהה תמיד.

30

ערוצי קבציםשימוש בערוצי קבצים דומה לשימוש בערוצי הקלט/פלט הסטנדרטיים. כל •

ערוצי עבור גם מוגדרות הסטנדרטיים הערוצים על המוגדרות הפעולות הקבצים.

שתילמד בהמשךCע"י שימוש בהורשה – תכונה של ++–

, ולכן על מנת להשתמש בהם צריכה fstreamfstreamמחלקות אלו מוגדרות בקובץ •להופיע בתחילת הקובץ השורה:

#include <fstream>#include <fstream>

המחלקה • ע"י מוגדרים מקבצים הקלט יש ifstreamערוצי זו למחלקה .constructorconstructor.המקבל כקלט מחרוזת ופותח את הקובץ ששמו ערך המחרוזת

המחלקה • ע"י מוגדרים לקבצים הפלט יש ofstreamערוצי זו למחלקה .constructorconstructorהמקבל כקלט מחרוזת ופותח את הקובץ ששמו ערך המחרוזת

לשתי המחלקות יש הורסים שסוגרים את הקבצים.•

31

דוגמה לעבודה עם קבצים

#include <fstream>using std::ifstream;using std::ofstream;using std::cerr;using std::endl; void copyFile(char * fromName, char* 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;

}} 32

מדוע אין צורך לסגור את קובץ

הקלט?

top related