C/C++ מדריכי
הצגה מזורזת :C++ הבנת
C++ הקדמה להיררכית מחלקות ב C הקדמה לתכנות ב דף הבית

שיעור 4 - מחלקות

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

4.1 תוכנית של רשימת כתובות

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

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

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

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

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

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

4.2 תוכנית בסגנון הישן

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

#include <iostream.h> 
#include <string.h> 

typedef struct
{
    char name[20];
    char city [20];
    char state[20];
} addrStruct;

const int MAX=10;
addrStruct list[MAX];
int numInList;

void addName()
{
    if (numInList < MAX)
    {
        cout << "Enter Name: ";
        cin >>  list[numInList].name;
        cout << "Enter City: ";
        cin >> list[numInList].city;
        cout << "enter State: ";
        cin >> list[numInList].state;
        numInList++;
    }
    else
    {
        cout << "List full\n";
    }
}

void printOneName(int i)
{
    cout << endl;
    cout << list[i].name << endl;
    cout << list[i].city << endl;
    cout << list[i].state << endl
}

void printNames()
{
    int i;

    for (i=0; i < numInList; i++)
        printOneName(i);
    cout << endl;
}

void findName()
{
    char s[20];
    int i;
    int found=0;

    if (numInList==0)
    {
        cout << "List empty\n";
    }
    else
    {
        cout << "Enter name to find: ";
        cin >> s;
        for (i=0; i < numInList; i++)
        {
            if (strcmp(s,list[i].name)==0)
            {
                printOneName(i);
                found=1;
            }
        }
        if (!found)
            cout << "No match\n";
    }
}

void paintMenu()
{
    cout << "Address list Main Menu\n";
    cout << "  1 - add to list\n";
    cout << "  2 - print list\n";
    cout << "  3 - find name\n";
    cout << "  4 - quit\n";
    cout << "Enter choice: ";
}

void main()
{
    char choice[10];
    int done=0;
    numInList=0;
    while (!done)
    {
        paintMenu();
        cin >> choice;
        switch(choice[0])
        {
            case '1':
                addName();
                break;
            case '2':
                printNames();
                break;
            case '3':
                findName();
                break;
            case '4':
                done=1;
                break;
            default:
                cout << "invalid choice.\n";
        }
    }
}

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

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

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

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

1. הרבה יותר קל להחליף את הרשימה במבני נתונים אחרים מאוחר יותר, כיוון שצריכים לשנות רק את פונקציות הרשימה.

2. התוכנית הרבה יותר מאורגנת -- רעיון הרשימה מופרד מיתר הקוד עד כמה שאפשר.

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

ב C התוכנית הייתה נראית כך:

#include <iostream.h> 
#include <string.h> 
 
typedef struct
{
    char name[20];
    char city [20];
    char state[20];
} addrStruct;
 
//-------- data and functions for the list -------
const int MAX=10;
addrStruct list[MAX];
int numInList;
 
void listInit()
{
    numInList=0;
}
 
void listTerminate()
{
}
 
int listFull()
{
    if (numInList >=MAX) return 1; else return 0;
}
 
int listEmpty()
{
    if (numInList==0) return 1; else return 0;
}
 
int listSize()
{
    return numInList;
}
 
int listAdd(addrStruct addr)
{
    if (!listFull())
    {
        list[numInList++]=addr;
        return 0;  // returns 0 if OK
    }
    return 1;
}
 
int listGet(addrStruct&  addr, int i)
{
    if (i < listSize())
    {
        addr=list[i];
        return 0;  // returns 0 if OK
    }
    return 1;
}
//------------------------------------------------
 
void addName()
{
    addrStruct a;
 
    if (!listFull())
    {
        cout << "Enter Name: ";
        cin >> a.name;
        cout << "Enter City: ";
        cin >> a.city;
        cout << "enter State: ";
        cin >> a.state;
        listAdd(a);
    }
    else
        cout << "List full\n";
}
 
void printOneName(addrStruct a)
{
    cout << endl;
    cout << a.name << endl;
    cout << a.city << endl;
    cout << a.state << endl;
}
 
void printNames()
{
    int i;
    addrStruct a;
 
    for (i=0; i < listSize(); i++)
    {
        listGet(a,i);
        printOneName(a);
    }
    cout << endl;
}
 
void findName()
{
    char s[20];
    int i;
    int found=0;
    addrStruct a;
 
    if (listSize==0)
        cout << "List empty\n";
    else
    {
        cout << "Enter name to find: ";
        cin >> s;
        for (i=0; i < listSize(); i++)
        {
            listGet(a, i);
            if (strcmp(s,a.name)==0)
            {
                printOneName(a);
                found=1;
            }
        }
        if (!found)
            cout << "No match\n";
    }
}
 
