שיטת תכנות במחלקות  class / struct 

 

 


תוכן הפרק

 

בניית מחלקה

הצהרה על מחלקה

הגדרת פעולות מחלקה

יצירת עצמים ושימוש בהם

השוואה בין פונקציה חופשית ופונקציה חברה במחלקה

הסתרת מידע במחלקות

דוגמא להסתרת מידע במחלקות

מצב עצם תקין -  שמורה

פונקציות בין השורות

אתחול והריסה של עצם

copy constructor

תחום הכרה    וטווח קיום

עצמים קבועים

const memeber functions

mutable in class

משתנה ופונקציה סטטי  במחלקה

enum  - enumeration constants

 

 

 

 

 

 

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

ב  c++  מנגנון המחלקות (class/struct) מקנה תמיכה נוחה להשגת:

    1.            אפשרות להגדיר מודול תוכנה בלי קשר לקבצים (לא חייבים מודול אחד בכל קובץ).

    2.            אין צורך להשתמש במשתנים גלובליים למימוש מודול.

    3.            אפשרות לשכפל בקלות מספר מודולים דומים (לדוגמא: מספר מיכלי מיים במקום אחד).

    4.            לאפשר אתחול אוטומטי (אי אפשר לשכוח לאתחל).

    5.            לאפשר הריסה אוטומטית (פעולה ההפוכה לאתחול אם צריך).

    6.            תמיכה נוחה ברעיונות הסתרת מידע (information hiding)- הסתרת משתנים ופונקציות.

    7.            תמיכה ברעיון הכימוס. (encapsulation) - איסוף של משתנים ופונקציות ל"כמוסה" אחת.

    8.            תמיכה בתכנות מונחה עצמים (object oriented programming).

    9.            תמיכה בשימוש חוזר בקוד  (code reuse).

 

 

מהי מחלקה::

·      הגדרת טיפוס (type) חדש - אחראי על מושג אחד מהתוכנית. (לדוגמא  int הוא טיפוס)

·      טיפוס משמש ליצירת עצמים (objects)  מסוג הטיפוס:  (לדוגמא  int a;  -  אפשר לראות את  a כעצם מסוג int).  גם אומרים כי העצם  a  הוא מופע   (instance)  של המחלקה int.

·      הגדרת טיפוס מכילה:

 1.               המאפיינים  (attributes) - המידע המוכל בטיפוס.

 2.               פעולות (method) - אוסף הפעולות שאפשר להפעיל על העצם.

·      הפעלת פעולה על עצם מכונה שליחת הודעה לעצם  (message-send).

·      התוצאה המחוזרת מהפעלת הפעולה מכונה (message-reply).

 

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

בניית מחלקה 

ניקח לדוגמא את המושג זוג מספרים:

Object type: pair

Knows:           

first                 //two integer numbers                   

            second            

            Can do:           

                        get_from_kb()             

                        show_sum()                             

 

·                                 המושג מחלקה הוא מושג כללי  מתכנות מונחה עצמים ויכול להיות ממוש בהרבה שפות (Smalltalk, Eifel, Java, ...).

·                                 בניית מחלקה נעשית בשלושה שלבים (ובדרך כלל בשלושה קבצים) שהם:

1.                     הצהרה כל המחלקה. -  הצהרה של  class/struct  שבתוכו  משתנים ופונקציות.

2.                     הגדרת פעולות המחלקה.  -   הגדרת הפונקציות שהוצהרו בתוך ה  class/struct

3.                     יצירת עצמים מסוג המחלקה והפעלת פעולות עליהם.  -   הגדרת עצמים מטיפוס המחלקה והפעלת פעולות עליהם.


הצהרה על מחלקה

 

//--------------------------------------------------------------------

// file: pair.h

//    class pair

 

struct pair {

    int first, second;

    void get_from_kb();

    void show_sum();

};

//--------------------------------------------------------------------

מימוש רעיון המחקלה ב  c++:

·                                 בכדי להצהיר על מחלקה חדשה אנו משתמשים ב  class/struct.

·                                 מאפייני המחלקה מכונים ב  c++  --  member data  והם משתני המחלקה.

·                                 פעולות המחלקה מכונות ב  c++  --  member functions  והן פעולות המחלקה.

 

 

 

הגדרת פעולות מחלקה

//--------------------------------------------------------------------

// file: pair.cpp

//  definition of class pair functions

 

#include <iostream.h>

