חזור לתיקייה: תרגול

6 - מחלקות, המחלקה string ואופרטור אינדקס

חיפוש בתיקיה
Ctrl K
לחץ כאן או הקש Ctrl K כדי לחפש בכל התיקיה
  • בנאים והורסים
  • שלושת הגדולים - Big three
  • המחלקה stirng
  • איטרטורים(אופרטור אינדקס)

6 - מחלקות, המחלקה string ואופרטור אינדקס

02340124

מבוא לתכנות מערכות

רשימות תרגול לקריאה בשבת

מחלקות, המחלקה string ואופרטור אינדקס

הופק מתוך תקיית התואר של שקד

uni-folder.vercel.app

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

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;
}

מהתוכנית הזו יודפס תחילה X::X():1X::X():1X::X():1 ואז X::X():2X::X():2X::X():2 ואז Y::Y()Y::Y()Y::Y() ואז Y::∼Y()Y::\sim Y()Y::∼Y() ואז X::∼X():2X::\sim X(): 2X::∼X():2 ואז X::∼X():1X::\sim X():1X::∼X():1

בואו נממש מחסנית קטנה אשר יש לה גודל מקסימלי.
הנה Stack.hStack.hStack.h:

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.cppStack.cppStack.cpp:

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; יוצרת אובייקט חדש מהמחלקה AAA באמצעות בנאי העתקה.
  • השורה a = b; משתמשת באופרטור ההשמה של המחלקה AAA.

שלושת הגדולים - Big three

בין בנאי ההעתקה, ההורס ואופרטור ההשמה הקיים קשר: אם צריך לממש אחד מהם, בדרך כלל צריך לממש את שלושתם..
איך יודעים מתי צריך לממש את הפונקציות האלו?

  • אם מעורבים מצביעים במחלקה בדרך כלל יש צורך בפונקציות האלו
  • אם יש שימוש ב-newnewnew ו-deletedeletedelete
  • אם מתבצעות הקצאות של משאבים אחרים (למשל קבצים)

המחלקה stirngstirngstirng

אנחנו נראה מימוש מסויים של המחלקה stringstringstring וזה לאו דווקא האחד שהמהדר משתמש בו ב-C++17C++17C++17 או בכל שפה אחרת.

נסתכל תחילה על דוגמה של שימוש:

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;
}

לאילו שדות נדרש:

  • המערך char  ∗char\; *char∗
  • אורך המערך הנ"ל

האם צריך בנאי העתקה, אופרטור השמה והורס? ברור
אילו פונקציות נוספות נוכל לממש? נראה כבר בקוד עצמו.

נתחיל בלבנות את קובץ ה-.h.h.h

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);

כעת נבנה את .cpp.cpp.cpp

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;
}

איטרטורים(אופרטור אינדקס)

איטרטורים הם פשוט פויינטר למבנה נתונים אשר מאפשר לנו לבצע מעבר סדרתי על דברים כמו רשימה מקושרת או מחסנית או קבוצה.
זה מאפשר לנו לעבור על כל האיברים במבנה הנתונים שלנו ללא תלות במימוש שלו או במה באמת יש בו.

דוגמה

עבור קבוצה עם האיברים {1,2,3}\left\{ 1,2,3 \right\}{1,2,3}. נרצה לעבור על כולם ולהדפיס אותם. באמצעות איטרטורים נעשה זאת כך:

Set s1;
// s1 = {1,2,3}
Set::Iterator it = s1.begin();
cout << (*it) << endl; // prints 1
++it;
cout << (*it) << endl; // prints 2

איטרטור הוא פשוט מחלקה אשר בה יש:

  • אופרטור ++++++ כ-prefixprefixprefix כדי לקדם את האיטרטור כך שיצביע לאיבר הבא.
  • אופרטור ∗*∗ כדי לגשת לאיבר עליו מצביע האיטרטור - בדרך כלל מחזיר רפרנס כדי לאפשר שינוי של האיבר.
  • אופרטור !=!=!= כדי להשוות בין איטרטורים ובפרט כדי לבדוק שלא הגענו לסוף המבנה.

ב+C++C++C++, אם למחלקה יש איטרטור המקיים את הנ"ל, נוכל, במקום לכתוב את:

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;
}