עד כה הגדרנו תכונות (Data Members) השייכות לאובייקט.
בכל יצירת מופע (Instance) של אובייקט נוצרים בזיכרון (ב- Manage heap) העתקים של אותם המשתנים החברים, לכל אובייקט יש Data Members משל עצמו ,אין שום קשר בין ה- Data Members של אובייקט אחד ל- Data members של האובייקטים האחרים מאותה המחלקה.
במילים אחרות, אם נגדיר מספר אובייקטים מהמחלקה מסויימת, לכל אובייקט יוקצו בזיכרון כל ה- Data Members, המוגדרים במחלקה, תוכנם של אותם המשתנים יהיה שונה מאובייקט לאובייקט.
לעיתים נרצה לנהל מאגר נתונים משותף לכל האובייקטים הנוצרים ממחלקה מסוימת, הסיבה לכך יכולה להיות חיסכון בזיכרון או ביצוע אלגוריתם מסוים.
לדוגמה, נניח שברצוננו למנות את מספר האובייקטים שנוצרו ממחלקה מסוימת מאז שהתוכנית החלה לרוץ או בתכונה אחרת נשתמש בערך זהה לכל האובייקטים.
במקרים אלו, ללא השימוש ב- Static Data Members יתכן ותיווצר בעיה כפי שהיא מוצגת בדוגמה הבאה (StaticDataMember01):
בשורה 6 מוגדרת התכונה PI, ל-PI ערך קבוע שאינו משתנה לעולם. בשורה 7 מוגדרת התכונה counter, באמצעותה נרצה לדעת כמה אובייקטים מהמחלקה Circle נוצרו במהלך ריצת התוכנית.
Main:
1 : classProgram
2 : {
3 : staticvoidMain(string[]args)
4 : {
5 : Circlec1=newCircle(1,2,100);
6 : Circlec2=newCircle(3,4,120);
7 : Circlec3=newCircle(4,5,130);
8 : c1.Print();
9 : c2.Print();
10 : c3.Print();
11 : }
12 : }
פלט:
בתוכנית נוצרו 3 אובייקטים מהמחלקה Circle, מדוע מודפס רק 1?
דוגמה רעה, נבדוק מדוע.
ביצירת האובייקט הראשון (c1) מופעל הבנאי שמאתחל את ערכי התכונות (לא בודק את ערכי הפרמטרים וזה כבר רע) ומעלה את התכונה counter ב-1.
ביצירת האובייקט השני (c2) מופעל הבנאי שמאתחל את ערכי התכונות ומעלה את התכונה counter ב-1. גם ביצירת האובייקט השלישי (c3) מופעל הבנאי שמאתחל את ערכי התכונות ומעלה את התכונה counter ב-1.
הבעיה היא שלכל אחד מהאובייקטים יש counter עצמאי.
בעיה נוספת היא בזבוז זכרון, התכונה PI מכילה ערך זהה בכל האובייקטים, גם אם נגדיר עוד מאות אלפי או מיליוני מופעים מהמחלקה Circle ערכה לא ישתנה, אז מדוע צריך להגדיר אותה מחדש בכל פעם?
תמונת הזיכרון אחרי הקצאת שלושת האובייקטים:
מה רואים באיור:
בכל יצירת מופע (Instance) של אובייקט נוצרים בזיכרון (ב- Manage heap) העתקים של התכונות, לכל אובייקט יש תכונות פרטיות משל עצמו ,אין שום קשר בין התכונות של אובייקט אחד לתכונות של האובייקטים האחרים מאותה המחלקה.
במילים אחרות, אם נגדיר מספר אובייקטים מהמחלקה Circle, לכל אובייקט יוקצו בזיכרון כל התכונות המוגדרות במחלקה, תוכנם (הערך) של אותן התכונות יהיה שונה מאובייקט לאובייקט.
ו-counter הוא לא יוצא דופן.
מבחינת תמונת הזיכרון עולה וצפה האיוולת השנייה, התכונה PI ערכה קבועה כבר אלפי שנים (וכנראה גם לא תשתנה בקרוב),
בכל הקצאת אובייקט אנו מבזבזים זיכרון לחינם משום שאין טעם להקצותה כל פעם, מספיקה פעם אחת בלבד.
הפיתרון המתבקש, הגדרת PI ו- Counter כתכונות סטאטיות (StaticDataMenber02):
הפעם, בשורות 6 ו-7 נגדיר את התכונות PI ו-counter כתכונות סטאטיות (Static Data Member). בשורה 13, בתוך הבנאי ניתן לראות התייחסות לתכונה הסטאטית, ההתייחסות היא באמצעות שם המחלקה ולא this.
סיבה? תכונה סטאטית היא ברמת המחלקה ולא ברמת האובייקט.
הפעם שנריץ את התוכנית (Main לא השתנתה) נקבל את הפלט הבא:
הפעם counter מכיל את הערך הנכון.
נבחן את תמונת הזכרון הפעם: הפעם התכונות PI ו- counter מוגדרות כתכונות סטאטיות ולכן הן מוגדרות ברמת המחלקה ולכן הן תכונות משותפות לכל האובייקטים מהמחלקה Circle.
חסכנו מקום בזיכרון ומנענו את השגיאה הלוגית בתכונה counter.
הערה – השימוש ב- this בפנייה לתכונות המופע ובשם המחלקה Circle בפנייה לתכונות הסטאטיות אינו חובה במקרה זה, אולם מומלץ בשל שיפור בהירות הקוד.
אורך החיים של תכונות סטאטיות
מכיוון שתכונות סטאטיות מוגדרות ברמת המחלקה ולא ברמת האובייקט הן נוצרות כאשר המחלקה נטענת לזיכרון עם תחילת ריצת התוכנית (לרוב).
ולכן ניתן לגשת אליהן לפני שנוצר אובייקט מהמחלקה ובלי קשר לקיומו.
Static Method
Static Methods דומות ל- Static Data Members בכך שהן אינן שייכות לאובייקטים אלא הן ברמת המחלקה. Static Methods זמינות מהרגע שהמחלקה נטענת לזיכרון.
כמו Static Data Members גם Static Methods מוגדרות באמצעות המילה השמורה static , תחביר:
class Sample
{
. . .
public static void DoSomething()
{
. . .
}
. . .
}
מכיוון שהן משויכות למחלקה ולא למופע (instance) מסוים מהמחלקה, הן מופעלות באמצעות שם המחלקה:
ClassName.StaticMethodName (. . . );
מכיוון ש- Static Methods זמינות מהרגע בו המחלקה נטענת לזיכרון, ולפני הקצאת האובייקט הראשון הן יכולות לטפל אך ורק ב- Static Data Members.
Static Data Members אינן מקבלות את הייחוס this .
דוגמה (StaticMethodSample):
1 : static classMyMath
2 : {
3 : publicstaticintSum(intn1,intn2)
4 : {
5 : returnn1+n2;
6 : }
7 : publicstaticintSub(intn1,intn2)
8 : {
9 : returnn1-n2;
10 : }
11 : publicstaticfloatAvg(intn1,intn2)
12 : {
13 : return(n1+n2)/2;
14 : }
15 : }
בשורות 3,7,11 - הגדרת Static Methods מתבצעת באמצעות המילה השמורה static. בשורה 1 הגדרנו את כל המחלקה כסטאטית, הדבר אפשרי משום שהיא מכילה רק מרכיבים סטאטיים,
במידה והיו בה תכונות או מתודות שאינן סטאטיות, הדבר לא היה אפשרי.
בשורות 5,6,7 - הפעלת ה- Static Methods של המחלקה MyMath באמצעות שם המחלקה.
פלט:
בנאים סטאטיים
תפקידו של בנאי המחלקה הוא לאתחל את התכונות של האובייקט ברגע הקצאתו.
תפקידו של בנאי מחלקה סטאטי (Static Ctor) הוא לאתחל את המשתנים הסטאטיים, הוא מופעל על ידי התוכנית כאשר המחלקה נטענת לזיכרון.
לבנאים סטאטיים לא ניתן להגדיר הרשאת גישה ולא ניתן להגדיר פרמטרים.
לדוגמה:
1 : classCircle
2 : {
3 : privateintm_X;
4 : privateintm_Y;
5 : privateintm_Radius;
6 : privatestaticdoublePI;
7 : staticCircle()
8 : {
9 : PI=3.1415;
10 : }
11 : publicCircle(intx,inty,intradius)
12 : {
13 : this.m_X=x;
14 : this.m_Y=y;
15 : this.m_Radius=radius;
16 : }
17 : ...
18 : }
בשורה 7 מוגדר בנאי סטאטי.
בתרגיל הדוגמה ניתן היה לוותר על הבנאי הסטאטי ולאתחל את המשתנה בשורת ההגדרה, במקרה זה הקומפיילר היה מייצר בעצמו בנאי סטאטי ומאתחל בתוכו את הערך של התכונה הסטאטית PI.
בנאי סטאטי נחוץ במקרים בהם האתחול הוא תוצאת קוד שנכתב, לדוגמה, קריאת מידע ממסד נתונים או מקובץ.