#include "pair.h"

 

void pair::get_from_kb()

{

    cin >> first >> second;

}

 

void pair::show_sum()

{

   cout << "the sum of " << first << " and " << second

        << " is: " << (first + second) << endl;

}

//--------------------------------------------------------------------

 

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

·        זהו גם ההבדל בין פונקציה במחלקה  (member-function)  לבין הפונקציות החופשיות שראינו עד עתה

  (free-function)    פונקציה של מחלקה מוגדרת

·        האופרטור  ::  (scope resulotion operator)  מציין כי השם מצד ימין שלו שייך למחלקה ששמה מופיע מצד שמאל שלו:   pair::show_sum, פרושו  הפונקציה  show_sum שייכת למחלקה pair.

 


יצירת עצמים ושימוש בהם

//--------------------------------------------------------------------

    pair p1, p2;   // creating two objects.

    p1.first = 2;

// file: pair_use.cpp

//  creating objects from class pair and using them.

 

#include "pair.h"

 

void main()

{

    p1.second = 3;

    p1.show_sum();    // prints: the sum of 2 and 3 is: 5

    p2.get_from_kb(); // reads two new values

    p2.show_sum();    // print thier sum

    p2 = p1;

    p2.show_sum();    // prints: the sum of 2 and 3 is: 5

}

//--------------------------------------------------------------------

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

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

·                                 כאן ראינו שמחלקה ב  c++ תומכת ברעיון הכימוס  (encapsulation)  בכך שהיא מאפשרת לארוז פונקציות ומשתנים  

                 הקשורים לרעיון ה  pair  ב"חבילה אחת".

 

 

השוואה בין פונקציה חופשית ופונקציה חברה במחלקה

פונקציה היא פעולה. יש שני סוגים של פונקציות:

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

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

3

 

r

 

b

 

r

 

 

 

ob

 

a

 

5

 

3

 

a

 
 

 

 


הפעלת הפונקציה

 

5

 

b

 

r

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


חזרה להתחלה

הסתרת מידע במחלקות

·                                 ראינו כי האפשרות לארוז פונקציות ומשתנים בתוך מחלקה מתאים לתמיכה ברעיון הכימוס  encapsulation.

·                                 אבל קיים רעיון חשוב לא פחות והוא הסתרת המידע (information hiding). כלומר הרתון להסתיר מהמשתמש במחלקה   

                 חלק מהפרטים.

 

דוגמא להסתרת מידע במחלקות

·                    נניח כי אנו רוצים להגדיר את המושג  time  זמן.

 

מאפיין:

·                    כמות שניות.   t

פעולות:

·                    להציג זמן לפי:                                                                           hhhh:mm:ss    show

·                    לקבוע זמן בשניות (עם בדיקה שהוא אינו שלילי).                                                      set(t)

·                    לקדם בדקות                                                                                            inc_min(m)

·                    לקדם בשעות.                                                                                            inc_hur(h)

·                    כל הקידומים יכולים להפחית או להוסיף אך אסור להגיע לזמן שלילי.

 

מצב עצם תקין -  שמורה

·                    סך כל הערכים של מאפייני העצם מכונים מצב העצם או  object state.

·                    לכל עצם קיים טווח ערכים תקין המכונה שמורה  (invriant).

·                    במקרה שלנו, זמן אינו יכול להיות שלילי, לכן השמורה שלנו היא   t >= 0. כלומר אם יושם למשתנה  t  בעצם ערך שלילי הרי העצם

·                    במצב לא תקין.

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

·                    התווית  public:  מגדירה כי יש גישה מכל פונקציה בתוכנית למשתנים ופונקציות השייכות לה.

·                    כל ההבדל בין   class  ו   struct  ב  c++  הוא שב  struct  ברירת המחדל היא public:  וב  class  ברירת המחדל היא  private: כלומר:

//--------------------------------------------------------------------

Text Box: struct X {           class X {         
           same as   public:
...                  ...
};                   };

Text Box: struct X {           class X {         
private:    same as 
...                  ...
};                   };

 

 

 

 

 

 

// file: time.h

//  class time declaration

 

class time {

    long _secs;  // default private:

 public:

    void show();

    void set(long s);

    void inc_min(long m);

    void inc_hur(long h);

private:

    void _inc_sec(long sec);

};

//--------------------------------------------------------------------

 

 


//--------------------------------------------------------------------

// file: time.cpp

//  class time implemantation

