רשימת האתחול של בנאי מבוצעת לפי הכניסה לקוד הבנאי עצמו.
לאחר ביצוע הקוד, בהורס ייקראו ההורסים לכל שדותיו של העצם הנהרס.
class X
{
int n;
public:
X(int n) : n(n)
{
cout << "X::X():" << n << endl;
}
~X()
{
cout << "X::~X():" << n << endl;
}
};
class Y
{
X x1, x2;
public:
Y() : x1(1), x2(2)
{
cout << "Y::Y()" << endl;
}
~Y()
{
cout << "Y::~Y()" << endl;
}
};
int main()
{
Y y;
return 0;
}
מהתוכנית הזו יודפס תחילה ואז ואז ואז ואז ואז
בואו נממש מחסנית קטנה אשר יש לה גודל מקסימלי.
הנה :
class Stack
{
int* data;
int maxSize;
int nextIndex;
public:
Stack(int maxSize);
~Stack();
int getSize() const;
void push(int n);
void pop();
int top() const;
bool isFull() const;
bool isEmpty() const;
};
כעת, נביט על :
Stack::Stack(int maxSize) : data(new int[maxSize]), maxSize(maxSize), nextIndex(0)
{
}
Stack::~Stack()
{
delete[] data;
}
void Stack::push(int n)
{
if (this->isFull())
error("Stack full");
data[nextIndex++] = n;
}
int Stack::getSize() const
{
return nextIndex;
}
void Stack::pop()
{
if (this->isEmpty())
error("Stack empty");
nextIndex--;
}
int Stack::top() const
{
if (this->isEmpty())
error("Stack empty");
return data[nextIndex - 1];
}
bool Stack::isFull() const
{
return getSize() >= maxSize;
}
bool Stack::isEmpty() const
{
return nextIndex <= 0;
}
ניתן להגדיר גרסאות נוספות למחסנית עם כל מני דברים אחרים.
בנאים והורסים
כמה הערות חשובות בנוגע להרצאות:
- השורה
A a = b;יוצרת אובייקט חדש מהמחלקה באמצעות בנאי העתקה. - השורה
a = b;משתמשת באופרטור ההשמה של המחלקה .
שלושת הגדולים - Big three
בין בנאי ההעתקה, ההורס ואופרטור ההשמה הקיים קשר: אם צריך לממש אחד מהם, בדרך כלל צריך לממש את שלושתם..
איך יודעים מתי צריך לממש את הפונקציות האלו?
- אם מעורבים מצביעים במחלקה בדרך כלל יש צורך בפונקציות האלו
- אם יש שימוש ב- ו-
- אם מתבצעות הקצאות של משאבים אחרים (למשל קבצים)
המחלקה
אנחנו נראה מימוש מסויים של המחלקה וזה לאו דווקא האחד שהמהדר משתמש בו ב- או בכל שפה אחרת.
נסתכל תחילה על דוגמה של שימוש:
int main()
{
string s = "So long";
string s2 = "and thanks for all the fish.";
string s3 = s + " " + s2;
s3[s3.size() - 1] - "!";
cout << s3 << endl;
}
לאילו שדות נדרש:
- המערך
- אורך המערך הנ"ל
האם צריך בנאי העתקה, אופרטור השמה והורס? ברור
אילו פונקציות נוספות נוכל לממש? נראה כבר בקוד עצמו.
נתחיל בלבנות את קובץ ה-
class string
{
int length;
char* data;
static char* allocatedAndCopy(const char* data, int size);
public:
string(const char* str = "");
string(const string& str);
~string();
int size() const;
string& operator=(const string&);
string& operator+=(const string& str);
const char& operator[](int index) const;
char& operator[](int index);
friend ostream& operator<<(ostream& stream, const string& str);
friend bool operator==(const string& a, const string& b);
friend bool operator<(const string& a, const string& b);
};
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);
כעת נבנה את
char* string::allocateAndCopy(const char* str, int size)
{
return strcpy(new char[size + 1], str);
}
string::string(const char* str) :
length(strlen(str)),
data(allocateAndCopy(str, strlen(str)))
{}
string::string(const string& char) :
length(str.size()),
data(allocateAndCopy(str.data, str.length))
{}
string::~string()
{
delete[] data;
}
int string::size() const
{
return length;
}
string& string::operator=(const string& str)
{
if (this == &str)
return *this;
char* tmp = allocateAndCopy(str.data, str.length);
delete[] data;
data = tmp;
length = str.length;
return *this;
}
string& string::operator+=(const string& str)
{
char* newData = allocateAndCopy(data, this->length + str.length);
strcat(newData, str.data);
delete[] data;
length += str.length;
data = newData;
return *this;
}
const char& string::operator[](int index) const
{
if (index >= size() || index < 0)
error("Bad index");
return data[index];
}
char& string::operator[](int index)
{
if (index >= size() || index < 0)
error("Bad index");
return data[index];
}
bool 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;
}
bool 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;
}
איטרטורים(אופרטור אינדקס)
איטרטורים הם פשוט פויינטר למבנה נתונים אשר מאפשר לנו לבצע מעבר סדרתי על דברים כמו רשימה מקושרת או מחסנית או קבוצה.
זה מאפשר לנו לעבור על כל האיברים במבנה הנתונים שלנו ללא תלות במימוש שלו או במה באמת יש בו.
עבור קבוצה עם האיברים . נרצה לעבור על כולם ולהדפיס אותם. באמצעות איטרטורים נעשה זאת כך:
Set s1;
// s1 = {1,2,3}
Set::Iterator it = s1.begin();
cout << (*it) << endl; // prints 1
++it;
cout << (*it) << endl; // prints 2
איטרטור הוא פשוט מחלקה אשר בה יש:
- אופרטור כ- כדי לקדם את האיטרטור כך שיצביע לאיבר הבא.
- אופרטור כדי לגשת לאיבר עליו מצביע האיטרטור - בדרך כלל מחזיר רפרנס כדי לאפשר שינוי של האיבר.
- אופרטור כדי להשוות בין איטרטורים ובפרט כדי לבדוק שלא הגענו לסוף המבנה.
ב+, אם למחלקה יש איטרטור המקיים את הנ"ל, נוכל, במקום לכתוב את:
Set s1;
for (Set::Iterator it = s1.begin(); it != s1.end(); ++it)
{
int n (*it);
cout << n << endl;
}
נוכל לכתוב את זה:
Set s1;
for (int n : s1)
{
cout << n << endl;
}