void paintMenu()
{
    cout << "Address list Main Menu\n";
    cout << "  1 - add to list\n";
    cout << "  2 - print list\n";
    cout << "  3 - find name\n";
    cout << "  4 - quit\n";
    cout << "Enter choice: ";
}
 
void main()
{
    char choice[10];
    int done=0;
    listInit();
    while (!done)
    {
        paintMenu();
        cin >> choice;
        switch(choice[0])
        {
            case '1':
                addName();
                break;
            case '2':
                printNames();
                break;
            case '3':
                findName();
                break;
            case '4':
                done=1;
                break;
            default: cout << "invalid choice.\n";
        }
    }
    listTerminate();
}

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

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

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

4.3 יצירת מחלקה -

הקוד הבא לוקח את הנתונים ואת שבע פונקציות הרשימה מהרישום הקודם ומיישם אותם כמחלקה ב ++C. לאחר מכן הוא משתמש במחלקה זו בתוכנית:

#include <iostream.h> 
#include <string.h> 
 
typedef struct
{
    char name[20];
    char city [20];
    char state[20];
} addrStruct;
 
const int MAX = 10;
 
class List
{
    addrStruct list[MAX];
    int numInList;
public:
    List(): numInList(0) // constructor
    {
    }
    ~List() // destructor
    {
    }
    int Full()
    {
        if (numInList >=MAX) return 1; else return 0;
    }
    int Empty()
    {
        if (numInList==0) return 1; else return 0;
    }
    int Size()
    {
        return numInList;
    }
    int Add(addrStruct addr)
    {
        if (!Full())
        {
            list[numInList++]=addr;
            return 0;  // returns 0 if OK
        }
        return 1;
    }
    int Get(addrStruct&  addr, int i)
    {
        if (i < Size())
        {
            addr=list[i];
            return 0;  // returns 0 if OK
        }
        return 1;
    }
};
//-----------------------------------------------
 
List list;
 
void addName()
{
    addrStruct a;
 
    if (!list.Full())
    {
        cout << "Enter Name: ";
        cin >> a.name;
        cout << "Enter City: ";
        cin >> a.city;
        cout << "enter State: ";
        cin >> a.state;
        list.Add(a);
    }
    else
        cout << "List full\n";
}
 
void printOneName(addrStruct a)
{
    cout << endl;
    cout << a.name << endl;
    cout << a.city << endl;
    cout << a.state << endl;
}
 
void printNames()
{
    int i;
    addrStruct a;
 
    for (i=0; i < list.Size(); i++)
    {
        list.Get(a,i);
        printOneName(a);
    }
    cout << endl;
}
 
void findName()
{
    char s[20];
    int i;
    int found=0;
    addrStruct a;
 
    if (list.Size()==0)
        cout << "List empty\n";
    else
    {
        cout << "Enter name to find: ";
        cin >> s;
        for (i=0; i < list.Size(); i++)
        {
            list.Get(a, i);
            if (strcmp(s,a.name)==0)
            {
                printOneName(a);
                found=1;
            }
        }
        if (!found)
            cout << "No match\n";
    }
}
 
void paintMenu()
{
    cout << "Address list Main Menu\n";
    cout << "  1 - add to list\n";
    cout << "  2 - print list\n";
    cout << "  3 - find name\n";
    cout << "  4 - quit\n";
    cout << "Enter choice: ";
}
  
int main()
{
    char choice[10];
    int done=0;
 
    while (!done)
    {
        paintMenu();
        cin >> choice;
        switch(choice[0])
        {
            case '1':
                addName();
                break;
            case '2':
                printNames();
                break;
            case '3':
                findName();
                break;
            case '4':
                done=1;
                break;
            default:
                cout << "invalid choice.\n";
        }
    }
    return 0;
    // list destroys itself when it goes out of scope.
}

מחלקת ה list קרובה לראש התוכנית ומתחילה במילים class List. זוהי רק הצהרה על טיפוס -- המופע הממשי של הרשימה מופיע בשורה:

List list;

שורה זו מצהירה על משתנה בשם list מטיפוס class List.