#include <iostream.h>

#include "time.h"

 

void time::show()  // show time: hh..hh:mm:ss

{

 cout << (_secs/3600) <<':'<< (_secs/60%60) <<':'

      << (_secs%60) << endl;

}

void time::set(long s)

{

    if (s >= 0)

        _secs = s;

}

void time::inc_min(long m)

{

    _inc_sec(m * 60);

}

void time::inc_hur(long h)

{

    _inc_sec(h*3600);

}

void time::_inc_sec(long sec)

{

    if (_secs + sec >= 0)

        _secs += sec;

}

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

// file: time_use.cpp

//  creating objects from class time and using them

#include "time.h"

 

void main()

{

    time t1, t2;

    t1.show();  // garbage

    t1.set(20);

    t1.show();

      // t1.t = -10; -- compile error: can't access private member t; 

    t1.set(-10); // do nothing

     // t1._inc_sec(11); compile error: can't access private member...

    t1.inc_hur(20);

    t1.show(); 

    t1.inc_min(-30*60); // do nothing

    t2 = t1;

    t2.show();

}

 

------- Example Ouput --------

1838:30:28    -- garbage time

0:0:20

20:0:20

20:0:20

·                    במימוש הפונקציות  public:  הפעלת פונקציית  private:   _inc_set()

·                    אין אפשרות לגשת מתוך הפונקציה החופשית  main()  למשתנים או פונקציות  private:.

·                    לאחר ש  set(s)  הצליחה אין אפשרות להביא את העצם למצב "מקולקל". (פרט להשמה של עצם מקולקל אחר).

 


פונקציות בין השורות  

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

כאשר כותבים פונקציה קטנה כ  inline  ממליצים למהדר לשתול את גוף הפונקציה במקום לקרא לפונקציה. בצורה זו המהדר כותב את גוף הפונקציה במקום פקודת הקריאה לפונקציה.

 

1.      המהדר לא חייב לקבל את ההמלצה להפוך פונקציה ל  inline.

2.      גוף פונקצית  inline  חייב להופיע בהצהרה  (בתוך הקובץ h).

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

4.      לא משתמשים ב  inline  אלא רק אם חייבים לעשות זאת מסיבות יעילות.

 

1. שימוש בפונקציה רגילה

// ........ X.H .........

class X {

public:

    int readm();

private:

    int m;

};

 

//......... X.CPP ........

int X::readm()

{

    return m;

}

 

//......... USE.CPP .......

void f (X aa, X bb)

{

    int a = aa.readm();  // calling to function X::readm()

    int b = bb.readm();

}

 

2. ייעול הקריאה על ידי  inline.

// ........ X.H .........

class X {

public:

    int readm();

private:

    int m;

};

 

inline int X::readm()

{

    return m;

}

 

//......... USE.CPP .......

void f (X aa, X bb)

{

    int a = aa.readm();  // inline, efficient as: a = aa.m;

    int b = bb.readm();

}


3.  כתיבת גוף הפונקציה בהצהרה על הפונקציה משמעותו  inline.

// ........ X.H .........

class X {

public:

    int readm() { return m; } // inline function.

private:

    int m;

};

 

//......... USE.CPP .......

void f (X aa, X bb)

{

    int a = aa.readm(); // inline, efficient as: a = aa.m;

    int b = bb.readm();

}

 

·                    בצורה מיוחד זו אנו גם מצהירים (declare) וגם מגדירים (define) את הפונקציה.

 

חזרה להתחלה

אתחול והריסה של עצם

·                    ראינו כי בזמן יצירת העצם, מצב העצם הוא מקרי וקרוב לודאי לא תקין.

·                    כל שימוש שעושים בעצם הנמצא במצב מחוץ לשמורה יכול לגרום לתוצאה בלתי צפויה. כלומר, לא מה שחשב המתכנת

·                    בזמן שהוא כתב את העצם.

·                    בנאי  (constructor)  -  פונקציית אתחול המופעלת אוטומטית בזמן יצירת העצם.

                      ·        פונקציית אתחול חייבת להיות בעלת שם זהה לשם המחלקה ואיננה מחזירה כל ערך אפילו לא  void.

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

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

                      ·        בנאי ללא ארגומנטים מכונה  default constructor   והוא מופעל אוטומטית.

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

                      ·        בנאי עם ארגומנטים וערכי ברירת מחדל יכול לשמש כ  default constructor.

