תוכן
הפרק
השוואה
בין פונקציה חופשית ופונקציה חברה במחלקה
בכלי
התכנותי "מחלקה" יש הרבה יותר מסתם איסוף של מספר נתונים למושג אחד.
ב 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: כלומר:
//--------------------------------------------------------------------
// 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 עבור כל עצם במערך.
·
יוצרים
עותק של עצם בכל אחד מהמקרים הבאים
א.
בזמו
שמאתחלים עצם אחד עם עצם אחר מאותו הסוג.
ב.
בזמן
שפונקציה מקבלת עצם 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
}
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
מותר לשנותו גם בתוך 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 זוהי שיטה להגדיר סדרה של ערכים קבועים מטיפוס 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.
}