תוכן הפרק
הגדרת
מצביע לקיחת כתובת משתנה (unary operator &) וגודלמשתנה בבתים (operator sizeof)
גישה למשתנה דרך מצביע (unary operators * )
המצביע this בתוך פונקצית של מחלקה.
מערכים
בגודל דינאמי - באמצעות
new delete
·
בזמן
ריצת התוכנית כל משתנה או עצם שמור בזכרון המחשב.
·
לכל
בית בזכרון המחשב יש כתובת.
·
אם
גודל המשתנה/עצם a בבתים (sizeof
a) גדול מבית אחד הרי המשתנה מאוחסון בגוש רציף של בתים בזכרון.
·
הבית
בעל הכתובת הנמוכה ביותר, שממנו מתחיל שטח אחסון העצם, מכונה כתובת העצם (object address) ..
·
משתנה
המיועד להכיל כתובת של עצם/משתנה אחר מכונה מצביע (pointer).
·
אם
מצביע מכיל כתובת של עצם בזכרון, אפשר, באמצעות המצביע, לפנות לכתובת ולפנות
למשתנה עצמו.
#include <iostream.h>
void main()
{
int n1 = 2, n2 = 3;
int *p; // p is a pointer to int
p = &n1; // p recives the adress of varaible n1
cout << "Value of n1 is: " << n1 << endl;
cout << "Adress of n1 is: " << p << endl;
p = &n2;
cout << "Value of n2 is: " << n2 << endl;
cout << "Adress of n2 is: " << p << endl;
cout << "Address of p itself: " << &p << endl;
cout << "Size of varaible n1 in bytes is: " << sizeof(n1) << endl;
cout << "Size of type int in bytes is: " << sizeof(int) << endl;
}
//---- -Output ---------
Value of n1 is: 2
Adress of n1 is: 0x0064FDEC
Value of n2 is: 3
Adress of n2 is: 0x0064FDF0
Address
of p itself: 0x0064FDF4
Size of varaible n1 in bytes is: 4
Size of type int in bytes is: 4
n1: n2: p:
· int *p פירושו שהמשתנה בשם p מיועד להכיל כתובת של משתנה מטיפוס int.
·
האופרטור
האונרי & -
מחזיר כתובת של משתנה או עצם.
·
p בתחילה מכיל כתובת
של משתנה n1.
·
לאחר
מכן p מכיל כתובת של
משתנה n2.
·
האופרטור sizeof
מחזור גודל בבתים של עצם/משתנה (לדוגמא n1) או
טיפוס.(לדוגמא int).
·
לסימן
* מספר משמעויות:
1.
הגדרת
משתנה מסוג מצביע לדוגמא: int *p;
2.
אופרטור
אונרי - גישה למשתנה שהמצביע p מצביע עליו: *p = 8
3.
אופרטור
בינארי - כפל: a = b * c.
·
לאחר
ההשמה p
= &n המשתנה n והבטוי *p שקולים.
·
מצביע
מוגדר כמצביע רק לטיפוס (int
double ...) מסויים. אין
אפשרות להשים למצביע כתובת של משתנה מטיפוס שונה מזה שהוא מוגדר להצביע עליו.
#include <iostream.h>
void main()
{
int n = 7;
int *p; // p is a pointer to int
p = &n; // p recives the adress of varaible n1
double d = 3.1415;
// p = & d; - error: p can point only to varaible of type int!
double *q; // q is a pointer to double
q = &d; // OK
cout << "Value of *p is: " << *p << endl;
cout << "Value of n is: " << n << endl;
cout << "Adress of n is: " << p << endl;
*p = 9; //
cout << "Value of *p is: " << *p << endl;
cout << "Value of n is: " << n << endl;
++*p;
cout << n << endl;
*p = *p * *p;
cout << n << endl;
cin >> *p; // same as cin >> n;
cout << n << endl;
cout << "Value of d is: " << d << endl;
cout << "Value of *q is: " << *q << endl;
cout << "Adress of d is: " << q << endl;
}
//---- Output --------
Value of *p is: 7
Value of n is: 7
Adress of n is: 0x0065FDE4
Value of *p is: 9
Value of n is: 9
10
100
5
Value of d is: 3.1415
Value of *q is: 3.1415
Adress of d is: 0x0065FDE8
·
מצביע
יכול להצביע גם על עצמים.
·
אם p מצביע לעצם בכדי לגשת לאיבר
x בעצם חייבים לכתוב
(*p).x אם נכתוב *p.x הכוונה היא
*(p.x).
·
בגלל
זאת הכינו קיצור בשפה במקום
לכתוב (*p).x כותבים p->x.
#include <iostream.h>
#include <math.h>
class p2d {
public:
int x, y;
double length() const { return sqrt(x * x + y * y); }
};
void main()
{
p2d p1, p2;
p2d *q; // q is a pointer to object of class p2d
q = &p1;
(*q).x = 3;
(*q).y = 3;
cout << "length of p1 is: " << (*q).length() << endl;
// using the more elegant ->
q->x = 4;
q->y = 6;
cout << "length of p1 is: " << q->length() << endl;
p2 = *q; // p2 = p1
cout << "length of p2 is: " << p2.length() << endl;
q->x = 1; // changing only p1
q->y = 2;
cout << "length of p1 is: " << q->length() << endl;
cout << "length of p2 is: " << p2.length() << endl;
}
// --- Output ---
length of p1 is: 4.24264
length of p1 is: 7.2111
length of p2 is: 7.2111
length of p1 is: 2.23607
length of p2 is: 7.2111
#include <iostream.h>
int sum_ptr_arr(int* par[], int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
sum += *par[i];
return sum;
}
void main()
{
int* a[3]; // a is array of 3 pointers to int;
int x = 1, y = 2, z = 3;
int i; // used by for
a[0] = & x;
a[1] = & y;
a[2] = & z;
for (i = 0; i < 3; i++)
cout << a[i] << ": " << *a[i] << endl;
cout << endl;
for (i = 0; i < 3; i++) // increment each varaible
++*a[i];
for (i = 0; i < 3; i++)
cout << *a[i] << ", ";
cout << endl;
cout << x << "--" << y << "--" << z << endl;
cout << "sum is: " << sum_ptr_arr(a, 3) << endl;
}
//---Output---
x0064FDE8: 10
0x0064FDE0: 2
0x0064FDDC: 3
2, 3, 4,
2--3--4
sum is: 9
· a הוא מערך שכל איבר בו הוא מצביע ל int, כלומר a[i] הוא מטיפוס מצביע ו *a[i] הוא מטיפוס int.
·
אפשר
להעבור מערך מצביעים כארגומנט לפונקציה.
מוגדר default constructor שיופעל. בדוגמא שלנו pa[1] מאותחל על ידי p2d(0,
0) - שהוא בנאי ברירת המחדל.
#include <iostream.h>
#include <math.h>
class p2d {
int x, y;
public:
p2d(int xx=0, int yy=0) : x(xx), y(yy) {}
double length() const { return sqrt(x * x + y * y); }
void show() const { cout << '(' << x << ", " << y << ')'; }
void trans(int dx, int dy) { x += dx; y += dy; }
};
// translate points in array of pointers to p2d's
void translate_parr(p2d* par[], int n, int dx, int dy)
{
for(int i = 0; i < n; i++)
par[i]->trans(dx, dy);
}
// show points in array of pointers to p2d's
void show_parr(p2d* par[], int n)
{
for(int i = 0; i < n; i++)
{
par[i]->show();
cout << endl;
}
}
void main()
{
p2d p1(1, 2), p2(3, 4);
p2d a[2] = {p2d(5,6)};
p2d* pa[4];
pa[0] = &a[0];
pa[1] = &p1;
pa[2] = &p2;
pa[3] = &a[1];
show_parr(pa, 4);
translate_parr(pa, 4, -2, 4);
show_parr(pa, 4);
}
//----Output--------
(6 ,5)
(1, 2)
(3, 4)
(0, 0)
(3, 10)
(-1, 6)
(1, 8)
(-2, 4)
·
כל
פונקציה של מחלקה חייבת להיות מופעלת על עצם, לדוגמא: object.f()
·
המילה
השמורה this משמעותה (רק בתוך פונקציה של מחלקה)
מצביע לעצם שעליו מופעלת הפונקציה.
#include
<iostream.h>
class B {
int n;
public:
B() n(0) {}
void f() { cout << this << endl; }
void g() { cout << this->n << endl; } // this->n is n
B h() { return *this; } // returns a copy of this object
};
void main()
{
B b;
b.f(); // prints this which is address of b
cout << &b << endl; // prints address of b
b.g(); // print 0
B b1 = b.h(); // asign a copy of b into b1
}
output:
0x0064FDF4
0x0064FDF4
עד
היום הכרנו מספר אופני הקצאת זכרון:
·
לעצם
אוטומטי - נוצר בזמן הכניסה לבלוק ונעלם כשיוצאים ממנו.
·
לעצם
סטטי - קיים כל זמן קיום התוכנית.
בעיה:
·
גודל
הזכרון שאנו מקצים חייב להיות נקבע בזמו כתיבת התוכנית.
·
לדוגמא:
גודל של מערך חייב להיות מספר קבוע הידוע בזמן ההידור.
·
אין
לנו אפשרות להחליט על כמות הקצאת זכרון בזמן ריצה.
את
הבעיות הללו פותרים האופרטורים new ו delete
void
main()
{
int *p; // p contains garbage address!!!.
// *p = 10; // Error: never! use uninitialized varaiable.
int n;
p = &n;
*p = 10; // fine.
p = new int; // creating a new int in heap memory.
*p = 10; // storing 10
delete p; // free memory that p points to
}
· new T - מקצה בזכרון הערימה עצם מטיפוס T.
·
P מקבל את כתובת העצם ולכן אפשר לעבוד עמו.
- לעצם אין שם, כמו למשתנה.
·
delete p - משחרר את שטח הזכרון ש new יצר. p חייב להיות אותה הכתובת בדיוק ש new יצר!
·
אם
לא מבצעים delete על הכתובת שב- p שטח הזכרון ישאר מוקצה עד סיום
התוכנית. בלי קשר לאיזו פונקציה עשתה
new.
class X {
int a, b;
public:
X(int aa, int bb) : a(aa), b(bb) {}
~X() { cout << "destructing" << endl; }
vois show() const { cout <<a << ", " << b << endl;}
};
void main()
{
X *q;
// q = new X; // Error: must provide arguments to constructor
q = new X(4, 5); // fine
q->show(); // prints: 4, 5
delete q; // prints: destructing - destructor operated.
}
· new יכול ליצור גם עצמים -ב heap memory.
·
אם
יש למחלקה בנאי חייבים לספק לו ארגומנטים - אלא אם יש גם default
constructor.
·
delete מפעיל את ה destructor "רגע" לפני שמשחררים את זכרון העצם.
int
a[5];
מגדיר בלוק זכרון רציף המכיל חמישה intים עוקבים.
a: a[0] a[1] a[4]
int
*pa = &a[0];
a[0] a[1] a[4]
הבטוי
pa מצביע לאיבר הראשון
במערך; הוא מכיל את הכתובת של a[0].
אם pa מצביע לאיבר מסויים במערך הרי הבטוי:
לכן
אם pa מצביע ל a[0]:
|
|
בכדי לחשב את ערך הבטוי pa + i מוסיפים לכתובת ב pa i*sizeof(*pa)בתים (sizeof ערכו
מספר הבתים הדרוש לאחסן את
ערך הביטוי שהוא מופעל עליו -
יוסבר בהמשך).
יש
קשר הדוק בין אינדקסים של מערכים ואריתמטיקה של מצביעים:
a[i] º *(a + i)
&a[i] º &(*(a + i)) º a + i
המהדר של C++ הופך כל מופע של a[i] ל *(a+i) .
סכנה!!: מותר להשתמש במצביע רק אם אתחלנו אותו
בכתובת של משתנה, עצם, מערך או גוש זכרון שהוקצה בתהליך מיוחד (new).
ה bug הנפוץ ביותר ב c++ והקשה ביותר לגילוי הוא פניה דרך מצביע
לשטח זכרון אסור:
גרוע
יותר הוא שהתוכנית עשויה לפעול בצורה מוזרה בגלל שינוי לא מכוון של נתונים בזכרון
(מריחת זכרון). ובסופו של דבר התוכנית עשויה לעוף בגלל בעיה שנוצרה בשלבים מוקדמים
יותר של הריצה.
pa =
a; // O.K.
pa++; // O.K.: pa points to next element
a = pa; // Error: a is constant (array name is a constant pointer)
a++; // Error: for the same reason
כאשר
מעבירים שם של מערך כארגומנט לפונקציה:
לכן
נוצר המצב שהמערכים עוברים על ידי call by reference,
בגלל שמועבר על ידי call by value שם המערך, שהוא
מצביע לאיבר הראשון במערך.
p =
new T[n];
· new T[n] - מקצה גוש זכרון רציף של n איברים, כל אחד מטיפוס T.
·
n יכול להיות ערך של מתשנה שיקבע רק בזמן
ריצה - מערך דינאמי.
·
p מצביע לאיבר הראשון במערך של עצמים
מטיפוס T.
·
delete[] p מוחק את המערך ש new הקצה - הכתובת ב p חייבת להיות אותה הכתובת ש new יצר.
#include <iostream.h>
void main()
{
char *s;
cin >> s; // Error: MUST initialize pointer
int n;
cout << "enter max length of word: ";
cin >> n;
s = new char[n];
cin >> s; // read onr word from input.
cout << s << endl;
// delete s; // Error: must use delete[] - array
delete[] s;
}
· אסור לבלבל בין הקצאה- שיחרור של עצם בודד לבין אלה של מערך:
#include
<iostream.h>
void main()
{
int *a;
cin >> n; // read number of numbers.
a = new char[n];
for (int i = 0; i < n; i++)
cin >> a[i];
for (i = n-1; i >= 0 ; i--)
cout << a[i] << endl;
delete[] a;
}
class
X {
int a, b;
public:
X(int aa, int bb) : a(aa), b(bb) {}
~X() { cout << "destructing" << endl; }
vois show() const { cout <<a << ", " << b << endl;}
};
void main()
{
X *a;
a = new X[5]; // Error: must provide arguments to constructor
}
· כאשר מקצים מערך של עצמים וקיים בנאי, חייב להיות גם default constructor!!
·
ה default
constructor מופעל על כל עצם
במערך!
·
כאשר
הורסים את המערך, אם קיים destructor
הוא מופעל על כל עצם במערך.
class
X {
int a, b;
public:
X() : a(0), b(0) {}
X(int aa, int bb) : a(aa), b(bb) {}
~X() { cout << "destructing" << endl; }
vois show() const { cout <<a << ", " << b << endl;}
};
void main()
{
X *a;
a = new X[5]; // fine: call default constructor 5 times!
for(int i = 0; i < 5; i++)
a[i].show();
delete[] a; // call destructor 5 times!
}
·
נגדיר
מערך של מצביעים לעצמים:
void
main()
{
X* *ap; // ap pointer to (pointer to X)
cout << "Enter number of objects: ";
int n;
cin >> n;
ap = new X*[n]; // creating new array of pointers
for (int i = 0; i < n; i++)
{
cout << "Enter object: a b: ";
int x, y;
cin >> x >> y;
ap[i] = new X(x, y);
}
for (i = 0; i < n; i++)
ap[i]->show();
// more complex deletion!!
for (i = 0; i < n; i++) // delete each object
delete ap[i];
delete[] ap; // delete array of pointers ap
}
· רוצים להגדיר מערך עם עצמים, שכל עצם יאותחל בערכים שונים מ default.
·
מקצים
מערך של מצביעים, ולאחר מכן מקצים + אתחול כל עצם בנפרד.
·
גם
המחיקה היא מורכבת יחסית: מוחקים כל עצם ולאחר מכן את מערך המצביעים.
·
הכלל
- לכל new חייב להתבצע ה delete ההופכי לו.