·                     הורס (destructor) - פונקציית הריסה המופעלת אוטומטית על עצם רגע לפני שהעצם נעלם.

                      ·        פונקציים הריסה בעלת שם זהה לשם המחלקה ומצד שמאל צמוד הסימן   ~.

                      ·        יכולה להיות רק פונקציית הריסה אחת.

                      ·        לפונקציית הריסה אין ארגומנטים.

                      ·        אם מוגדר הורס במחלקה תמיד הוא הוא מופעל לפני הריסת העצם.

//--------------------------------------------------------------------

// file: x.h

class X {

public:

    X();

    ~X();

};

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

// file: x.cpp

#include <iostream.h>

#include "x.h"

 

X::X()

{

   cout << "constructiing X here" << endl;

}

 

X::~X()

{

    cout << "destructing X here" << endl;

}

 

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

// file: x_use.cpp

#include <iostream.h>

#include "x.h"

 

void main()

{

    X a;  // default constructor called here

    cout << "doing something" << endl;

} // destructor called here

 

--- Output ----

constructiing X here

doing something

destructing X here

 

//--------------------------------------------------------------------

// file: y.h

 

class Y {

    int m;

public:

    Y(int i);

    void show();

};

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

// file: x.cpp

#include <iostream.h>

#include "y.h"

 

Y::Y(int i) : m(i)   // same as m = i

{

}

 

void Y::show()

{

    cout << "m is: " << m << endl;

}

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

// file: y_use.cpp

#include <iostream.h>

#include "y.h"

 

void main()

{

    // Y a; -- error: must provide argument to the constructor

    Y b(2);   // constructor called >>> b.m = 2

    b.show();

}

--- Output ----

m is: 2

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

·                    אין חובה להגדיר בנאי או הורס.

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

 

 


//--------------------------------------------------------------------

// file: rect.h

 

class Rect {

    int _width, _hight; // width, hight

public:

    Rect() : _width(0),  _hight(0) {}

    Rect(int w) : _width(w),  _hight(0) {}

    Rect(int w, int h) : _width(w),  _hight(h) {}

   

    void show() { cout << _width << ' ' <<  _hight << endl; }

};

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

#include "rect.h"

 

void main()

{

    Rect r1;      // using default constructor

    Rect r2(4);   // using constructor: Rect(4)

    Rect r3(5, 6) // using constructor: Rect(5, 6)

 

    r1.show();

    r2.show();

    r3.show();

}

//-------Output ----------

0 0

4 0

5 6

·          שימוש בערכי ברירת מחדל לקבלת אפקט של מספר בנאים

·          שימוש בפונקציות  inline

 

//--------------------------------------------------------------------

// file: rect.h

 

class Rect {

    int _width, _hight; // width, hight

public:

    Rect(int w = 0, int h = 0) : _width(w),  _hight(h) {}

    void show() { cout << _width << ' ' <<  _hight << endl; }

};

//--------------------------------------------------------------------

 

//--------------------------------------------------------------------

#include "rect.h"

 

void main()

{

    Rect r1;      // using default constructor: Rect(0, 0)

    Rect r2(4);   // using constructor: Rect(4, 0)

    Rect r3(5, 6) // using constructor: Rect(5, 6)

 

    r1.show();

    r2.show();

    r3.show();

}

//-------Output ----------

0 0

4 0

5 6

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

 

·        אם כל מצב של העצם (כל ערך של משתניו) מותר הרי אין צורך מיוחד בבנאי. לדוגמא המחלקה  p2d  אינה חייבת בבנאי.

·        השמורה של ערכי המלבן היא שאורכו ורחבו אי שליליים.

·        הבנאי שלנו חייב לבנות עצם במצב תקין ולכן חייבים להכניס תקונים.

 

//--------------------------------------------------------------------

// file: rect.h

 

class Rect {

    int _width, _hight; // width, hight

public:

    Rect(int w = 0, int h = 0);

    int area(); // area of rect.

};

//--------------------------------------------------------------------

//--------------------------------------------------------------------

// file: rect.cpp

#include "rect.h"

 

Rect::Rect(int w, int h) // don't declare default arguments here!

: _width(w),  _hight(h)

{

    if (_width < 0)

       _width = -_width;

    if(_hight <= 0)

        _hight = -_hight;

}

 

int Rect::area()

{    return _width * _hight; }

 

//--------------------------------------------------------------------

