מחלקות ועצמים
(תכנות מונחה עצמים)
12.1 מבוא
תכנות מונחה עצמים הוא התכנות הבסיסי שעליו נבנתה שפת C#, וזוהי גישה בתכנות. גישה זו מחקה את המציאות ואת העולם האמיתי שבו אנו חיים, שבו קיימים דברים רבים (עצמים) הפועלים זה על זה או לבדם, ומכך נוצרים אירועים ותוצאות. למשל, העצם אדם פועל עם העצם מכונת קפה ועם העצם קפסולות קפה, ומכך נוצר למשל ספל אספרסו. לכן תוכניות C# נבנות כך שהן מכילות עצמים רבים (objects) הפועלים יחד ומשפיעים זה על זה לפי פעולות ולפי תוכנית ראשית הקובעת זאת. העצמים, הפעולות והתוכנית הראשית נקבעים לפי מטרת המתכנת, וכך אנו מקבלים הדמיה של העולם האמיתי ויכולים לנהל פרויקטים ודרישות שמביאים אותנו אל מטרה מוגדרת.
תכנות מונחה עצמים (OOP) נחשב תכנות מודרני ומתקדם, בעל יעילות וגמישות גבוהות יותר, יש בו יותר אפשרויות והוא ברור יותר. אחת התכונות החשובות ביותר שלו היא שהוא מאפשר לנו להתבסס על חלוקת המשימות ועל פיצולן ולאחר מכן על השילוב, ולכן זהו תכנות בעל יכולת לעבודה משולבת.
המונחים הבסיסיים בגישת תכנות זו הם:
- מחלקה (class).
- עצם (object).
- פעולה בונה או בנאי (constructor).
- פעולות גישה (setters and getters).
- תכונות וקבועים (attributes).
- פעולות או שיטות (methods).
- אופרטור הנקודה . (operator dot).
- המפנה this.
- הרשאות גישה (access modifier).
- העמסה (Overload). (*)
- כימוס (Encapsulation).
12.2 המחלקה והעצם
נתחיל בהיכרות עם המחלקות (classes) ועם העצמים (objects)
12.2.1 המחלקה (class)
אפשר להתייחס למחלקה כאל קבוצה (או מסגרת כללית), והיא הגדרה כללית ליצירת עצמים השייכים למסגרת זו.
דוגמאות:
- המחלקה יכולה להיות בעלי החיים, ואז כל עצם השייך למחלקה זו הוא בעל חיים (עצם).
- המחלקה יכולה להיות המכוניות, ואז כל עצם השייך למחלקה זו הוא מכונית (עצם).
- המחלקה יכולה להיות המורים, ואז כל עצם השייך למחלקה זו הוא מורה (עצם).
- המחלקה יכולה להיות התלמידים, ואז כל עצם השייך למחלקה זו הוא תלמיד (עצם).
- המחלקה יכולה להיות הכתובות, ואז כל עצם השייך למחלקה זו הוא כתובת (עצם).
- המחלקה יכולה להיות הנקודות על צירים, ואז כל עצם השייך למחלקה זו הוא נקודה (עצם).
לכל מחלקה חייבים להיות הדברים הבאים:
- התכונות והקבועים: אלה הנתונים והמשתנים שיהיו לעצמים ממחלקה זו.
- הפעולות: אלה פעולות השייכות לעצמים שיהיו ממחלקה זו.
- שם התלמיד (משתנה המכיל נתונים מסוג string)
- מספר הזהות של התלמיד (משתנה המכיל נתונים מסוג int)
- ממוצע התלמיד (משתנה המכיל נתונים מסוג double)
- ציוני התלמיד (משתנה המכיל נתונים מסוג מערך מספרים שלמים int[ ]).
- הכיתה (משתנה המכיל נתונים מסוג int)
- המגמה (משתנה המכיל נתונים מסוג string)
ואילו הפעולות במחלקת התלמידים יכולות להיות:
- יצירת דוח ציונים.
- שינוי הכיתה (מעבר מכיתה לכיתה).
- חישוב הממוצע.
- עדכון ציון.
- הדפסת דוח פרטי התלמיד.
- שם המוצר (משתנה המכיל נתונים מסוג string)
- מספר הקוד של המוצר (משתנה המכיל נתונים מסוג int)
- מחיר המוצר (משתנה המכיל נתונים מסוג double)
- כמות המוצר הקיימת בחנות. (משתנה המכיל נתונים מסוג int)
- שם החברה שמייצרת מוצר זה. (משתנה המכיל נתונים מסוג string)
- סיווג המוצר (משתנה המכיל נתונים מסוג string).
ואילו הפעולות במחלקת המוצרים בחנות יכולות להיות:
- יצירת דוח על המוצר.
- פעולת התראה כשמוצר זה עומד להיגמר.
- חישוב המחיר.
- הנחה או תוספת.
- פעולות מבצעים.
- פעולות מכירה וקנייה של המוצר.
12.2.2 העצם (object)
זהו קטע קוד השייך למחלקה מסוימת, הכולל נתונים ומידע של עצם זה וכולל פעולות שעצם זה יכול לבצע או שמתבצעות על נתוניו. עצם מסוג מחלקה מסוימת נוצר באמצעות פעולה של יצירת עצם (יצירת מופע) של מחלקה זו.
כשיוצרים עצם חדש מסוג מחלקה מסוימת, יש לעצם זה התכונות והנתונים של מחלקה זו וגם כל הפעולות של מחלקה זו.
12.2.3 כתיבת המחלקה וכתיבת העצם בשפת C#
נתחיל בהגדרת המחלקה בשפת C#:
נגדיר את המחלקה באמצעות המילה class, והיא מילה שמורה בשפה.
class שם המחלקה
{
גוף המחלקה והוא מכיל
את הגדרת התכונות והפעולות
}- נציב את הגדרת המחלקות לפני הפעולה הראשית Main, וכן אפשר גם אחריה.
- בתוך המחלקות מגדירים תחילה את התכונות והקבועים ואחר כך כותבים פעולות למחלקה זו.
12.2.4 הרשאות גישה
- יש לכתוב הרשאות גישה לתכונות ולפעולות שבתוך המחלקה.
הרשאת הגישה יכולה להיות:
- public פירושה שאפשר לגשת לתכונה או לפעולה זו מכל מקום באופן כללי, גם מחוץ לקטע הקוד, הבלוק { } של מחלקה זו. הדבר מאפשר לעצמים שנוצרו ממחלקה זו לגשת לתכונות ולפעולות אלה, להשתמש בהן ולהשפיע עליהן.
- private פירושה שאפשר לגשת לתכונה או לפעולה זו רק מתוך קטע הקוד, הבלוק { }, ואיננו יכולים לפנות לתכונות מחוץ לבלוק זה. הדבר אינו מאפשר לעצמים שנוצרו ממחלקה זו לגשת לתכונות ולפעולות אלה, להשתמש בהן ולהשפיע עליהן ישירות.
12.2.5 הפעולה הבונה (בנאי)
- מומלץ לכתוב פעולה בונה כדי ליצור עצמים ממחלקה זו עם תכונות התחלתיות:
רצוי שהפעולות שנבנה יכללו פעולת בנייה (הפעולה הבונה), המאפשרת לנו ליצור עצמים ממחלקה זו.
שמה של הפעולה הבונה זהה בדיוק לשם המחלקה (אותו שם של המחלקה), והיא הפעולה שבאמצעותה אפשר ליצור בכל פעם עצמים חדשים ממחלקה זו. הפעולה הבונה יכולה לקבל בין סוגריה ערכים לתכונות (לכולן או לחלקן) כדי לאחסן אותם בתכונות של עצם זה כתכונות התחלתיות של העצם ביצירתו, ויכולה גם לא לקבל ערכים כלל, לפי מה שאנו מחליטים בתכנות.
בהגדרת הפעולה הבונה, הפעולה חייבת להיות public, כלומר כולם יכולים להשתמש בה, וכן אין לכתוב את המילה void לפני הגדרת שמה, שהוא שם המחלקה.
אם פעולה בונה זו אינה קיימת, קיימת פעולה בונה של ברירת המחדל מן השפה, ובה ערכי התכונות של העצם הם ערכי אפס.
12.2.6 פעולות הגישה Set ו-Get
- מומלץ לכתוב פעולות גישה לתכונות המחלקה.
פעולות הגישה הן פעולות המעניקות לעצם גישה לתכונותיו, שהגדרנו קודם כ-private, ולכן אי אפשר לגשת לתכונות אלה ישירות דרך העצם אלא באמצעות פעולות גישה, והרשאת הגישה של פעולות הגישה חייבת להיות public
פעולות הגישה משפיעות על תכונות העצם ומאפשרות לנו לקרוא אותן. יש שני סוגים של פעולות גישה:
- פעולות גישה שקוראות ערכים (נותנות לנו ערכים) ונקראות Geters; הן פעולות שקוראות ונותנות לנו את ערכי התכונות של העצם.
- פעולות גישה שמציבות ערכים (משנות ערכים) ונקראות Seters; הן פעולות שמשפיעות על ערכי התכונות של העצם ויכולות לשנות אותם.
- אפשר לכתוב גם פעולות אחרות בתוך המחלקה, כמו פעולות הדפסת דוחות ....
נבין את כל זאת מן הדוגמה הבאה:
נבנה מחלקה Car, מכונית, המאפשרת לנו ליצור עצמים מסוג מכונית, כלומר ליצור מכונית. תכונות המחלקה הן: סוג המכונית, מחיר המכונית וצבע המכונית. נבנה פעולה בונה כדי לקבל מופע של עצם חדש (מופע של מכונית), ונבנה פעולות גישה Geters ו-Seters, וכן נבנה פעולה שמדפיסה דוח על המכונית.
class Car
{
// התכונות והקבועים
private string kind; // תכונת סוג המכונית
private int price; // תכונת מחיר המכונית
private string color; // תכונת צבע המכונית
// פעולה בונה
public Car(string kind, int price,string color)
{
this.kind = kind;
this.price = price;
this.color = color;
}
// ****************** פעולות גישה
// ------------- Seters ----------------------------
// Set to kind
public void SetKind(string kind)
{
this.kind = kind;
}
// Set to price
public void SetPrice(int price)
{
this.price = price;
}
// Set to color
public void SetColor(string color)
{
this.color = color;
}
// ------------- Geters ---------------------------
// Get to kind
public string GetKind( )
{
return this.kind;
}
// Get to price
public int GetPrice( )
{
return this.price;
}
// Get to color
public string GetColor( )
{
return this.color;
}
// פעולות אחרות
public void details()
{
Console.WriteLine("The Car is {0} {1}. it worth {2} NIS",kind,color,price);
}
}כפי שאנו רואים, כל תכונות המחלקה (הסוג, המחיר, הצבע) הוגדרו עם הרשאת גישה private, כדי שהגישה לתכונות אלה תהיה רק דרך קטע הקוד (הבלוק) עצמו של כל עצם, ולא תהיה אפשרות לגשת לתכונות אלה ישירות מן העצם שאנו יוצרים.
לכן, כדי לשנות תכונות אלה או לקרוא אותן, כתבנו פעולות Set ו-Get שהוגדרו כ-public, והן שמאפשרות לנו ולעצם לגשת לתכונות אלה (הסוג, המחיר, הצבע) מכל מקום בתוכנית.
השתמשנו במילה השמורה this עם הנקודה כדי להתייחס אל תכונת המחלקה (כלומר תכונת העצם).
הפעולה הבונה התחילה במילה public ומיד אחריה שם המחלקה, Car, וכתבנו אותה כדי לתת לכל התכונות ערכים התחלתיים.
פעולות Set מציבות בתכונות המחלקה (בתכונות העצם שנוצר ממחלקה זו) את הפרמטרים שהפעולה מקבלת, וזאת באמצעות this המתייחס אל תכונת המחלקה. אנו משתמשים ב-this כדי להבדיל בין תכונת המחלקה לבין הפרמטר שאנו מקבלים, מפני שיש להם (או עשוי להיות להם) אותו שם.
המילה this מתייחסת אל העצם, והיא הפניה אל העצם שניצור או אל העצם שמפעיל את הפעולה. בלי שימוש ב-this יהיה לנו בלבול בין תכונת העצם לבין הפרמטר, ואם נכתוב למשל kind=kind;, הפעולה שתתבצע היא הצבת ערך הפרמטר kind בעצמו ולא בתכונת העצם, וזה לא מה שאנו רוצים בתכנות המחלקה. לכן משתמשים במילה this כדי לציין שהכוונה לתכונת העצם (התכונה שנמצאת במחלקה) ולא לפרמטר.
בפעולות Get חובה להציב את הפקודה return, כדי שתחזיר לנו את התכונה כדי שנקרא אותה ותיתן לנו אותה. כמו כן, פעולות Get אינן מקבלות פרמטרים.
כך, באמצעות פעולות הגישה (Set ו-Get) אפשר לגשת לתכונות המחלקה (ולכן לתכונות העצם).
הפעולה האחרונה שכתבנו היא פעולה שמדפיסה את פרטי העצם. (פרטי המכונית).
אם נמחק את פעולת ה-Set של תכונה מסוימת, נמנע את שינויה ואת הכתיבה לתכונה זו, כי היא private, וכן אם נמחק את פעולת ה-Get של תכונה מסוימת, נמנע את קריאתה ואת הגישה אליה.
כעת נלמד כיצד מגדירים עצם ונחיל זאת על הדוגמה הקודמת:
הגדרת עצם נעשית באמצעות הכרזה עליו מסוג המחלקה הנדרשת. המחלקה שלנו היא Car, כלומר עלינו לכתוב את שם העצם ואת סוגו (מחלקתו) כך:
שם המחלקה שם העצם ;
כדי לבנות עצם זה (חובה לבנות אותו, כלומר ליצור לו מופע עם תכונות התחלתיות לפי המחלקה, ולא רק להגדיר אותו) נשתמש בשם העצם עם הפעולה new, שראינו כמוה קודם במחלקה Random, כך:
שם העצם = new שם המחלקה( פרמטרים לפי התכונות בפעולה הבונה שבנינו);
אפשר לבצע את ההגדרה ואת הבנייה בשורה אחת, כך:
שם המחלקה שם העצם = new שם המחלקה(פרמטרים לפי התכונות בפעולה הבונה שבנינו);
כך נוצר עצם חדש (מופע של עצם חדש), וכאן השתמשנו בפעולה הבונה שכתבנו במחלקה, בכך ששלחנו ערכים לתכונות (פרמטרים) בעת השימוש ב-new
נראה כעת כיצד נכתוב בתוכנית הראשית שלנו הגדרה ובנייה של שתי מכוניות, נדפיס את פרטי שתי המכוניות שיצרנו, ואז נשנה את מחיר המכונית הזולה יותר כך שמחירה יהיה כמו המכונית היקרה יותר (מחיר שתי המכוניות יהיה אותו סכום, השווה למחיר הגבוה יותר), נשנה את צבע המאזדה לכחול ונדפיס שוב את פרטי המכוניות לאחר השינוי.
הערכים ההתחלתיים של המכוניות הם:
המכונית הראשונה תהיה סקודה, מחירה 100000 וצבעה שחור, ואילו השנייה תהיה מאזדה, צבעה אדום ומחירה 30000
static void Main(string[] args)
{
Car c1, c2; // עצם ששמו c1 ועצם נוסף ששמו c2
c1 = new Car("Skoda",100000,"Black");
c2 = new Car("Mazda",30000,"Red");
Console.WriteLine("*****");
c1.details();
c2.details();
Console.WriteLine("** After updating the price to the cars**");
if (c1.GetPrice() > c2.GetPrice())
c2.SetPrice(c1.GetPrice());
else
c1.SetPrice(c2.GetPrice());
c2.SetColor("Blue");
Console.WriteLine("#####");
c1.details();
c2.details();
Console.WriteLine("#####");
}הגדרנו את המכונית הראשונה c1 ואת המכונית השנייה c2 ובנינו אותן באמצעות הפעולה הבונה שכתבנו קודם, והיא:
// פעולה בונה
public Car(string kind, int price,string color)
{
this.kind = kind;
this.price = price;
this.color = color;
}פעולה בונה זו דורשת מאיתנו לשלוח 3 פרמטרים בעת בניית עצם (מכונית) מן המחלקה Car, והם מסוג מחרוזת string, מסוג מספר שלם int ומחרוזת string, ובסדר המוצג בהגדרתה (שאנו קובעים כמתכנתים).
לכן הגדרנו את שתי המכוניות ובנינו אותן עם המאפיינים ההתחלתיים הנתונים, כך:
Car c1, c2; // עצם ששמו c1 ועצם נוסף ששמו c2
c1 = new Car("Skoda",100000,"Black");
c2 = new Car("Mazda",30000,"Red");נשים לב שבמצב הנוכחי איננו יכולים לבנות עצמים ולשלוח יותר משלושה פרמטרים או פחות משלושה, או בסדר השונה מן הסדר שלמעלה, מפני שקיימת הגדרה של פעולה בונה אחת, אך בהמשך נוכל לעשות זאת אחרי שנלמד את עקרון ההעמסה, שפירושו כתיבה של יותר מפעולה בונה אחת.
אופרטור הנקודה כאן מאפשר לנו לפנות לתכונות של כל עצם או לפעולות העצם שהוגדרו במחלקה, וזאת אחרי שנציב את הנקודה אחרי שם העצם.
שם העצם.תכונה
שם העצם.הפעולה( ) ;
כדי להפעיל את הפעולה details השייכת למחלקה, חייב העצם שיצרנו להפעיל אותה, והיא פעולה שאינה מקבלת פרמטרים, לכן הפעלנו אותה באמצעות אופרטור הנקודה . dot לכל עצם, והיא ביצעה את הנדרש לתכונות של כל עצם בנפרד.
c1.details(); c2.details();
פעולה זו תגרום להדפסת פרטי כל מכונית כפי שהגדרנו את הפעולה details, שהייתה:
// פעולות אחרות
public void details()
{
Console.WriteLine("The Car is {0} {1}. it worth {2} NIS",kind,color,price);
}לאחר מכן יש לנו שימוש בפעולות הגישה Get ו-Set, כאשר c1.GetPrice() מחזירה לנו את התכונה Price של העצם c1, כלומר את מחיר המכונית c1, מפני שהפעולה GetPrice( ) הייתה כך:
public int GetPrice( )
{
return this.price;
}וכאן this יתייחס לתכונה של העצם שהפעיל פעולה זו, והוא c1
וכאשר c2.GetPrice() מחזירה לנו את התכונה Price של העצם c2, כלומר את מחיר המכונית c2.
ואילו שינוי מחיר המכונית נעשה באמצעות הפעולה SetPrice(int price), שאליה חובה לשלוח ערך שלם כפרמטר כדי שתציב אותו בתכונה המתאימה.
if (c1.GetPrice() > c2.GetPrice())
c2.SetPrice(c1.GetPrice());
else
c1.SetPrice(c2.GetPrice());אם מחיר המכונית הראשונה גדול יותר, נפעיל את הפעולה SetPrice של המכונית השנייה ונציב בה את המחיר הגבוה יותר, שהוא מחיר המכונית הראשונה, שאנו מקבלים באמצעות c1.GetPrice(), וזה מספר שלם מפני שהיא מחזירה את התכונה Price של העצם, והיא מסוג int, כלומר: c2.SetPrice(c1.GetPrice());
ואילו אם מחיר המכונית הראשונה אינו גדול יותר (כלומר מחיר המכונית השנייה הוא הגדול יותר), נשנה את מחיר המכונית הראשונה למחיר השווה למכונית השנייה, כלומר: c1.SetPrice(c2.GetPrice());
כדי לשנות את צבע המכונית השנייה עלינו להפעיל את פעולת הגישה לצבע של העצם c2, והיא הפעולה SetColor, שאליה נשלח פרמטר מסוג מחרוזת string, שהוא צבע המכונית.
c2.SetColor("Blue");לאחר מכן נדפיס מחדש את פרטי המכוניות האלה כדי לראות שתכונותיהן השתנו, וזאת בעזרת הפעולה הבונה ופעולות הגישה Set ו-Get
ויופיע לנו על המסך הפלט הבא:
***** The Car is Skoda Black. it worth 100000 NIS The Car is Mazda Red. it worth 30000 NIS ** After updating the price to the cars** ##### The Car is Skoda Black. it worth 100000 NIS The Car is Mazda Blue. it worth 100000 NIS ##### Press any key to continue . . .
הערות:
- איננו יכולים לפנות ישירות דרך העצם לתכונותיו בדרך הבאה:
c2.color = "Blue";, מפני שהתכונה color הוגדרה כ-private, אבל אילו הגדרנו אותה public, הדבר היה אפשרי. כאן טמונה חשיבות הרשאת הגישה private, שבה לא מתאפשר לשנות תכונות אלה מן העצם עצמו ללא פיקוח, והפיקוח נעשה באמצעות שימוש בפעולות Get ו-Set. - את הפעולות הגדרנו public כדי שיהיו זמינות לעצם מכל מקום.
- אם נרצה לשמור על תכונה בלי אפשרות לשנותה, נוכל למחוק את פעולת ה-Set של תכונה זו, המוגדרת private, ואז לא תהיה אפשרות להציב ערכים בתכונה זו, אלא באמצעות הפעולות הבונות, אם הוגדרה הצבת ערכים לתכונה זו בפעולה הבונה.
- בניית העצם נעשית פעם אחת בלבד לכל עצם, ואי אפשר להשתמש שוב בפעולה הבונה לעצם שכבר נוצר כדי לעדכן את ערכי התכונות.
12.3 הפעולות הבונות
אם לא נכתוב פעולה בונה, קיימת בשפה פעולה בונה של ברירת המחדל. זוהי פעולה בונה שבונה את העצם עם כל תכונות המחלקה בלי לתת לתכונות ערך התחלתי מסוים, אלא ניתן לכל התכונות ערך התחלתי של אפס.
אז, כדי לבנות עצם ולהגדיר אותו, נוכל לכתוב את הדבר הבא:
שם המחלקה שם העצם = new שם המחלקה( ) ;
נשים לב שהסוגריים בפעולה הבונה ריקים ואיננו שולחים אליה ערכים.
אם כתבנו פעולה בונה מסוימת, המחשב מבטל את הפעולה הבונה של ברירת המחדל, ואז איננו יכולים ליצור עצמים בלי לשלוח ערכים לפי הפעולה הבונה שבנינו.
מכאן אפשר לומר שאנו יכולים לא לכתוב פעולה בונה, אבל ביצירת עצם חדש (מופע של עצם) נשתמש בפעולה new ובשם המחלקה בלי לשלוח ערכים כלשהם בסוגריים, ואסור לשלוח ערכים כפרמטרים לעצם דרך הפעולה הבונה של ברירת המחדל מן השפה. אבל אם בנינו פעולה בונה וכתבנו בה שהעצם החדש מקבל את התכונות כפרמטרים, עלינו לשלוח אותן, ואז אסור לכתוב סוגריים ריקים ולא לשלוח ערכים.
אפשר לכתוב מחדש בעצמנו את הפעולה הבונה של ברירת המחדל אם נרצה בה, בנוסף לפעולה הבונה שכתבנו, ואפשר לכתוב פעולות בונות נוספות (יותר מדרך אחת לבנות עצם וליצור אותו), כך שכל פעולה שונה מהאחרת בפרמטרים שתקבל כדי ליצור עצם חדש.
כתיבת פעולות בונות רבות נקראת העמסה, וזה עיקרון שפועל גם על הפעולות.
כדוגמה לכך נגדיר מחלקה של תלמיד תיכון באמצעות שתי תכונות, השם והכיתה, כך:
class Student
{
private string name;
private int the_class;
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student();
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s2.SetName("Abd");
s1.SetClass(11);
s2.SetClass(10);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}כפי שאנו רואים, לא כתבנו פעולות בונות כלל ולא כתבנו פעולות כלל מלבד פעולות הגישה (Set ו-Get). לכן בתוכנית, כשרצינו ליצור עצם, הגדרנו אותו באמצעות הפקודה new וכתבנו את שם המחלקה ולא שלחנו פרמטרים כלל לעצם החדש, כלומר השארנו את הסוגריים ריקים.
אנו רואים בתוכנית שיצרנו שני עצמים חדשים, s1 ו-s2, ותכונותיהם הן תכונות אפס הנובעות מן הפעולה הבונה של ברירת המחדל שבשפה. לכן כשנדפיס את פרטי שני העצמים האלה, ערכי תכונותיהם יהיו אפס.
אחר כך, באמצעות פעולות הגישה, נציב ערכים לתלמיד הראשון (שמו המאם והוא בכיתה 11) ולתלמיד השני (שמו עבד והוא בכיתה 10), נדפיס את התכונות ונראה ששני העצמים קיבלו תכונות אלה.
כפי שמוצג בפלט הבא:
from class: 0 from class: 0 Hammam from class: 11 Abd from class: 10
כעת נוסיף פעולה בונה שמקבלת את כל התכונות, ונראה שהפעולה הבונה של ברירת המחדל תתבטל ולא נוכל להשתמש בה.
הפעולה הבונה היא:
// פעולה בונה
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}וכך יהיה לנו:
class Student
{
private string name;
private int the_class;
// פעולה בונה
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student(); // ERROR : We can`t
Student s2;
s2 = new Student(); // Error: We can`tכאן תהיה לנו שגיאת תכנות בתוכנית, בעת יצירת עצם חדש באמצעות new, מפני שחובה לשלוח פרמטרים כפי שהגדרנו את הפעולה הבונה. לכן אפשר לכתוב ולתקן את התוכנית כך:
static void Main(string[] args)
{
Student s1 = new Student("",0);
Student s2;
s2 = new Student("Abd",10);
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}לתלמיד הראשון שלחנו ערכי אפס, ויכולנו לשלוח ערכים התחלתיים לתכונות, ולכן נזקקנו לשימוש בפעולת הגישה set כדי לשנות תכונות אלה. ואילו לתלמיד השני שלחנו ביצירתו את הערכים ההתחלתיים הנכונים לתכונות, והם שמו וכיתתו.
ובעת ההרצה יופיע:
from class: 0 Abd from class: 10 Hammam from class: 11 Abd from class: 10
אם נרצה להחזיר את הפעולה הבונה של ברירת המחדל, עלינו לכתוב אותה בנוסף לפעולה הבונה שכתבנו, כך שיהיו לנו שתי פעולות בונות ולא רק אחת, ושתי הפעולות הבונות יהיו כך:
// פעולה בונה
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// כתיבה מחדש של הפעולה הבונה של ברירת המחדל כי הוספנו פעולה בונה
public Student()
{
}התוכנית תהיה כך:
class Student
{
private string name;
private int the_class;
// פעולה בונה
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// כתיבה מחדש של הפעולה הבונה של ברירת המחדל כי הוספנו פעולה בונה
public Student()
{
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student("Abd",10);
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}אנו רואים שיצירת העצם הראשון s1 נעשתה באמצעות הפעולה הבונה של ברירת המחדל שכתבנו מחדש, ואילו העצם השני נוצר באמצעות הפעולה הבונה שמקבלת את כל התכונות כפרמטרים
כעת אפשר גם לכתוב פעולה בונה נוספת שמקבלת פרמטר אחד בלבד (למשל השם), ואז אפשר להשתמש בה כדי לבנות עצם ולשלוח רק את השם. ואפשר להוסיף פעולה בונה אחרת שמקבלת פרמטר אחד בלבד (למשל הכיתה), ואז אפשר להשתמש בה כדי לבנות עצם ולשלוח רק את הכיתה.
אפשר גם לכתוב פעולות בונות שנותנות את ערכי התכונות ההתחלתיים (את כולם או את חלקם) מתוך הגדרת הפעולה הבונה (מן המתכנת), ואינן מקבלות אותם מן המשתמש, לא כפרמטרים ולא כתכונות התחלתיות, ואפשר לשנות אותם אחרי יצירת העצם באמצעות פעולות הגישה.
נכתוב פעולה בונה נוספת (למשל מקבלת רק את השם כפרמטר, ולכן התכונה האחרת, הכיתה, תהיה אפס, מפני שכל תכונה שאינה נכללת בפעולה הבונה שאנו כותבים תקבל ערכי אפס, וזה מסביר את הפעולה הבונה של ברירת המחדל שבשפה, שאינה מקבלת פרמטר כלשהו ויש לה סוגריים ריקים) כך:
// פעולה בונה שמקבלת פרמטר אחד בלבד והוא השם
public Student(string name)
{
this.name = name;
}לכן המחלקה תהיה כך:
class Student
{
private string name;
private int the_class;
// פעולה בונה
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// כתיבה מחדש של הפעולה הבונה של ברירת המחדל כי הוספנו פעולה בונה
public Student()
{
}
// פעולה בונה שמקבלת פרמטר אחד בלבד והוא השם
public Student(string name)
{
this.name = name;
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}ואילו התוכנית הראשית שלנו תהיה:
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student("Abd",5);
Student s3 = new Student("Mriam");
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
Console.WriteLine("{0} from class: {1}", s3.GetName(), s3.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
s3.SetClass(3);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
Console.WriteLine("{0} from class: {1}", s3.GetName(), s3.GetClass());
}והפלט יהיה:
from class: 0 Abd from class: 5 Mriam from class: 0 Hammam from class: 11 Abd from class: 5 Mriam from class: 3 Press any key to continue . . .
12.4 דוגמה להרשאות הגישה ולפעולות הגישה (Set ו-Get) ולחשיבות הפעולה הבונה
נבנה מחלקת מלבנים, ותכונות המחלקה יהיו:
- האורך: מספר עשרוני (double).
- הרוחב: מספר עשרוני (double).
נגדיר את התכונות בהתחלה כ-public ונראה שאפשר ליצור מלבנים ולגשת לתכונות כדי להציב בהן ערכים או לקרוא אותם מן התוכנית הראשית (מחוץ למחלקה). אפשר לעשות זאת מפני שהרשאת הגישה של התכונות היא public, כלומר אפשר לגשת לתכונות מכל מקום בלי הגבלה.
בהתחלה לא נגדיר פעולה בונה (נסתמך על הפעולה הבונה של ברירת המחדל) ונבנה את הפעולות הבאות:
- פעולה שמחשבת את שטח המלבן. (מחזירה את השטח double)
- פעולה שמחשבת את היקף המלבן. (מחזירה את ההיקף double)
- פעולה שמציירת את המלבן. (אינה מחזירה ערכים)
- פעולה שקובעת אם המלבן הוא ריבוע (מקרה פרטי) או לא. (מחזירה ערך בוליאני true אם המלבן ריבוע, או מחזירה false אם המלבן אינו ריבוע)
public class Rectangle
{
public double length, width;
public double Area()
{
return this.length*this.width;
}
public double Perimeter()
{
return 2 * (this.length + this.width);
}
public void draw()
{
for(int i=1;i<=this.length;i++)
{
for (int j = 1; j <= this.width; j++)
Console.Write("*");
Console.WriteLine();
}
}
public bool IsSquare()
{
if (this.length == this.width)
return true;
else
return false;
}
}אין לנו צורך לכתוב פעולות גישה, מפני שאפשר לגשת לתכונות באמצעות אופרטור הנקודה (dot): כותבים את שם העצם, אחריו נקודה, וכותבים את התכונה ומתייחסים אליה כאל משתנה שאפשר לקרוא אותו ולשנות את ערכו מכל מקום, כפי שנראה בתוכנית הבאה, שבה נבנה שני עצמים (שני מלבנים r1 ו-r2) ונקבע את ממדיהם כך שאורך המלבן הראשון 5 ורוחבו 5, ואילו אורך השני 4 ורוחבו 2.
static void Main(string[] args)
{
double area, d;
Rectangle r1 = new Rectangle();
//Rectangle r2 = new Rectangle(4,2); שגיאה ERORR
Rectangle r2 = new Rectangle();
r1.length = 5;
r1.width = 5;
r2.length = 4;
r2.width = 2;
Console.WriteLine("rectangle 1 is: {0}X{1}", r1.length,r1.width);
Console.WriteLine("rectangle 2 is: {0}X{1}", r2.length, r2.width);
area = r1.Area();
d = r1.Perimeter();
Console.WriteLine("The area of first rectangle is: {0}",area,d);
Console.WriteLine("The perimeter of second rectangle is:{0}",r2.Perimeter());
Console.WriteLine("-------- NOW WE WILL DRAW THE RECTANGLES --------");
Console.WriteLine("Rectangle 1: ");
r1.draw();
Console.WriteLine("Rectangle 2: ");
r2.draw();
if(r1.IsSquare())
Console.WriteLine("Rectangle 1 is also SQUARE");
if(r2.IsSquare())
Console.WriteLine("Rectangle 2 is also SQUARE");
}הערות חשובות:
- לא ראוי שנגדיר את התכונות כ-public, כדי שיהיו מוגנות מפני גישה אליהן.
- מכיוון שלא כתבנו את הפעולה הבונה שמקבלת את תכונות העצם כפרמטרים, השתמשנו בפעולה הבונה של ברירת המחדל (default constructor), ולכן נזקקנו להזין את ערכי התכונות לכל עצם, תכונה אחר תכונה. ואם התכונות private, נצטרך להשתמש בפעולות Set לכל תכונה בנפרד, מה שעלול להאריך את התוכנית ולקחת זמן רב. לכן מומלץ לכתוב פעולה בונה (גם אם פעולת ברירת המחדל נמחקה – שכן אנו יכולים לכתוב אותה או לוותר עליה)
התוצאה לאחר ההרצה:
rectangle 1 is: 5X5 rectangle 2 is: 4X2 The area of first rectangle is: 25 The perimeter of second rectangle is:12 -------- NOW WE WILL DRAW THE RECTANGLES -------- Rectangle 1: ***** ***** ***** ***** ***** Rectangle 2: ** ** ** ** Rectangle 1 is also SQUARE Press any key to continue . . .
נשנה את התכונות מ-public ל-private, ולכן חובה לכתוב פעולות גישה, מפני שבלי פעולות הגישה תהיה לנו Error בפקודות שבהן אנו ניגשים לתכונות ישירות. נכתוב גם פעולה בונה שמקבלת את התכונות.
נקבל את המחלקה בצורה זו:
public class Rectangle
{
private double length, width;
public Rectangle(double length,double width) // פעולה בונה
{
this.length = length;
this.width = width;
}
//Seters
public void SetLength(double length)
{
this.length = length;
}
public void SetWidth(double w) // we can call the parameter as we want
{
this.width = w;
}
//Geters
public double GetLength()
{
return this.length;
}
public double GetWidth()
{
return this.width;
}
//Methods
public double Area()
{
return this.length*this.width;
}
public double Perimeter()
{
return 2 * (this.length + this.width);
}
public void draw()
{
for(int i=1;i<=this.length;i++)
{
for (int j = 1; j <= this.width; j++)
Console.Write("*");
Console.WriteLine();
}
}
public bool IsSquare()
{
if (this.length == this.width)
return true;
else
return false;
}
}נכתוב את התוכנית הקודמת עם כמה שינויים כדי שתתאים להרשאות הגישה ולפעולה הבונה. זאת מפני שעלינו לשים לב לשינוי כמה מן הפקודות הקודמות, שיהיו כעת שגיאת תכנות בגלל השינויים שערכנו בהרשאות הגישה, והן אלה:
//Rectangle r1 = new Rectangle(); Error //Rectangle r2 = new Rectangle(); Error //r1.length = 5; Error //r1.width = 5; Error //r2.length = 4; Error //r2.width = 2; Error
ואילו התוכנית הראשית לאחר השינויים תהיה:
static void Main(string[] args)
{
double area, d;
Rectangle r1 = new Rectangle(5,5);
Rectangle r2 = new Rectangle(4,2); // it is Correct now
Console.WriteLine("rectangle 1 is: {0}X{1}", r1.GetLength(),r1.GetWidth());
Console.WriteLine("rectangle 2 is: {0}X{1}", r2.GetLength(), r2.GetWidth());
area = r1.Area();
d = r1.Perimeter();
Console.WriteLine("The area of first rectangle is: {0}",area,d);
Console.WriteLine("The perimeter of second rectangle is: {0}", r2.Perimeter());
Console.WriteLine("-------- NOW WE WILL DRAW THE RECTANGLES --------");
Console.WriteLine("Rectangle 1: ");
r1.draw();
Console.WriteLine("Rectangle 2: ");
r2.draw();
if(r1.IsSquare())
Console.WriteLine("Rectangle 1 is also SQUARE");
if(r2.IsSquare())
Console.WriteLine("Rectangle 2 is also SQUARE");
}תוצאת ההרצה תישאר כפי שהייתה קודם.
rectangle 1 is: 5X5 rectangle 2 is: 4X2 The area of first rectangle is: 25 The perimeter of second rectangle is: 12 -------- NOW WE WILL DRAW THE RECTANGLES -------- Rectangle 1: ***** ***** ***** ***** ***** Rectangle 2: ** ** ** ** Rectangle 1 is also SQUARE Press any key to continue . . .
12.5 נושאים מתקדמים: (נושאים לבחירה – חלקם נדרש בתוכנית הלימודים של משרד החינוך)
12.5.1 מערך חד-ממדי כתכונה של המחלקה
נסביר את הנושא באמצעות יישום בדוגמה.
דוגמה:
תלמידים (לכל תלמיד שם פרטי ומספר זהות) לומדים במכללה למדעים, ובכל שנה לומד התלמיד 6 מקצועות שונים.
תלמיד נחשב עובר בשנת הלימודים אם הממוצע שלו בשנה (ממוצע ששת המקצועות) גבוה מ-65, וגם לא נכשל ביותר משני מקצועות מתוך ששת המקצועות של שנת הלימודים (כישלון במקצוע הוא ציון נמוך מ-55).
נפתור את הבעיה בשלבים, כך:
(א) כתוב מחלקת תלמיד Student ובה התכונות הבאות:
name - השם מסוג מחרוזת
id - הזהות מסוג מספר שלם
marks – הציונים מסוג מערך מספרים עשרוניים באורך 6.
average - הממוצע מסוג מספר עשרוני.
פתרון:
class Student
{
private string name;
private int id;
private double[] marks = new double[6];
private double avarage;
}(ב) הוסף פעולה בונה שמקבלת רק את שתי התכונות: השם והזהות.
פתרון:
public Student(string name, int id)
{
this.name = name;
this.id = id;
}(ג) הוסף את הפעולות הבאות:
- פעולות גישה:
seters רק לתכונת הציונים (קליטת 6 ציונים למערך).
לא נקלוט את תכונת הממוצע, מפני שהיא מחושבת על סמך תכונת המערך (ציוני המערך).
לא נקלוט את השם או את הזהות בפעולת גישה set, מפני שאנו שולחים אותם ביצירת תלמיד (עצם) חדש.
geters לכל התכונות. (שים לב שאחת התכונות האלה היא מערך).
פתרון:
פעולה לקליטת מערך שלם, והוא תכונה מתכונות העצם.
public void setmarks(double[] marks)
{
this.marks = marks;
}ואילו פעולות get הן כך:
public string getname()
{
return this.name;
}
public int getid()
{
return this.id;
}
public double[] getmarks()
{
return this.marks;
}
public double getavarage()
{
return this.avarage;
}(ד) הוסף את הפעולות הבאות:
- פעולה לחישוב הממוצע על סמך ציוני התלמיד מתכונת מערך הציונים.
- פעולה שבודקת אם ממוצע התלמיד הוא ממוצע עובר או לא (אם הממוצע הוא ממוצע עובר, הפעולה מחזירה true, ואם ממוצע התלמיד אינו ממוצע עובר, הפעולה מחזירה false). ידוע שממוצע עובר הוא כאשר הממוצע גבוה מ-65.
- פעולה שבודקת אם התלמיד עבר או נכשל במקצועות הלימוד: אם התלמיד נכשל ביותר משני מקצועות, הוא נחשב כמי שנכשל במקצועות הלימוד (כישלון במקצוע מסוים הוא ציון נמוך מ-55 במקצוע זה). אם התלמיד נכשל במקצועות הלימוד, על הפעולה להחזיר true, ואם לא נכשל, היא מחזירה false.
פתרון:
public double Average()
{
int i;
double sum = 0;
for (i = 0; i < 6; i++)
{
sum += this.marks[i];
}
this.avarage = sum / 6;
return sum / 6;
}
public bool checkavarage()
{
if (this.avarage <= 65)
return false;
else
return true;
}
public bool checkfails()
{
int cntr=0,i;
for(i=0;i<6;i++)
{
if (this.marks[i] < 55)
cntr++;
}
if (cntr > 2)
return true;
else
return false;
}12.5.2 מערך עצמים
נמשיך בדוגמה כדי להסביר גם נושא זה.
(ה) כתוב תוכנית שמגדירה:
- מערך עצמים מסוג תלמיד באורך N, כאשר N הוא קבוע (אפשר לתת לו כל ערך מספרי שתרצה).
- מערך ציונים באורך 6 תאים, שיכיל את ציוני התלמיד שנקלוט בתוכנית כדי לשלוח אותם כמערך אל פעולת הגישה set של תכונת מערך הציונים של העצם.
- על התוכנית למלא את מערך העצמים (התלמידים) על ידי קליטת שם התלמיד וזהותו כתכונות של העצם, כך שכל עצם יהיה בתא אחד מתאי המערך.
- על התוכנית לקלוט את ציוני התלמידים לתוך המערך (כל תלמיד בנפרד) ואז לשלוח מערך זה כתכונה אל העצם שנמצא במערך התלמידים. (שים לב שמערך התלמידים הוא מערך עצמים)
- על התוכנית לבדוק אם מצבו האקדמי של התלמיד הוא הצלחה (תקין = TKIN) או שמצבו האקדמי הוא כישלון (לא תקין = "MSH TKIN"). הדבר מחייב שימוש בפעולות חישוב הממוצע לכל תלמיד ומילוי תכונת הממוצע אצל כל תלמיד, ומחייב שימוש בפעולות הזמינות לכל תלמיד לגבי מצבו השנתי מבחינת הצלחתו בממוצע השנתי והצלחתו במקצועות הלימוד.
- לבסוף על התוכנית להדפיס דוח על כל תלמיד (שמו וזהותו), מצבו האקדמי (TKIN או MSH TKIN) וממוצעו.
פתרון:
static void Main(string[] args)
{
const int N = 1; // number of students
string name,AS;
int id,i;
double avg;
Student[] students = new Student[N];
for (i = 0; i < N; i++)
{
Console.Write("Give me the name of the {0} student: ",i+1);
name = Console.ReadLine();
Console.Write("Give me the id of the {0} student:",i+1);
id = int.Parse(Console.ReadLine());
students[i] = new Student(name, id);
}
for (int j = 0; j < N; j++)
{
double[] Marks = new double[6];
for (i = 0; i < 6; i++)
{
Console.Write("give me the mark number {0} of the {1} student:",i + 1,j+1);
Marks[i] = double.Parse(Console.ReadLine());
}
students[j].setmarks(Marks);
students[j].Average();
}
for (i = 0; i < N; i++)
{
if(students[i].checkavarage() && !students[i].checkfails())
AS = "TKIN";
else
AS = "MSH TKIN";
Console.WriteLine(students[i].getname() + "[id:" + students[i].getid() + "] --> Academic Situation: " + AS + " Average:" + students[i].getavarage());
}
Console.ReadKey();
}נכתוב כעת את כל המחלקה, הפעולות והתוכנית יחד כקטע אחד, ונקבל את הדבר הבא:
using System;
namespace ConsoleApp50
{
class Student
{
private string name;
private int id;
private double[] marks = new double[6];
private double avarage;
public Student(string name, int id)
{
this.name = name;
this.id = id;
}
public void setmarks(double[] marks)
{
this.marks = marks;
}
public double Average()
{
int i;
double sum = 0;
for (i = 0; i < 6; i++)
{
sum += this.marks[i];
}
this.avarage = sum / 6;
return sum / 6;
}
public bool checkavarage()
{
if (this.avarage <= 65)
return false;
else
return true;
}
public bool checkfails()
{
int cntr=0,i;
for(i=0;i<6;i++)
{
if (this.marks[i] < 55)
cntr++;
}
if (cntr > 2)
return true;
else
return false;
}
public string getname()
{
return this.name;
}
public int getid()
{
return this.id;
}
public double[] getmarks()
{
return this.marks;
}
public double getavarage()
{
return this.avarage;
}
}
class Program
{
static void Main(string[] args)
{
const int N = 1; // number of students
string name,AS;
int id,i;
double avg;
Student[] students = new Student[N];
for (i = 0; i < N; i++)
{
Console.Write("Give me the name of the {0} student:",i+1);
name = Console.ReadLine();
Console.Write("Give me the id of the {0} student:",i+1);
id = int.Parse(Console.ReadLine());
students[i] = new Student(name, id);
}
for (int j = 0; j < N; j++)
{
double[] Marks = new double[6];
for (i = 0; i < 6; i++)
{
Console.Write("give me the mark number {0} of the {1} student:", i + 1,j+1);
Marks[i] = double.Parse(Console.ReadLine());
}
students[j].setmarks(Marks);
students[j].Average();
}
for (i = 0; i < N; i++)
{
if(students[i].checkavarage() && !students[i].checkfails())
AS = "TKIN";
else
AS = "MSH TKIN";
Console.WriteLine(students[i].getname() + "[id:" + students[i].getid() + "] --> Academic Situation: " + AS + " Average:" + students[i].getavarage());
}
Console.ReadKey();
}
}
}12.5.3 כימוס (Encapsulation)
זוהי טכניקה שמטרתה להסתיר את התכונות והפעולות הפנימיות: קובעים שהרשאות הגישה יהיו private, ואז בונים ממשק שימוש המאפשר לתכונות חשובות מסוימות או לפעולות חשובות מסוימות להיות נגישות, באמצעות הגדרת פעולות public או פעולות גישה Set ו-Get. כך המחלקה עוברת כימוס, וכל תכולתה אינה זמינה למשתמשים, אלא רק מה שנקבע בתוכנית.
12.5.4 שליחת עצם כפרמטר לפעולה חיצונית
כפי שלמדנו קודם בפרק הפעולות החיצוניות, פעולה יכולה לקבל פרמטרים מסוגים שונים כקלט שלה, שהיא מקבלת כשקוראים לה ממי שקורא לה ומפעיל אותה.
אפשר לשלוח עצם (שכבר נוצר ויש בו תכונות, פעולות גישה ופעולות השייכות לו) אל פעולה, על ידי שליחת שמו אל הפעולה ברשימת הפרמטרים של הפעולה, אך לפני כן על הפעולה להיות מוגדרת כמי שמקבלת עצם בפרמטרים שלה.
נניח שהגדרנו את המחלקה Book, כלומר כתבנו את הדבר הבא:
class Book
{
.....
}בתוך המחלקה הוגדרו תכונות, פעולה בונה, פעולות גישה ופעולות השייכות לעצמים מן המחלקה Book.
אם נרצה להגדיר פעולה חיצונית בשם ForChildren שמקבלת את העצם כפרמטר ומחזירה למשל ערך בוליאני, נכתוב את הפעולה בצורה הבאה:
static bool ForChildren(Book b)
{
}בעת קריאה לפעולה זו אפשר לשלוח אליה עצם. למשל, אם יש לנו עצם (שכבר נוצר ויש לו תכונות), אפשר לשלוח את שמו אל הפעולה בעת הקריאה לה, למשל:
Book b1 = new Book("The Lion King", 17, true, "L.James");
Book b2 = new Book("C#", 204, true, "Hammam");
if(ForChildren(b1)==true)בדוגמה הבאה יובהר הדבר:
- נבנה מחלקה Book שיש לה התכונות:
- title – מסוג string, מכיל את כותרת הספר.
- pages – מסוג int, מכיל את מספר עמודי הספר.
- color – מסוג bool, מכיל true אם הספר צבעוני, אחרת false.
- author – מסוג string, מכיל את שם מחבר הספר.
- נבנה פעולה בונה ונבנה את כל פעולות הגישה, בנוסף לפעולה השייכת למחלקה ומחזירה מחרוזת ובה פרטי הספר הבאים: (שם הספר, שם מחברו ומספר עמודיו).
- נבנה פעולה חיצונית בשם ForChildren שמקבלת עצמים מסוג Book כפרמטר ומחזירה ערך בוליאני true אם הספר מיועד לילדים, או מחזירה false אם הספר למבוגרים. ידוע שספר ילדים חייב להיות צבעוני ומספר עמודיו חייב להיות קטן מ-50.
פתרון:
class Book
{
private string title;
private int pages;
private bool color;
private string author;
public Book(string title,int pages,bool color,string author)
{
this.title = title;
this.pages = pages;
this.color = color;
this.author = author;
}
public void SetTitle(string title)
{
this.title = title;
}
public void SetPages(int pages)
{
this.pages = pages;
}
public void SetColor(bool color)
{
this.color = color;
}
public void SetAuthor(string author)
{
this.author = author;
}
public string GetTitle()
{
return this.title;
}
public int GetPages()
{
return this.pages;
}
public bool GetColor()
{
return this.color;
}
public string GetAuthor()
{
return this.author;
}
public string BookInfo()
{
return "Book: " + this.title + "(" + this.author + ") " + this.pages + " Pages.";
}
}
static bool ForChildren(Book b)
{
if (b.GetColor() == true && b.GetPages() < 50)
return true;
else
return false;
}- כעת נבנה בתוכנית הראשית שני עצמים (ניצור שני עצמים), ניתן להם ערכים התחלתיים (כדוגמה), נקרא לפעולה החיצונית ForChildren ונשלח אליה כל ספר (כל אחד משני העצמים), ואם הספר מיועד לילדים, עלינו להדפיס את פרטיו החשובים על המסך.
פתרון:
static void Main(string[] args)
{
Book b1 = new Book("The Lion King", 17, true, "L.James");
Book b2 = new Book("C#", 204, true, "Hammam");
if(ForChildren(b1)==true)
Console.WriteLine(b1.BookInfo());
if (ForChildren(b2) == true)
Console.WriteLine(b2.BookInfo());
}דוגמה נוספת:
נתונה המחלקה Clock, המגדירה נתונים מסוג שעון.
public class Clock
{
private int hour; //0-23
private int min; //0-59
public Clock(int hour, int min)
{
this.hour = hour;
this.min = min;
}
public int GetHour()
{
return this.hour;
}
public void SetMin(int min)
{
this.min = min;
}
}(א) השלם את כתיבת פעולות הגישה במחלקה זו.
(ב) כתוב פעולה חיצונית (static) בשם IsEqualClocks. פעולה זו מקבלת שני שעונים (שני עצמים מסוג שעון) כפרמטרים ובודקת אם שני השעונים מראים את אותה שעה.
(ג) בשדה התעופה נוקטים אמצעי בטיחות שלפיהם אסור שתהיה נחיתה של יותר ממטוס אחד באותו זמן. מגדל הפיקוח בודק את זמני הנחיתה של המטוס המגיע לשדה התעופה, ונותן להם אישור נחיתה אם אין זמנים זהים, ואינו נותן אישור אם זמני הנחיתה זהים.
בתוכנית הראשית Main, שבה הוגדר מערך המכיל זמני נחיתה ושמו times (המערך מכיל עצמים t1,t2,t3,... מסוג Clock של שעות הנחיתה של המטוס המגיע), כתוב קטע קוד (פקודות שיוצרות עצם חדש מסוג שעון וקולטות מן המשתמש את זמן הנחיתה של מטוסו (מטוס אחר שאינו במערך). הקטע בודק ומדפיס על המסך אם יש סכנת התנגשות או לא.
(יש להשתמש בפעולה שכתבת בסעיף הקודם).
אין צורך לבדוק את תקינות הקלט. ואין צורך להגדיר או לבנות את המערך times ולא להזין לו נתונים.
פתרון:
(א)
public int GetMin()
{
return this.min;
}
public void SetHour(int hour)
{
this.hour = hour;
}
public override string ToString()
{
string h = ""+this.hour;
if (this.hour < 10) h = "0" + h;
string m = "" + this.min;
if (this.min < 10) m = "0" + m;
return h + ":" + m;
}(ב)
public static bool IsEqualClocks(Clock c1, Clock c2)
{
if(c1.GetHour()==c2.GetHour() &&
c1.GetMin()==c2.GetMin())
return true;
return false;
}(ג)
int h = int.Parse(Console.ReadLine());
int m = int.Parse(Console.ReadLine());
Clock c = new Clock(h, m);
bool flag = false;
for(int i=0; i<times.Length; i++)
{
if(IsEqualClocks(times[i],c)) flag=true;
}
if(flag)
Console.WriteLine("Danger of collision");
else
Console.WriteLine("There is no danger of collision");12.6 תרגילים פתורים
(א)
כתוב מחלקה בשם Client שמגדירה לקוחות למעקב אצל דיאטנית.
תכונות הלקוחות הן:
- שם.
- מספר זהות.
- משקל התחלתי (ראשוני) והוא המשקל בעת ההרשמה אצל הדיאטנית ובתחילת התוכנית התזונתית.
- המשקל השבועי.
- הגובה.
- עלות הטיפול (התשלום).
כתוב פעולה בונה שמקבלת את השם, מספר הזהות, המשקל ההתחלתי והגובה.
כתוב פעולות גישה לתכונות המתאימות. (אין set לעלות הטיפול כי זה ערך שמחושב, וכן אין set למשקל ההתחלתי).
כתוב פעולה שמחשבת את עלות הטיפול (התשלום) לפי תעריף קבוע לירידה במשקל, לכל קילוגרם, כאשר הלקוח משלם רק על כל משקל שאיבד (שירד), ונקבע מחיר של 100 שקלים לכל קילוגרם שהלקוח מוריד
כתוב פעולה שמפיקה דוח: פעולה זו מחזירה מחרוזת ובה פרטי הלקוח.
(ב)
כתוב תוכנית שמגדירה שני לקוחות ויוצרת אותם, ועוקבת אחר תוכניתם במשך שלושה שבועות. על התוכנית להדפיס בסוף את פרטי שני הלקוחות האלה וכמה על כל אחד מהם לשלם.
(א)
class Client
{
private string name;
private string id;
private double firstWeight; //Taken from the constructor. Without set can`t be change.
private double weight;
private double tall;
private int charge; //Without set because it calculated.
// Constructor that build the object with: name,id,first weight and the tall
public Client(string name, string id, double firstWeight, double tall)
{
this.name = name;
this.id = id;
this.firstWeight = firstWeight;
this.tall = tall;
}
public void setName(string name)
{
this.name = name;
}
public void setId(string id)
{
this.id = id;
}
public void setWeight(double weight)
{
this.weight = weight;
}
public void setTall(double tall)
{
this.tall = tall;
}
public string getName()
{
return this.name;
}
public string GetId()
{
return this.id;
}
public double getFirstWeight()
{
return this.firstWeight;
}
public double getWeight()
{
return this.weight;
}
public double getTall()
{
return this.tall;
}
public int getCharge()
{
return this.charge;
}
public void clacMoney() //its don`t return any value, just update the charge.
{
const int PRICE_PER_KILO = 100;
double money;
money = (this.firstWeight - this.weight) * PRICE_PER_KILO;
this.charge=(int)money; //casting to int
}
public string report()
{
return this.name+"[id:"+this.id+"]--> F.W="+this.firstWeight+" ,L.W="+this.weight+" Charge="+this.charge;
}
}(ב)
static void Main(string[] args)
{
const int NUMBER_OF_WEEKS = 3;
const int NUMBER_OF_CLIENTS = 2;
int i,j;
string name, id;
double weight, tall;
double money=0;
Console.WriteLine("******** First Person ********");
Console.Write("Enter the name:");
name = Console.ReadLine();
Console.Write("Enter the id: ");
id = Console.ReadLine();
Console.Write("Enter your first weight: ");
weight = double.Parse(Console.ReadLine());
Console.Write("Enter your tall: ");
tall = double.Parse(Console.ReadLine());
Client p1 = new Client(name,id,weight,tall);
Console.WriteLine("******** second Person ********");
Console.Write("Enter the name:");
name = Console.ReadLine();
Console.Write("Enter the id: ");
id = Console.ReadLine();
Console.Write("Enter your first weight: ");
weight = double.Parse(Console.ReadLine());
Console.Write("Enter your tall: ");
tall = double.Parse(Console.ReadLine());
Client p2 = new Client(name, id, weight, tall);
for (j = 0; j < NUMBER_OF_CLIENTS; j++)
{
Console.WriteLine("Inserting data about {0} client",j+1);
for (i = 0; i < NUMBER_OF_WEEKS; i++)
{
Console.Write("Hi, Week {0}. insert your weight: ",i+1);
weight = double.Parse(Console.ReadLine());
if (j == 0)
{
p1.setWeight(weight);
p1.clacMoney();
}
else
{
p2.setWeight(weight);
p2.clacMoney();
}
}
}
Console.WriteLine(p1.report());
Console.WriteLine(p2.report());
}(א) כתוב מחלקת שעון Clock ובה התכונות הבאות:
- השעה h מספר שלם מ-0 עד 23
- הדקה m מספר שלם מ-0 עד 59
(ב) בנה פעולות גישה set ו-get לתכונות שלמעלה.
(ג) בנה את הפעולות הבאות:
- prnt פעולה שאינה מקבלת פרמטרים ואינה מחזירה פרמטרים, אלא מדפיסה על המסך את השעה.
- tik פעולה שמקדמת את השעון בדקה אחת (זז קדימה דקה).
(ד) כתוב תוכנית ראשית שבה נוצר שעון שמתחיל מן הזמן הבא: 14:30, והדקות זזות קדימה באמצעות שימוש בפעולה tik, וזאת כל פרק זמן קבוע (כאילו השעון מתקדם), ונדפיס על המסך את השעה.
השתמש בפקודה Console.Clear(); כדי למחוק את המסך ולהדפיס מחדש.
using System;
namespace clock1
{
class Clock
{
private int h;
private int m;
public void sethour(int h)
{
this.h = h;
}
public void setmin(int m)
{
this.m = m;
}
public int gethour()
{
return this.h;
}
public int getmin()
{
return this.m;
}
public void prnt()
{
Console.WriteLine(this.h+":"+this.m);
}
public void tik()
{
if (this.m == 59)
{
this.m = 0;
if (this.h == 23)
this.h = 0;
else
this.h++;
}
else
this.m++;
}
}
class Program
{
static void Main(string[] args)
{
Clock t1 = new Clock();
t1.sethour(14);
t1.setmin(30);
while(true)
{
t1.prnt();
for(int i=0; i<90000000; i++)
{ }
t1.tik();
Console.Clear();
}
}
}
}