» נושאי לימוד
» נושאי לימוד
יום שני 29 באפריל 2024
קטעי קוד מעניינים - המשך
דף ראשי  מתקדמים  רשימות מקושרות, מחסניות ותורים  תוכנית דוגמא  קטעי קוד מעניינים - המשך גרסה להדפסה

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

 

על מנת לעשות זאת, אנו מכריזים על פוינטר למשתנה מקומי מסוג קשר בשם currentRefToNode ומאתחילם אותו להתייחס לקשר הראשון ברשימה.

אז, אנחנו מפילים אותו אל תוך הלולאה אשר יתחסל כאשר הערך של currentRefToNode יהפוך לאפס.

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

 

אנו משתמשים בצורת העמסת אירועים של האופרטור "+" כדי להמיר את האובייקט המידע לאובייקט String ומעבירים אובייקט String זה לפונקצית printIn().

ביטלנו את פונקצית ה-toString() בהגדרה של המחלקה ממנה אובייקט המידע שלנו אותחל במיידיות, כך שזה גורם לאובייקט מידע שלנו להיות מומר לאובייקט String לפי הספציפיקציה שלנו (אחרת אופרטור  שורת האירועים היה גורם לו להיות מומר לאובייקט String  על פי ברירת מחדל). אובייקט ה-String מועבר לאחר מכן לפונקצית printIn() אשר מציגה אותו על גבי מישור פלט סטנדרטי.

 

אם לא ברור לך לחלוטין מה קורה כאן, עליך לחזור ולסקור חלק מהחומר הכלול בקורס המבוא.

 

הדבר האחרון שאנחנו עושים בתוך הלולאה (לפני החזרה לראשית הלולאה ובדיקת ביטוי התנאי) הוא לעדכן את המשתנה המותנה (currentRefToNode) על מנת לגרום לו להכיל את ערך הפוינטר למשתנה בשם nextNode בקשר הנוכחי.

 

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

 

טוב, זה מוביל אותנו לפונקציה הכי מורכבת בתוכנה שלנו: פונקציה בשם fetchFromBack() אשר מסירה קשר מהחלק האחורי של הרשימה ומחזירה פוינטר לאובייקט מידע הסגור בתוך קשר זה.

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

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

 

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

 

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

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

משתנה אחד מתייחס לקשר הראשון. אנו נשתמש בו כמשתנה עבודה על מנת לעבור על הרשימה.

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

 

  Object fetchFromBack() throws Excep{
    if(this.isEmpty()) //RawList is empty
      throw new Excep("Empty list in fetchFromBack");
    else { //RawList is not empty
      //declare and initialize a local reference variable
      Node tempRefToNode = lastNode;
     if(firstNode == lastNode)//only one node in the list
        firstNode = lastNode = null;  //set both to null
      else {//more than one node in the list
        //Declare and initialize another local
         // reference variable
       Node currentRefToNode = firstNode;
       while(currentRefToNode.nextNode != lastNode)
          currentRefToNode = currentRefToNode.nextNode;
        lastNode = currentRefToNode;
       currentRefToNode.nextNode = null;
      }//end else
     //Return the data object from the saved last node

      return tempRefToNode.dataObj;
  //fetch successful
    }//end else
  }//end fetchFromBack()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

(currentRefToNode.nextNode != lastNode)

 

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

כאשר לולאת התנאי מסתיימת, עלינו לשחרר את הקשר האחרון ולכוון את פוינטר ה-nextNode בקשר הנוכחי לאפס על מנת להראות שכעת זהו הקשר האחרון ברשימה.

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

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

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

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

 

      return tempRefToNode.dataObj;

 

פונקציה זו היא מאד לא יעילה, וכפי שציינו קודם, לא נשתמש בו כאשר נבצע תת-חלוקה של מחלקת RawList. הפונקציה הוצגה כאן בעיקר עבור ההדגמה ועבור תרגיל הרחבת המחשבה. בנוסף, חלק ממושגי התרגום הטמונים כאן ידרשו בשיעור הבא בו נגדיר מחלקת Ordered List.

 

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

 