שים לב שהמחלקה List דומה מאוד בהתחלה ל structure. היא מצהירה על שני משתנים באותו אופן ש structure היה עושה זאת. אלה נקראים data members. לאחר מכן היא מכילה את המילה "public" (פומבי): מילה זו מציינת שהפונקציות הבאות יהיו ידועות לכל קוד שמשתמש במחלקה זו. המילה ההפוכה היא "private" (פרטי) ומשתמשים בה כאשר פונקציות או משתנים צריכים להישאר סמויים מיתר התוכנית. המשתנים והפונקציות שמוגדרים במחלקה הם פרטיים על פי ברירת המחדל אלא אם כן הופכים אותם במפורש לפומביים כפי שמוצג כאן (שני ה data members הם פרטיים על פי ברירת המחדל, ו 7 הפונקציות הן פומביות).

לאחר ה data members מגיעות הפונקציות החברות. אלה הן הפונקציות שיכולות לחול על מופעים של מחלקה זו. שתי הפונקציות הראשונות -- List ו List~ -- הן יחידות במינן, ונקראות הבנאי (constructor) וההורס (destructor) בהתאמה. הבנאי מופעל באופן אוטומטי כאשר מופע של המחלקה מתחיל להתקיים. במקרה זה, המופע מתחיל להתקיים בהתחלת ביצוע התוכנית כיוון שמצהירים עליו כמשתנה גלובלי, אבל בנאים של משתנים מקומיים מופעלים כאשר המשתנה המקומי מתחיל להתקיים ובנאים של מצביעים מופעלים כאשר נקרא new על המצביע. הבנאי הוא בעל אותו שם כמו המחלקה עצמה.

List(): numInList(0) // constructor
{
}

האתחול של ה data member שנקרא numInList הנו יחיד במינו כאן. דרך אחרת לעשות זאת תהיה לכתוב:

List() // constructor
{
    numInList = 0;
}

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

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

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

משתמשים במופע list לאורך התוכנית. בכל פעם שצריכים לעשות משהו ל list מוצאים את שם המופע list שלאחריו נקודה ואז שם פונקציה. דבר זה שוב מקביל לתחביר של structure. הנקודה פירושה, "קרא לפונקציה חברה מהמחלקה List על המופע הספציפי list".

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

4.4 דוגמא פשוטה יותר

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

#include <iostream.h> 

class Stack
{
    int stk[100];
    int top;
public:
    Stack(): top(0) {}
    ~Stack() {}
    void Clear() {top=0;}
    void Push(int i) {if (top < 100) stk[top++]=i;}
    int Pop() 
    {
        if (top > 0) return stk[--top]; 
        else return 0;
    }
    int Size() {return top;}
};

int main()
{
    Stack stack1, stack2;

    stack1.Push(10);
    stack1.Push(20);
    stack1.Push(30);
    cout << stack1.Pop() << endl;
    stack2=stack1;
    cout << stack2.Pop() << endl;
    cout << stack2.Pop() << endl;
    cout << stack1.Size() << endl;
    cout << stack2.Size() << endl;
    return 0;
}

תוכנית זו מכילה שני חלקים: המחלקה Stack ופונקצית ה main. המחלקה מגדירה טיפוס Stack, ומצהירים על שני מופעים מטיפוס זה בתוך main. לכל אחד מהמופעים האלו יהיה העתק משלו של ה data members שנקראים stk ו top, והפעולה sizeof על כל אחד תראה שבדיוק מספיק מקום (202 או 404 בתים, תלוי בסביבה) מוקצה לכל אחד. מחלקה משתמשת בדיוק באותו מקום בו structure עם אותם data members משתמש -- אין כל תקורת זיכרון על הפונקציות החברות.

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

לדוגמא:

stack1.Push(10);

שורה זו מציינת שהערך 10 צריך להידחף על stack1. המופע stack1 מחזיק שני קטעי נתונים (stk ו top) שמכילים ערכים. שורה זו פירושה, "קרא לפונקציה Push על המבנה stack1 -- הפנה את הפקודות שב Push ואת הערך 10 למערך הממשי ולמספר השלם שמוחזקים בתוך stack1". יש שתי מחסניות נפרדות לחלוטין בתוכנית זו: stack1 ו stack2. פקודה כמו stack2.Push(5) משמעותה ש 5 צריך להידחף על המבנה stack2.

פקודת ההשמה שבאמצע פונקצית ה main היא מעניינת. היא עושה את אותו הדבר שהשמה בין שני structures הייתה עושה -- הערכים של ה data members של הצד הימני מועתקים ל data members שמשמאל:

stack2 = stack1;

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

4.5 מחלקת מלבן