·                    אסור להצהיר על ערכי ברירת מחדל גם בהגדרת הפונקציה (רק בהצהרה).

//--------------------------------------------------------------------

#include <iostream.h>

#include "rect.h"

 

void main()

{

    Rect a_ziro[3]; // calling default constructor 3 times.

    Rect a[] = {

        Rect(), Rect(2, 4), Rect(-1, 3)

    };

   

    int b, sum = 0;

    for (int i = 0; i < 3; i++)

    {

        cout << (b = a[i].area()) << ' ' << a_ziro[i].area() << ' ';

        sum += b;

    }

    cout << sum << endl;

}

//-------Output ----------

0 0 8 0 3 0

11

·                    הגדרנו מערך של עצמים, כולל אתחול.

·                    רואים כי הבנאי מונע הגעת עצם למצב שלילי

·                    אנו משתמשים ב- b  בכדי לחסוך חישוב כפול של  a[i].area()

·                    כאשר מגדירים מערך של עצמים, ומוגדר default constructor (פונקצית אתחול שאפשר לקרא לה ללא ארגומנטים), אזי בזמן יצירת   

        המערך קוראים ל default constructor  עבור כל עצם במערך.


copy constructor

                           ·      יוצרים עותק של עצם בכל אחד מהמקרים הבאים

                              ‏א.            בזמו שמאתחלים עצם אחד עם עצם אחר מאותו הסוג.

                               ‏ב.            בזמן שפונקציה מקבלת  עצם  by value

                                ‏ג.            בזמן שפונקציה ממחזירה  עצם  by value

 

