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

שיעור 6 - הגדרה מחדש של אופרטורים

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

void main()
{
    Mint max("100");
    Mint counter("1"), one("1");
    Mint t1("0"), t2("1");
    Mint d;

    do
    {
        d.Add(t1,t2);
        t1=t2;
        t2=d;

        counter.Add(counter,one);
    } while (!counter.Equal(max));
    d.Print();
}

במקום זאת היינו רוצים להיות מסוגלים לכתוב קוד שנראה "נורמלי", כמו זה:

void main()
{
    Mint max("100");
    Mint counter("1");
    Mint t1("0"), t2("1");
    Mint d;

    do
    {
        d = t1 + t2;
        t1=t2;
        t2=d;
        counter = counter + "1";
    } while (! (counter==max));
    cout << d << endl;
}

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

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

List lst;
...
lst.Insert(5);

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

ניתן להגדיר מחדש כמעט כל אופרטור:

+ - * / % ^ & | ~ ! , = > < => =< ++ -- >> << == =! && || =+ =- =/ =% =^ =& =| =* =>> =<< [ ] ( ) <- *<- new delete

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

הקוד להלן מציג את המחלקה Mint משוכתבת כך שהאופרטורים "+", "==", ו ">>" מוגדרים מחדש, יחד עם קטע קוד של בדיקה שמשתמש בכל השלושה:

class Mint: public List
{
public:
    Mint():List() {}
    Mint(char *s):List()
    {
        char *p;
        for (p=s; *p; p++)
            AddToEnd(*p-'0');
    }

    friend Mint operator+ (Mint & a, Mint & b)
    {
        int carry, temp;
        int erra, errb, na, nb;
        Mint x;

        carry=0;
        erra=a.GetLast(na);
        errb=b.GetLast(nb);
        while (!erra || !errb)
        {
            if (erra)
                temp=nb+carry;
            else if (errb)
                temp=na+carry;
            else
                temp=na+nb+carry;
            x.AddToFront(temp%10);
            carry=temp/10;
            erra=a.GetPrevious(na);
            errb=b.GetPrevious(nb);
        }
        if (carry> 0)
            x.AddToFront(carry);
        return x;
    }

    int operator==(Mint & a)
    {
        if (a.Size()!=Size())
            return 0;
        else
        {
            int i, na, nb;
            a.GetFirst(na);
            GetFirst(nb);
            for (i=0; i < a.Size(); i++)
                if (na!=nb)
                    return 0;
                else
                {
                    a.GetNext(na);
                    GetNext(nb);
                }

            return 1;
        }
    }

    friend ostream&  operator << (ostream&  s, Mint & m)
    {
        int n, err;

        err=m.GetFirst(n);
        while( !err )
        {
            s << n;
            err=m.GetNext(n);
        } 
        return s;
    }
};

void main()
{
    // add two numbers
    Mint a("1234567");
    Mint b("1234");
    Mint c;

    c = a + b;
    cout << "it's fine " << c << "...really" << endl;
    cout << a + "3333" << endl;

    // find the 100th Fibonacci number
    Mint counter;
    Mint t1, t2;
    Mint d;

    t1 = "0";
    t2 = "1";
    counter = "1";
    do
    {
        d = t1 + t2;
        t1 = t2;
        t2 = d;
        counter = counter + "1";
    } while (! (counter == "100") );
    cout << d << endl;
}

הבה נתחיל בבחינת הפונקציה "==":

int operator==(Mint & a)

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

Mint b, m;
...
if (b == m)

שאר הקוד זהה לפונקציה Equal שראינו בשיעור 5. המספר השלם המוחזר משמש כתוצאה של ההשוואה. עם הפונקציה הזו במקום, אופרטור ה "==" נקרא כל אימת שהמהדר מוצא אופרטור "==" בין שני ערכים מטיפוס Mint.

האופרטור המוגדר מחדש "+" הוא פונקצית friend:

friend Mint operator+ (Mint & a, Mint & b)

היא מוצהרת כ friend כיוון שאנחנו לא רוצים שהיא תשתמש אוטומטית בצד השמאלי של פקודת פלוס כ this כיוון שהיא תנקה אותו (כפי שמתואר בשיעור 5). היות שהיא friend היא מתנהגת כפונקצית C רגילה בלי מצביע this. היא מחברת את שני ה mints שמועברים אליה ומחזירה תוצאת mint.

בתוך פונקצית ה main יש מספר פקודות מהצורה הבאה:

c = "3333"

ו

c = c + "1";

איך המהדר יודע מה לעשות? איך הוא יודע להפוך "1" ל mint? כיוון שיש לנו בנאי של mint שמקבל טיפוס *char, הבנאי מופעל אוטומטית בניסיון להתאים את האופרטורים של ה +. אם היינו יוצרים בנאי נוסף שמקבל פרמטר שהוא long, היינו יכולים גם לכתוב קוד כזה:

c = c + 1;

המרה זו של ערך int תהיה אוטומטית גם כן. הפקודה הבאה לא תעבוד:

c = "2222" + "3333";

למהדר אין דבר שיאמר לו שה "+" אמור לחבר mints, כך שהוא לא יכול לבצע את ההמרה -- מצד אחד של ה "+" צריך להיות mint כדי לסמן למהדר.

גם האופרטור >> מוגדר מחדש. הפונקציה צריכה להיות friend כיוון שהפרמטר השמאלי אינו מטיפוס המחלקה. היא צריכה לקבל התייחסות לפרמטר ostream ואז לפרמטר מטיפוס המחלקה. היא גם צריכה להחזיר התייחסות ל ostream. בכל אופן לאחר שעשינו זאת הקוד הוא פשוט. עם הפונקציה הזו במקום כל פעולת פלט ב ++C שמשתמשת ב mint תעבוד.

אופרטור ה << מוגדר מחדש באופן דומה:

friend istream& operator >> (istream& s, Mint&  m)
{
    buf[100];

    s >> buf;
    m = buf; // calls the constructor
    return s;
}

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




לדף הראשון

<< לדף הקודם

לדף הבא >>