איך מחליטים מה צריך להפוך לעצם ומה לא? ביסודו של דבר מה שעושים זה לקחת כל קבוצה קטנה של פרטי נתונים קשורים שניתן למצוא בתוכנית, לחבר לזה מספר פונקציות, וליצור מחלקה. בדוגמת המחסנית לעיל, המערך stk והמספר השלם top הם פרטי הנתונים שנחוצים למחסנית. מספר פונקציות שימושיות קשורות לקיבוץ הנתונים הקטן הזה (Push, Pop, Clear,ו Size). יחד הנתונים והפונקציות יוצרים מחלקה.

נניח שעליך לזכור קואורדינטות למלבן באחת מהתכניות שלך. המשתנים שלך מכונים x2 ,y1 ,x1, ו y2 -- x1 ו y1 מייצגים את הפינה השמאלית העליונה ו x2 ו y2 מייצגים את הפינה הימנית התחתונה. יחד הם מייצגים מלבן. אילו פונקציות שימושיות קשורות לערכים האלו? יש צורך להיות מסוגלים לאתחל אותם (עבודה מושלמת לבנאי), ואולי יהיה שימושי למצוא את השטח וההיקף של המלבן. המחלקה עשויה להיראות כך:

class Rect
{
    int x1, y1, x2, y2;
public:
    Rect(int left=0,int top=0,
        int right=0,int bottom=0):
        x1(left),  y1(top),  x2(right),  y2(bottom)
    {
    }
    ~Rect() {}
    int Height() { return (y2-y1); }
    int Width() { return (x2-x1); }
    int Area() { return Width()*Height(); }
    int Perimeter() { return 2*Width()+2*Height();}
};  

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

4.6 פרטים על מחלקות

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

#include <iostream.h> 

class Sample
{
    int num;
public:
    Sample(int i): num(i) 
    {
        cout << "constructor " << num
            << " called" << endl;
    }
    ~Sample() 
    { 
    cout << "destructor " << num 
        << " called" << endl;}
};
 
int main()
{
    Sample *sp;
    Sample s(1);

    cout << "line 1" << endl;
    {
        Sample temp(2);
        cout << "line 2" << endl;
    }
    cout << "line 3" << endl;
    sp = new Sample(3);
    cout << "line 4" << endl;
    delete sp;
    cout << "line 5" << endl;
    return 0;
}

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

Data members ופונקציות חברות יכולים להיות פומביים או פרטיים, תלוי בתפקידם בתוכנית. זה טוב לשאוף לעבר המטרה שלא יהיו שום data members שהם פומביים. ניתן להשתמש בחבר פומבי בכל מקום בתוכנית, בעוד שבחבר פרטי יכולה להשתמש רק פונקציה שהיא חברה במחלקה. הבה נשנה קצת את המחלקה Rect כדי לראות מה זה אומר:

class Rect
{
    int x1, y1, x2, y2;
public:
    Rect(int left=0,int top=0,
        int right=0,int bottom=0):
        x1(left),  y1(top),  x2(right),  y2(bottom)
    {
    }
    ~Rect() {}
private:
    int Height() { return (y2-y1); }
    int Width() { return (x2-x1); }
public:
    int Area() { return Width()*Height(); }
    int Perimeter() { return 2*Width()+2*Height();}
};  

כעת הפונקציות Width ו Height הן פרטיות. ניתן לקרוא לפונקציות כפי שמוצג כאן כיוון ש Area ו Perimeter הן פונקציות חברות. אבל אם תנסה את זה:

    Rect r;
    ...
    cout  << r.Height();

תקבל שגיאת קומפילציה כיוון ש Height הוא פרטי.

השמה בין שני מופעים של מחלקה פשוט מעתיקה את ה data members ממופע אחד לשני. לדוגמא:

    Rect r1,r2;
    ...
    r1=r2;

זה אותו הדבר כמו לומר:

    r1.x1 = r2.x1;
    r1.y1 = r2.y1;
    r1.x2 = r2.x2;
    r1.y2 = r2.y2;

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

class Rect
{
    int x1, y1, x2, y2;
public:
    // the constructor uses default param. See tutor 2
    Rect(int left=0,int top=0,int right=0,int bottom=0);
    ~Rect();
    int Height();
    int Width();
    int Area();
    int Perimeter();
};

Rect::Rect(int left, int top, int right, int bottom):
    x1(left),  y1(top),  x2(right),  y2(bottom)
// default values are understood from the prototype
{
}

Rect::~Rect()
{
}

int Rect::Height()
{
    return (x2-x1);
}

int Rect::Width() 
{
    return (y2-y1);
}

int Rect::Area()
{
    return Width()*Height(); 
}

int Rect::Perimeter()
{
    return 2*Width()+2*Height();
}

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

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




לדף הראשון

<< לדף הקודם

לדף הבא >>