void f(A a)  {  //... }

 

A g()        {  A b(5);  return a; }

 

i.         בעיה:  נניח כי אני רוצה בכל העתקה של עצם לאפס את המונה  count. 

ii.       פתרון:  הגדרת  copy constructor.

 

class A {

       int n;

   int count

public:

   A(int n) : n(n), count(0) {}

   A(const A& other) : n(other.n), count(0) {}

   void prn() {cout << n<<endl; count++; }

};

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

 

void main()

{

   A a1(2);

   A a2 = a1; // a2.count is 0

   f(a1);     // a.count (in f) is 0

   g();       // (copy of b).count is 0

}

i.         עיקר השימוש ב  copy constructor  הוא בזמן שהמחלקה מכילה מצביעים (נראה בהמשך).


 תחום הכרה    וטווח קיום 

עבור כל משתנה או עצם המוגדר בתוכנית קימות שתי תכונות:

         1.            תחום הכרה  (scope) - מושג הקשור לטקסט התוכנית, אותו חלק מהתוכנית שבו מוכר המשתנה, כלומר שמותר לכתוב קוד המתיחס   

       אליו.

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

 

·        למשתנה/עצם גלובלי או  static

·        טוח קיום - משך כל זמן ריצת התוכנית.

·        טוח הכרה

o       אם מוצהר כגלובלי, מורכ ממקום ההצהרה ועד סוף הקובץ.

o       אם מוצהר בתוך בלוק - מוכר ממקום ההצהרה ועד סוף הבלוק (כולל בלוקים פנימיים).

 

·        למשתנה/עצם אוטומטי (מוגדר בתוך בלוק):

·        טוח קיום - נוצר ברגע מעבר הבצוע על מקום ההגדרה שלו ומסיים את חייו ברגע יציאה מהבלוק בו הוא מוגדר.

·        טוח הכרה - מוכר ממקום ההצהרה ועד סוף הבלוק (כולל בלוקים פנימיים).

·        אסור להצהיר באותו בלוק על אותו משתנה פעמיים.

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

 

·        אופרטור אבחנת הטווח scope resolution operator  '::' הפועל בצורה:

 

class-name::identifier

 

    class-name  הוא שם המחלקה אשר המזהה  identifier  (משתנה או פונקציה) הוצהרו בתוכה.

    אם משמיטים את  class-name  אזי שם מהצורה   ::identifier  מתייחס למשתנה גלובלי או

    לפונקציה חופשית.

 

דוגמאות:

1. טווח הכרה בין פונקציות ומשתנים גלובליים.

int y = 6; // global variable

void f()

{

    int x = 4;

    cout << x << endl; // OK - prints 4

    cout << y << endl; // OK - prints 6

    cout << z << endl; // ERROR: unknown variable z,

}

void main()

{

    f();   // OK

    g(5);  // ERROR: function g() should have a prototype

    cout << x << endl; // ERROR: unknown varaible x!

    cout << y << endl; // OK! - prints 6

    cout << z << endl; // ERROR: at this point z is undefined.

}

 

int z = 9; // global variable.

void g(int x)

{

    cout << x << endl; // OK - print value of argument x (5)

    cout << y << endl; // OK - prints 6

    cout << z << endl; // OK - prints 9

}


 

2. טווח הכרה בתוך בלוקים.

int x = 0;      // global

 

void f()

{

    int x;      // define first local x, which hides global x.

    x = 1;      // assign to first local x.

    {

        x = 2;  // assign to first local x.

        int x;  // define second local x, which hides first local x.

        x = 3;  // assign to second local x.

        ::x = 4;// assign to global x.

    }

    x = 3;      // assign to first local x.

}

 

int y = x;      // initialize global y with the value of global x.

 

 

 

3. בתוך מחלקה

 

int a = 2;

 

class X {

public:

   void f();

   void g(int a);

 

private:

   int a;

};

 

 

void X::f()

{

    ::a = 5;  // assign to global a

    a   = 4;  // assign to member a (in private)

}

 

 

void X::g(int a)

{

    ::a  = 5;  // assign to global a

    a    = 4;  // assign to argument a 

    X::a = 6;  // assign to private a!

}

בפונקציה g()  הארגומנט  a  מסתיר את המשתנה הפרטי  a  המוגדר כחבר במחלקה.

 


עצמים קבועים 

5.      המילה השמורה  const  באה לציין כי אסור לשנות הערך.

6.      בתוכניות  C++  יש שימוש רב  ב const.

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

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

 

int global = 10;

const int& GetGlobal() // return a reference to constant

{

    return global;

}

 

void main()

{

    const int size = 50;

    const int v[] = {1, 2, 3};

    size++; // ERROR

    v[1]++; // ERROR

 

 

    int&  rg = GetGlobal(); // ERROR - can not convert const

                            //        to non const.

    const int& crg = GetGlobal(); // OK

    crg = 12;                     // ERROR

 

          int  a  = 1;

    const int  c  = 2;

    const int& r1 = c; // OK

    const int& r2 = a; // OK

    int& r3 = c;       // ERROR

    r3 = 7;            // change the value of c.

    a = 33;            // OK

}

const memeber functions

9.       מעבירים ארגומנטים   by reference  גם בכדי למנוע העתקה של עצמים גדולים (יעילות).

10.  אין אפשרות לשנות נתונים של עצם קבוע.

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

 class Big{

public:

    int  Get(int i) const  { return a[i];   }

    void Set(int i, int d) { a[i] = d;      }

    int  pub;

private:

    int a[50];

};

 

// using call by reference for avoiding copy of a large object.

// using const to protect from change (similar to call by value)

void f(Big& b, const Big& cb)

{

    int a = 4;

    b.Set(3, a);   // OK

    a = b.Get(3);  // OK

    b.pub = 6;     // OK

    cb.Set(3, a);  // Error: using non const function on const

                   //        object.

    a = cb.Get(3); // OK:  using a const function.

    cb.pub = 6;    // Error: changing a const object.

}


 mutable in class

·      אם מוגדר לנו משתנה כ  mutable   מותר לשנותו גם בתוך const member function .

class A {

    mutable int print_count;

    int n;

public:

    void print() const { print_count++; cout << n; }

};

·      מנגנון ה  mutable  עוזר לנו לכתוב פונקציה קבועה (מבחינה לוגית), אף על פי שמחינה פיזית היא איננה כזו.

משתנה ופונקציה סטטי  במחלקה 

·                    משתנה סטטי במחלקה (static memeber varaible)  הוא דומה למשתנה גלובלי בתוכנית, במובן:

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

                      ·        במובן זה המשתנה הוא גלובלי עבוא עצמי המחלקה.

                      ·        כל שינוי שנעשה בו מורגש בכל העצמים מטיפוס המחלקה.

                      ·        גם עצם ממחלקה כלשהי יכול להיות  static member.

·                    משתנה סטטי במחלקה קיים כל זמן קיום התוכנית (כמו משתנה סטטי רגיל) ולכן:

                      ·        חייבים להגדיר אותו בנפרד, בדומה למשתנה גלובלי.

                      ·        אפשר לגשת אליו אפילו אם לא קיימים עצמים מטיפוס המחלקה.

 

·                    פונקציה סטטית במחלקה  (static memeber function) היא פונקציה שמותר לה להשתמש, ממה שיש במחלקה, 

·                    רק במשתנים ופונקציות  static. לכן:

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

                      ·        לכן אפשר להפעיל אותה אפילו אם לא מוגדר עצם כלל מטיפוס המחלקה.

 

 

 

//----------------------------------------------------------------

// file:occcnt.h

// class OccCnt -  Occurrence Count

 

class OccCnt {

    static int _cnt;  // count number of occurrences of class

    int _a;

public:

    OccCnt() : _a(0) { _cnt++; }

    ~OccCnt() { _cnt--; }

   

    static void show_occ();

    // static void inca() { _a++; } // Error: must use static memeber only

    void inca() { _a++; }

    void show() const;   

};

 

//----------------------------------------------------------------

// OccCnt.cpp

 

#include <iostream.h>

#include "occcnt.h"

 

// defining static member similar to globla

int OccCnt::_cnt = 0;

 

void OccCnt::show_occ() { cout << _cnt << endl; }

 

void OccCnt::show() const    // can't be static function (using _a)

{ cout << _a << "  " << _cnt << endl; }

 


//----------------------------------------------------------------

// OccCnt_use.cpp

#include "occcnt.h"

 

void main()

{

    OccCnt::show_occ(); // dose not need an object

    OccCnt a;   

    OccCnt::show_occ();

    if (1 < 2)

    {

        OccCnt b;

        OccCnt::show_occ();

        b.inca();

        b.show(); // need an object

        OccCnt c;

        OccCnt::show_occ();

        OccCnt d[10];

        OccCnt::show_occ();

    }

    OccCnt::show_occ();

}

 

//------ Output ------------

0

1

2

1  2

3

13

1

חזרה להתחלה


 enum  - enumeration constants

·                        enum זוהי שיטה להגדיר סדרה של ערכים קבועים מטיפוס int .

enum {FALSE, TRUE};

               0      1

·                        כל שם ברשימה הוא קבוע מטיפוס int.

·                        הערך של השם הראשון הוא 0 והבא אחריו 1,  2, ...  .  אם נתנו ערך אחר במפורש, ממשיכים למספר מאותו ערך.

enum {C1, C2=4, C3, C4};

           0    4       5     6

·                        מותר להגדיר טיפוס  enum:

enum escapes {BELL = ‘\a‘, BACKSPACE = ‘\b‘, TAB = ‘\t‘ };

enum month {JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

 

·                        השמות המוגדרים ב enum מוכרים לפי חוקי ה scope הרגילים (מוכרים בתוך הבלוק בו הוגדרו וכו').

·                        כל השמות המוגדרים בתוך enum (אחד או יותר) ונמצאים באותו scope חייבים להיות שונים.

enum {AA, BB};

enum {BB, CC}; // Error: BB already defined

 

·                        הערכים בתוך ה enum אינם חייבים להיות שונים.

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

enum (ONE = 1, TWO, FOUR = TWO * 2, EIGHT = FOUR * TWO};

 

·                        בדומה למחלקות אפשר להגדיר משתנה מטיפוס  enum.

enum {ONE, TWO} o, t;

enum Boolean {FALSE, TRUE} a, b;

Boolean c, d;

month m;

                           ·                        משתנה enum הוא משתנה היכול להכיל כל ערך מטיפוס int. ההבדל בינו לבין משתנה מטיפוס  int "רגיל"

           הוא  שהמהדר יזהיר על השמה של ערך מטיפוס  int או קבוע int שאינו מוגדר ברשימה של  ה enum לתוך

           משתנה enum.

enum boolean {FALSE, TRUE};

class X {

public:

    enum size {BIG = 1000, MEDIUM = 200, SMALL = 10};

    void SetB(boolean bb);

    void SetSz(size s);

 

private:

    boolean b;

    size sz;

};

 

void X::SetB(boolean bb) { b = bb; }

void X::SetSz(size s)    { sz = s; }

 

void main()

{   boolean b1 = TRUE;       // uses global boolean

// The type of the variable godel is size as defined for class X, but

// the variable godel itself is not a part of an object of class X.

// Furthermore we use X::BIG because BIG is defined in class X

    X::size godel = X::BIG; 

    X o;

    o.SetB(FALSE);

    o.SetSz(X::SMALL);

 

    b1 = 2; // compiler warning: int assigned to enum.

}

 

חזרה להתחלה