זה מביא אותנו לסוף השיעור בנושא הגדרת מחלקה עבור מחלקה בשם RawList. כעת, יש לנו מחלקה אשר מגדירה מחלקת רשימה מקושרת כלל תכליתית בה ניתן להשתמש למגוון מטרות.

 

בשיעור זה, נשתמש במחלקת RawList לשתי מטרות. בפרט, נרחיב מחלקה זו לשתי מחלקות חדשות. אחת המחלקות החדשות תשמש לאתחל באופן מיידי אובייקטים המציגים התנהגות LIFO  של מחסנית. השנייה תשמש לאתחל באופן מיידי אובייקטים המציגים התנהגות FIFO של תור.

 

חשוב מאד לציין שאלה מבני נתונים כלליים לחלוטין בכל הנוגע לסוג המידע שהם יכולים להכיל (כל עוד המידע הוא אובייקט מידע).

 

אובייקטים מכל סוג (כולל סוגים מעורבים) יכולים להיות מוכלים על ידי מבנים אילו. אם אתה זקוק להם להכיל סוגים פרימיטיביים, כדוגמת int, תוכל להשתמש במחלקות wrapper  על מנת להפוך משתנים פרימיטיביים אלו לאובייקטים. אני מקווה שאתה זוכר מהן מחלקות wrapper.

 

חלק הקוד הבא הוא המחלקה אשר מרחיבה את מחלקת RawList על מנת להפיק מחלקת תור בשם MyQueue. מחלקה זו מחלקת את המחלקה RawList לתת מחלקות כך שהן מעניקות התנהגות של תור עבור אובייקטים אשר אותחלו מהמחלקה. תור הוא מבנה FIFO. ניתן להשיג התנהגות FIFO על ידי הכנסת מידע אל אחורי אובייקט ה- RawList ולהסיר אותו מקדמת האובייקט.

 

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

 

כתרגיל לסטודנט הסבר מדוע בחרנו להוסיף לחלק האחורי הרשימה ולהוריד מן החלק הקדמי במקום הוספה לחלק הקדמי והורדה מהחלק האחורי. שתי הגישות היו מספקות את התנהגות ה-FIFO   הרצויה.

 

שוב, שים לב שמחלקה זו עובדת אך ורק עם אובייקטים כלליים מסוג "אובייקט", ולכן יכולה לטפל באירועים מכל סוג או אירועים מעורבים.

 

class MyQueue extends RawList{
  public void enqueue(Object obj){
    this.toBack(obj);//enqueue data to the back of the list
  }//end enqueue()
   public Object dequeue() throws Excep{
    //dequeue data from the front of the list
    return this.fetchFromFront();
  }//end dequeue()
  public void printQueue(){
    this.printRawList();//use the existing print capability
  }//end printQueue()
  public boolean isQueueEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isQueueEmpty
    }//end class MyQueue

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

חלק הקוד הבא מגדיר מחלקה המרחיבה את מחלקת RawList בדרך המספקת התנהגות מחסנית. מחסנית זוהי התנהגות LIFO . התנהגות LIFO ניתנת להשגה על ידי חיבור מידע לקדמת הרשימה והורדתו מהחלק האחורי של הרשימה.

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

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

 

class MyStack extends RawList{
  public void push(Object obj){
    this.toFront(obj);//attach new data to front of list
  }//end push
  public Object pop() throws Excep{
    return this.fetchFromFront();
  }//end pop()
  public void printStack(){
    this.printRawList();//use existing print capability
  }//end printStack()
  public boolean isStackEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isStackEmpty
    }//end class MyStack

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

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

 

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

 

 02-12-03 / 19:01  עודכן ,  13-10-03 / 18:31  נוצר ע"י רונית רייכמן  בתאריך 
 תוכנית דוגמא - הקודםהבא - תוכנית בדיקה 
תגובות הקוראים    תגובות  -  0
דרכונט
מהי מערכת הדרכונט?
אינך מחובר, להתחברות:
דוא"ל
ססמא
נושאי לימוד
חיפוש  |  לא פועל
משלנו  |  לא פועל
גולשים מקוונים: 4