3 - קרן כליף
Download
Report
Transcript 3 - קרן כליף
מצביעים
קרן כליף
© Keren Kalif
ביחידה זו נלמד:
2
מהו מצביע (פוינטר)
מוטיבציה למצביעים
אופרטורים * ו& -
אתחול מצביע
העברת פרמטר לפונקציה by pointer
מצביע const
הקשר בין מערך לכתובת
פעולות חיבור וחיסור עם כתובות
מצביע מטייל על מערך
העברת מערך לפונקציה
הבעייתיות בהחזרת מערך מפונקציה
מערך של כתובות
העברת מצביע לפונקציה לצורך שינוי הצבעתו
© Keren Kalif
מוטיבציה
3
החזרת יותר מערך אחד מפונקציה
שפונקציה תוכל לשנות את הפרמטרים שהיא קיבלה
העברת מערכים לפונקציות
הקצאת מערכים בגודל לא ידוע בזמן קומפילציה – הקצאה
דינאמית (לא נראה ביחידה זו)
© Keren Kalif
מהו מצביע (פוינטר)
עד כה ראינו טיפוסים שונים:
דוגמאות:
4
– intמכיל מספר שלם
– doubleמכיל מספר עשרוני
– charמכיל תו
מצביע הוא טיפוס המכיל כתובת של משתנה אחר
עבור כל טיפוס שלמדנו עד כה יש מצביע מהטיפוס המתאים
(למשל מצביע לתא המכיל ,intמצביע לתא המכיל double
וכו')
גודלו של משתנה מטיפוס מצביע הוא 4בתים
© Keren Kalif
הגדרת מצביע
כדי להגדיר מצביע:
;><type>* <var_name
למשל:
5
הגדרת משתנה המצביע למשתנה אחר מטיפוס :int
;int* ptr
הגדרת משתנה המצביע למשתנה אחר מטיפוס :char
;char* ptr
© Keren Kalif
אופרטור &
כל משתנה נמצא בזיכרון בכתובת כלשהי
כדי לקבל את הכתובת של משתנה כלשהו נשתמש
באופרטור &
את הכתובת שנקבל נוכל לשים במשתנה מטיפוס
מצביע מהטיפוס המתאים
6
© Keren Kalif
אופרטור & -דוגמא
1000
1000
3
???
int: x
1004
1004
???
int*: pX
1004
1000
1008
1008
1008
יודפסx=3 :
יודפסaddress of x = 1000 :
יודפסpX = 1000 :
יודפסaddress of pX = 1004 :
)(void main
{
;int x = 3
כדי להדפיס כתובת
;int* pX = &x
משתמשים ב%p -
;)printf("x = %d\n", x
;)printf("address of x = %p\n", &x
;)printf("pX = %p\n", pX
;)printf("address of pX = %p\n", &pX
אנו רואים שהכתובות במחשב הן לא
מספרים דצימאלים ,אלא הקסה-דצימאלים!
בדוגמאות שלנו נמשיך להשתמש
בכתובות בבסיס דצימאלי
7
}
© Keren Kalif
נשים לב..
8
הדרך היחידה לתת ערך למשתנה מטיפוס מצביע היא ע"י מתן
כתובת של משתנה אחר ע"י האופרטור & ,או השמה ממשתנה
המכיל מצביע מאותו טיפוס
לא ניתן לבצע השמה עם מספר
לא נבצע השמה של כתובת למשתנה מצביע שאינו מאותו
טיפוס (למשל מכתובת של doubleלכתובת של )int
© Keren Kalif
דוגמאות
void main()
{
int x = 3;
double* pDouble;
int* pInt1, *pInt2;
pInt1 = &x;
pInt1 = 1000;
pDouble = pInt1;
pInt2 = pInt1;
}
int: x
כאשר מגדירים כמה מצביעים בשורה
צריך להגדיר * לפני כל אחד,אחת
// cannot convert from int to int*
// cannot convert from int* to double*
???
3
1000
???
1004
int*: pInt1
???
1000
1008
int*: pInt2
???
1000
1012
double*: pDouble
כל משתנה מטיפוס מצביע תופס
בלי קשר לגודל, בתים בזיכרון4
הטיפוס אליו הוא מצביע
9
© Keren Kalif
דוגמא להדפסת משתנים בפורמט שונה
void main()
{
int x = 97;
9710 == 6116
12FF6016== 124502410
printf("as int:
%d\n", x);
printf("as char:
%c\n", x);
printf("as address(hexa): %p\n\n", x);
printf("&x as address:
%p\n", &x);
printf("&x as int:
%d\n", &x);
as int: 97
as char: a
as hexa: 00000061
&x as addess: 0012FF60
&x as int: 1245024
}
10
© Keren Kalif
* אופרטור
כדי לפנות לתוכן שבכתובת אליה אנו מצביעים נשתמש
* באופרטור
void main()
{
int x = 3, y;
int* pX = &x;
1000 תפנה לתוכן שבכתובת
y = *pX;
// same as y = x;
printf("x = %d\n", x);
}
printf("*pX = %d\n", *pX);
int: x
3
1000
printf("y = %d\n", y);
int: y
???
3
1004
1000
1008
int*: pX
11
© Keren Kalif
דוגמא
void main()
{
int x = 7;
int* px = &x;
int** ppx = &px;
int*** pppx = &ppx;
int: x
7
int*: px
1000
int**: ppx
1004
int***: pppx
1008
printf("x
= %d \t &x=%p\n", x, &x);
printf("px = %p &px=%p\n", px, &px);
printf("ppx = %p &ppx=%p\n", ppx, &ppx);
printf("pppx = %p &pppx=%p\n", pppx, &pppx);
printf("pppx = %p\n", pppx);
printf("*pppx = %p\n", *pppx);
printf("**pppx = %p\n", **pppx);
printf)“***pppx = %d\n", ***pppx);
1000
1004
1008
1012
x = 7 &x= 1000
px = 1000 &px=1004
ppx = 1004 &ppx=1008
pppx = 1008 &pppx=1012
pppx = 1008
*pppx = 1004
**pppx = 1000
***pppx = 7
}
12
© Keren Kalif
אתחול מצביעים
כמו כל משתנה אחר ,גם משתנה מטיפוס מצביע מכיל זבל עד אשר
הוא מאותחל
כאשר ננסה לפנות לתוכן של מצביע שהתוכן שלו הוא זבל –
התוכנית תעוף!! כי למעשה אנחנו מנסים לגשת לתא זיכרון שלא
קיים
)(void main
{
;int x = 3, y
;int* pX
;y = *pX
}
13
1000
3
int: x
1004
???
int: y
1008
???
int*: pX
© Keren Kalif
איך נראה מצביע מזובל בקומפיילר
ראינו שהכתובות שאיתן אנו מדגימים אינן הכתובות
שהקומפיילר משתמש
כתובת מזובלת בקומפיילר אינה ??? אלא כתובת מיוחדת
בהקסה-דצימלי:
)(void main
{
;int x
פה התוכנית תעוף כי מנסים לגשת לכתובת מזובלת
;int* pX = &x, *p
;)printf("pX=%p, p=%p\n", pX, p
}
14
© Keren Kalif
:(הנחה
)1( ?מצביעים – מה יקרה בתוכנית
)1000 הזיכרון מתחיל בכתובת
void main()
{
int num1=10, num2=20;
int *p1, *p2;
printf("&num1 = %p\n", &num1);
printf("&p1 = %p\n", &p1);
}
int: num1
10
1000
int: num2
20
1004
int*: p1
???
1008
int*: p2
???
1012
&num1 = 1000 :יודפס
&p1 = 1008 :יודפס
15
© Keren Kalif
:(הנחה
)2( ?מצביעים – מה יקרה בתוכנית
)1000 הזיכרון מתחיל בכתובת
void main()
{
int num1=10, num2=20;
int *p1, *p2;
p1 = &num1;
num2 = *p1;
p1 = 1000 :יודפס
printf)“p1=%p\n”, p1);
printf)“num2=%d\n”, num2);
printf)“&num2=%p\n”, &num2);
num2 = 10 :יודפס
&num2 = 1004 :יודפס
}
int: num1
10
1000
int: num2
20
10
1004
int*: p1
1000
???
1008
int*: p2
???
1012
16
© Keren Kalif
:(הנחה
)3( ?מצביעים – מה יקרה בתוכנית
)1000 הזיכרון מתחיל בכתובת
void main()
{
int num1=10, num2=20;
int *p1, *p2;
..התוכנית תעוף כי מנסים לגשת לתוכן של זבל
*p1 = num1;
num2 = *p1;
printf)“num2=%d\n”, num2);
}
int: num1
10
1000
int: num2
20
1004
int*: p1
???
1008
int*: p2
???
1012
17
© Keren Kalif
:(הנחה
)4( ?מצביעים – מה יקרה בתוכנית
)1000 הזיכרון מתחיל בכתובת
void main()
{
int num1=10, num2=20;
int *p1, *p2;
p1 = &num1;
*p1 = num2;
printf)“num1=%d\n”, num1);
num1 = 20 :יודפס
}
int: num1
20
10
1000
int: num2
20
1004
int*: p1
???
1000
1008
int*: p2
???
1012
18
© Keren Kalif
:(הנחה
)5( ?מצביעים – מה יקרה בתוכנית
)1000 הזיכרון מתחיל בכתובת
void main()
{
int num1=10, num2=20;
int *p1, *p2;
p1 = &num1;
:int לא יתקמפל כי מנסים לשים כתובת בתוך משתנה המכיל
cannot convert from int* to int
*p1 = &num2;
*p1 = *p2;
printf)“num1=%d\n”, num1);
}
int: num1
10
1000
int: num2
20
1004
int*: p1
1000
???
1008
int*: p2
???
1012
19
מצביעים – מה יקרה בתוכנית? ()6
(הנחה:
© Keren Kalif
הזיכרון מתחיל בכתובת )1000
)(void main
{
;int num1=10, num2=20
;int *p1, *p2
לא יתקמפל כי לא ניתן לשנות את הכתובת
;p1 = &num1
של משתנה ,אלא רק את ערכו!!
;&num2= p1
;)printf)“num2=%d\n”, num2
}
20
1000
10
int: num1
1004
20
int: num2
1008
???
1000
int*: p1
1012
???
int*: p2
© Keren Kalif
swap : – דוגמאby value העברה
מספרים ומחליפה ביניהם2 פונקציה המקבלת:המטרה
void swap(int a, int b)
{
printf("In function, before swap: a=%d, b=%d\n", a, b);
int temp = b;
b = a;
a = temp;
printf("In function, after swap: a=%d, b=%d\n", a, b);
}
void main()
...הפונקציה לא באמת החליפה בין הערכים
{
int num1=2, num2=3;
int: a
3
2
2000
int: b
2
3
2004
3
???
2008
int: temp
swap הזיכרון של
int:
int: num1
num1
2
???
1000
1000
3 1004
???
1004
main -הזיכרון של ה
int:
int: num2
num2
printf("In main, before swap: num1=%d, num2=%d\n", num1, num2);
swap(num1, num2);
printf("In main, after swap: num1=%d, num2=%d\n", num1, num2);
}
21
© Keren Kalif
swap : – דוגמאby pointer העברה
מספרים ומחליפה ביניהם2 פונקציה המקבלת:המטרה
void swap(int* a, int* b)
{
int temp = *b;
printf("In function, before swap: *a=%d, *b=%d\n", *a, *b);
*b = *a;
*a = temp;
printf("In function, after swap: *a=%d,* b=%d\n",* a, *b);
}
int*: a
1000
int*: b
1004
int: temp
3
???
2000
2004
2008
swap הזיכרון של
void main()
{
int num1=2, num2=3;
printf("In main, before swap: num1=%d, num2=%d\n", num1, num2);
swap(&num1, &num2);
printf("In main, after swap: num1=%d, num2=%d\n", num1, num2);
}
int:
int: num1
num1
int:
num1
3
???
2
1000
1000
1000
2
???
3
1004
1004
1004
main -הזיכרון של ה
int:
int: num2
num2
int:
num2
22
© Keren Kalif
העברת פרמטר לפונקציה – by pointer
ראינו:
23
כאשר מעבירים משתנה לפונקציה עותק שלו מועבר למחסנית של
הפונקציה (העברה )by value
אם בפונקציה משנים את הפרמטר זה לא משפיע על המשתנה
המקורי
גם כאשר מעבירים משתנה מטיפוס מצביע לפונקציה מעבירים
עותק (של הכתובת) ,אבל כאשר מבצעים שינוי בתוכן המצביע
בפונקציה אז השינוי משפיע גם על המשתנה המקורי
העברת פרמטר מטיפוס מצביע לפונקציה המשנה את תוכן
המצביע נקראת העברה by pointer
© Keren Kalif
החזרת יותר מערך יחיד מפונקציה
24
למשל נרצה לכתוב פונקציה המקבלת מערך ,וצריכה להחזיר
מהו המספר המקסימאלי ומה המינימאלי
מאחר וניתן ע"י הפקודה returnלהחזיר ערך אחד בלבד אנחנו
בבעיה...
הפתרון הוא להעביר כפרמטר by pointerמשתנה שיכיל
לבסוף את התוצאה
© Keren Kalif
מציאת מינימום ומקסימום
void minMax(int arr[], int size, int* min, int* max)
{
int i;
*min = *max = arr[0];
for ) i=1 ; i < size ; i++(
}
if (*min > arr[i])
*min = arr[i];
if (*max < arr[i])
*max = arr[i];
}
}
כאשר מעבירים
void main()
{
int arr[] = {5,2,8};
int minimum, maximum;
:תזכורת
מערך לפונקציה מתייחסים
! ולא לעותק שלו,למערך המקורי
minMax(arr, sizeof(arr)/sizeof(arr[0]), &minimum, &maximum);
printf("max is %d and min is %d\n", maximum, minimum);
3
2000
int*: min
1012
2004
int*: max
1016
2008
int: size
int:i
???
1
2
3
2012
minMax הזיכרון של
int[]: arr
int:minimum
5
1000
2
1004
8
1008
5
2
???
1012
5
???
8
1016
main -הזיכרון של ה
int:maximum
}
25
© Keren Kalif
הארות
ניתן היה להעביר לפונקציה רק את minאו רק את maxואת
הערך השני להחזיר ע"י return
26
אבל :כאשר הפונקציה מחזירה יותר מערך אחד והם כולם בעלי
אותה תפקיד ,נעדיף שכולם יוחזרו (by pointerאחידות בסגנון)
אם מעבירים לפונקציה פרמטר by pointerשהפונקציה
מתבססת על ערכו ,חובה לאתחלו בפונקציה ,ולא להתבסס על
אתחול (שאולי) בוצע בפונקציה שקראה
© Keren Kalif
by pointer אתחול פרמטר המועבר
void countPositive(int arr[], int size, int* count)
{
int i;
*count = 0; // it is our responsibility to initialize the value!
for ( i=0 ; i < size ; i++ )
{
if (arr[i] > 0)
(*count)++;
}
}
3
2000
int*: count
1012
2004
int: i
???
0
1
2
3
2008
int: size
countPositive הזיכרון של
int[]: arr
void main()
{
int arr[] = {-4,2,-8};
int numOfPositive; // we can’t assume that who wrote
// the main initialized that variable!!
int: numOfPositive
-4
1000
2
1004
-8
1008
1
0
?
1012
main -הזיכרון של ה
countPositive(arr, sizeof(arr)/sizeof(arr[0]), &numOfPositive);
printf("There are %d positive numbers in the array\n", numOfPositive);
}
27
© Keren Kalif
כתובת האיבר המקסימלי:פונקציה המחזירה מצביע
int* getAddressOfMaxElem(int arr[], int size)
}
int i, maxIndex = 0;
for ( i=1 ; i < size ; i++ )
if (arr[i] > arr[maxIndex])
maxIndex = i;
return &arr[maxIndex];
{
3
2000
int: i
???
1
2
3
2004
int: maxIndex
???
0
1
2008
int: size
getAddressOfMaxElem הזיכרון של
int[]: arr
void main()
{
int arr[] = {3,7,2};
int size = sizeof(arr) / sizeof(arr[0]);
int* pMax = getAddressOfMaxElem(arr, size);
int: size
3
???
1000
7
???
1004
2
???
1008
???
3
1012
1004
???
??? 1016
main -הזיכרון של ה
int*: pMax
printf("Max value is at address %p and is %d\n", pMax, *pMax);
{
28
© Keren Kalif
אתחול מצביע
29
ראינו שניתן לאתחל מצביע עם כתובת של משתנה מהטיפוס
המתאים
ראינו שכאשר לא מאתחלים מצביע ,אז כמו כל משתנה לא
מאותחל ,הוא מכיל לזבל
ניתן לאתחל מצביע שלא מצביע לשום-מקום בערך מיוחד
הנקרא ,NULLשזוהי למעשה הכתובת 0
פניה למצביע שהוא NULLלא תגרום לתעופת התוכנית
פניה לתוכן של מצביע שהוא NULLכן תגרום לתעופת
התוכנית ,כי אין בו כלום..
© Keren Kalif
פניה למצביע NULLלעומת מצביע זבל
שורה זו עוברת בהצלחה
ומדפיסה את הכתובת NULL
ניסיון הדפסה לכתובת זבל
מעיף את התוכנית
30
)(void main
{
;int* p1 = NULL, *p2
;)printf("%p", p1
;)printf("%p", p2
{
© Keren Kalif
אתחול מצביע ל - NULL -דוגמא
)(void main
{
פה התוכנית לא תעוף כי ניגשים לכתובת מאופסת
;int x
;int* pX = &x, *p = NULL
;)printf("pX=%p, p=%p\n", pX, p
;)printf("*p=%d\n", *p
}
פה התוכנית כן תעוף כי ניגשים
לתוכן של מקום שאין בו כלום
31
© Keren Kalif
כתובת איבר לחיפוש:NULL פונקציה המחזירה
int* findNumber(int arr[], int size, int lookFor)
}
int i;
for ( i=0 ; i < size ; i++ )
if (arr[i] == lookFor)
return &arr[i];
return NULL;
{
int: size
int: i
int: lookFor
3
2000
1
3
???
2
2004
4
2008
findNumber הזיכרון של
int[]: arr
3
???
1000
7
???
1004
void main()
2
???
1008
{
???
3
int: size
int arr[] = {3,7,2};
1012
NULL
int size = sizeof(arr) / sizeof(arr[0]);
??? 1016
int*: p
int* p = findNumber(arr, size, 4);
main -הזיכרון של ה
if (p != NULL)
printf("The number is found at address %p\n", p);
else printf("The number is not in the array\n");
{
32
© Keren Kalif
const משתנה
ואז לא ניתן לשנות את ערכו,const -ניתן להגדיר משתנה כ
במהלך ריצת התוכנית
void main()
}
const double PI = 3.14;
PI = 3.1417; // l-value specifies const object
{
33
© Keren Kalif
מצביע const
על תוכן ההצבעה
)(void main
{
עדיין ניתן לפנות ל x-ישירות ולשנות את ערכו
;int x = 2, y
;const int* pX = &x
;x = 5
;*pX = 4
// l-value specifies const object
;pX = &y
{
1000
1000
5
???
2
int: x
1004
1004
???
int: y
1008
1008
1004
1000
???
const int*: pX
הזיכרון של הmain -
34
לא ניתן לפנות ל *pX -ולשנות את ערכו
מצביע constלמשתנה אינו הופך את המשתנה
ל ,const -אלא אך ורק בעיני המצביע עצמו!
© Keren Kalif
על ההצבעה
const מצביע
void main()
{
int x = 2, y;
קבוע ולא ניתן לשנותpX התוכן של
את ערכו לאחר האתחול
int* const pX = &x;
x = 5;
*pX = 4;
pX = &y;
// l-value specifies const object
{
int: xx
int:
2
5
???
4
1000
1000
int: yy
int:
???
1004
1004
int* const:
const: pX
pX
int*
1000
???
1008
1008
main -הזיכרון של ה
35
© Keren Kalif
סיכום :מצביע const
36
ניתן גם להגדיר מצביע כconst -
ישנם 2אופנים להגדיר מצביע כ:const -
.1
כך שלא ניתן לשנות את התוכן בכתובת שהמשתנה מצביע מכיל:
;const <type>* var
.2
כך שלא ניתן לשנות את הכתובת אותה המשתנה מצביע מכיל:
;<type>* const var
© Keren Kalif
שימוש במצביע constבהעברת פרמטר לפונקציה
37
כאשר מעבירים נתונים לפונקציה ,למעשה מעבירים העתק
שלהם ) ,(by valueאלא אם מעבירים אותם by pointer
ראינו שכאשר מעבירים מערך לפונקציה ,למעשה מעבירים
את המערך המקורי ,ולא העתק )(by pointer
הפונקציה יכולה "בטעות" לשנות אותו
לכן פונקציות המקבלות מערך ללא כוונה לשנות אותו,
יצהירו על הפרמטר שהוא const
© Keren Kalif
דוגמא להעברת פרמטר כconst -
הפונקציה מצהירה שלא תשנה את ערכי המערך
)void foo(const int arr[], int size
{
;arr[0] = 10
// l-value specifies const object
}
ניסיון לשנות את תוכן המערך ,בניגוד להצהרה!
38
© Keren Kalif
תרגול
39
© Keren Kalif
הקשר בין מערך וכתובת
כאשר פונים למשתנה רגיל מקבלים את הערך בתא
כאשר פונים למערך ,מקבלים את כתובת תחילת המערך
)(void main
{
פניה לשם המערך נותנת לנו את כתובת ההתחלה שלו!
;}int arr[] = {4,2,8
;)printf("In main1: The array starts at %p\n", arr
ואפשר גם כךprintf("In main2: The array starts at %p\n", &arr); :
}
פניה לכתובת של arr
1000
4
1004
2
1008
8
40
int[]: arr
יודפסIn main: The array starts at 1000 :
© Keren Kalif
הקשר בין מערך וכתובת ()2
41
מאחר ושם המערך הוא למעשה כתובת תחילת המערך ,ניתן
להפעיל עליו את האופרטור *
ראינו כי כאשר מפעילים את האופרטור * על משתנה המכיל
כתובת של ,intאנו מקבלים את הערך שבתוך התא של
הכתובת ,כלומר intכלשהו
במערך ,כתובת ההתחלה מכילה את האיבר הראשון במערך,
לכן הפעלת האופרטור * על שם המערך תחזיר את הערך של
האיבר הראשון במערך
© Keren Kalif
)3( הקשר בין מערך וכתובת
#include <stdio.h>
void main()
{
1000 כתובת
int arr[] = {4,2,8};
printf("value of first element is %d\n", *arr);
}
1000 התוכן שבכתובת
int[]: arr
כתובת ההתחלה
מכילה את האיבר
הראשון במערך
4
1000
2
1004
8
1008
42
© Keren Kalif
פעולות אריתמטיות על כתובות
מוגדרות 3פעולות אריתמטיות לפעולות עם כתובות:
43
כתובת +מספר שלם כתובת
כתובת -מספר שלם כתובת
כתובת– כתובת מספר שלם
כאשר מחברים כתובת pלטיפוס typeמספר שלם kהתוצאה:
)p+k =p + k*sizeof(type
כאשר מחסרים מכתובת pלטיפוס typeמספר שלם kהתוצאה:
)p-k =p - k*sizeof(type
© Keren Kalif
1 דוגמא
– פעולות אריתמטיות על כתובות
void main()
: ייתן1 - קידום ב,int מאחר וזוהי כתובת של
{
p+k =p + k*sizeof(type)
int arr[] = {4,2,8};
1000 + 1*4 = 1004
int* p = arr;
printf("&arr=%p, p=%p\n", arr, p);
&arr=1000, p=1000 :יודפס
p++;
printf("&(arr+1)=%p, p=%p\n", arr+1, p);
&(arr+1)=1004, p=1004 :יודפס
*(arr+1)=2, *p=2 :יודפס
printf("*(arr+1)=%d, *p=%d\n", *(arr+1), *p);
}
int[]: arr
int*: p
???
4
1000
???
2
1004
???
8
1008
???
1000
1004
1012
44
© Keren Kalif
2 דוגמא
– פעולות אריתמטיות על כתובות
void main()
{
int arr[] = {4,2,8};
int* p = NULL;
printf("arr+0=%p, *(arr+0)=%d\n", (arr+0), *(arr+0));
printf("arr+1=%p, *(arr+1)=%d\n", (arr+1), *(arr+1));
printf("arr+2=%p, *(arr+2)=%d\n", (arr+2), *(arr+2));
p = arr + 2;
printf("p=%p, *p=%d\n", p, *p);
}
int[]: arr
int*: p
???
4
1000
???
2
1004
???
8
1008
1008
???
0
1012
arr+0=1000, *(arr+0)=4 :יודפס
arr+1=1004, *(arr+1)=2 :יודפס
arr+2=1008, *(arr+2)=8 :יודפס
p=1008, *p=8 :יודפס
45
© Keren Kalif
3 דוגמא
– פעולות אריתמטיות על כתובות
void main()
{
int arr[] = {4,2,8}, i;
int* p = NULL;
for (i=0 ; i < sizeof(arr)/sizeof(arr[0]) ; i++)
printf("arr+%d=%p, *(arr+%d)=%d\n", i, (arr+i), i,*(arr+i));
p = arr + 2;
...וכמובן שניתן גם עם לולאה
printf("p=%p, *p=%d\n", p, *p);
}
int[]: arr
int*: p
???
4
1000
???
2
1004
???
8
1008
1008
???
0
1012
arr+0=1000, *(arr+0)=4 :יודפס
arr+1=1004, *(arr+1)=2 :יודפס
arr+2=1008, *(arr+2)=8 :יודפס
p=1008, *p=8 :יודפס
46
© Keren Kalif
פעולות אריתמטיות על שם מערך
ניתן לראות כי אם arrהינו שם של מערך אזי ערך הביטוי )(arr+i
הוא כתובת האיבר ה i -במערך ,והביטוי ) *(arr+iהינו תוכן האיבר
ה: i -
)&arr[i] ≡ )arr+i
)arr[i] ≡ *)arr+i
)(void main
{
;}int arr[] = {4,2,8
;)]printf("arr+1=%p, &arr[1]=%p\n", (arr+1), &arr[1
;)]printf("*(arr+1)=%d, arr[1]=%d\n", *(arr+1), arr[1
}
47
© Keren Kalif
4 דוגמא
– פעולות אריתמטיות על כתובות
void main()
{
int* p;
int size;
int arr[] = {4,2,8};
size = sizeof(arr)/sizeof(arr[0]);
int*: p
1020
1012
???
1000
int: size
???
3
1004
int[]: arr
???
4
1008
???
2
1012
???
8
1016
???
1020
p = arr + size;
printf("p=%p, *p=%d\n", p, *p);
p = p - 2;
p=1020, *p=??? :יודפס
printf("After p=p-2:\n");
printf("p=%p, *p=%d\n", p, *p);
p=1012, *p=2 :יודפס
}
48
© Keren Kalif
סדר פעולות
מה תהייה התוצאה של הפעולות הבאות:
קודם מקדמים את pXל 1004 -ועל זה מפעילים *?
קודם מפעילים את * על pXועל התוכן מפעילים ?++
כאשר מופעלים כמה אופרטורים אונריים על משתנה סדר
הפעולות הוא מימין לשמאל
49
1004
1000
int*: pX
האם:
1000
3
int: x
;int x=3
;int* pX = &x
;*pX++
לסוגריים יש עדיפות ,לכן התיקון יהיה (*pX)++
מעבר עולה על איברי מערך עם מצביע (ולא עם
אינדקס) – פוינטר מטייל
© Keren Kalif
1000
4
???
1004
2
???
1008
8
???
1012
1012
1000
1004
1008
???
int*: p
1016
???
3
int: size
int[]: arr
pהוא משתנה שמחזיק כל פעם את
הכתובת של האיבר הבא במערך אותו רוצים להדפיס,
ומאחר והוא מכיל כתובת ל int -הוא מטיפוס *.int
צריך לרוץ איתו עד אשר הוא יכיל את
הכתובת של אחרי סיום המערך (התנאי לסיום הלולאה)
תזכורת:
= )arr+size = &arr + size*sizeof(int
1000 + 3*4 = 1012
)(void main
{
;}int arr[] = {4,2,8
;int* p
;)]int size = sizeof(arr)/sizeof(arr[0
;)" printf("Values in the array:
) for ( p=arr ; p < arr+size ; p++
;)printf("%d ", *p
;)"printf("\n
50
}
מעבר יורד על איברי מערך עם מצביע (ולא עם
אינדקס) – פוינטר מטייל
© Keren Kalif
void main()
{
int arr[] = {4,2,8};
int* p;
int size = sizeof(arr)/sizeof(arr[0]);
printf("Values in the array: ");
for ( p=arr+size-1 ; p >= arr ;
printf("%d ", *p);
printf("\n");
}
4
???
1000
2
???
1004
8
???
1008
int*: p
1000
1008
1004
???
996
1012
int: size
???
3
1016
int[]: arr
p-- )
:תזכורת
arr+size-1 = &arr + (size-1)*sizeof(int) =
1000 + (3-1)*4 = 1008
51
52
© Keren
Kalif
© Keren Kalif
נשים לב...
ראינו שניתן לבצע את הפעולה ++על משתנה מטיפוס כתובת
אבל אסור לקדם מערך ( )arr++אפילו ש arr -הוא כתובת
תחילת המערך
כך למעשה ננסה לשנות את כתובת תחילת המערך ,והרי לא ניתן
לשנות את מיקומם של משתנים בזיכרון..
)(void main
{
;}int arr[] = {4,2,8
1000
1004
1000
1004
1004
???
44
???
22
int[]: arr
arr
int[]:
;)printf("*arr=%d\n", *arr
// same as: arr = arr+1 arr = 1004
???
88
1008
1008
;)printf("*arr=%d\n", *arr
שורה זו לא תתקמפל ,כי לא ניתן לשנות
}
את מיקומם של משתנים בזיכרון!!
;arr++
52
© Keren Kalif
חיסור בין כתובות
חיסור בין כתובות נותן את מספר התאים ביניהם (ולא את הפרש
)!הכתובות
void main()
{
int arr[] = {4,2,8};
int* p;
int size = sizeof(arr)/sizeof(arr[0]);
printf("The values in the array: ");
1000-1000 = 3
1004-1000
1008-1000
1012-1000
0
1
2
for ( p=arr ; p-arr < size ; p++ )
printf("%d ", *p);
printf("\n");
int[]: arr
arr
int[]:
4
???
4
2
???
2
int*:
int*: p
p
8
???
8
???
1012
1000
1004
1008
int: size
size
int:
???
3
1000
1000
1000
1004
1004
1004
1008
1008
1008
1012
1012
1012
1016
1016
1016
}
53
© Keren Kalif
העברת מערך לפונקציה
54
ראינו שבעזרת כתובת תחילת המערך (שם המערך) ניתן
לגשת לכל איברי המערך
ראינו כאשר מעבירים מערך לפונקציה ,הפונקציה יכולה לשנות
את המערך המקורי
הסיבה היא שלא מועבר עותק של המערך לפונקציה ,אלא
מועברת כתובת ההתחלה שלו (שם המערך)
כאשר מעבירים מערך לפונקציה יש להעביר כפרמטר גם את
גודלו
© Keren Kalif
העברת מערך לפונקציה
#include <stdio.h>
int* arr :או
void printArray(int arr[], int size)
{
printf("In func: The array starts at %p\n", arr);
}
int*: arr
1000
2000
3
2004
printArray הזיכרון של
int: size
למעשה מעבירים,כאשר מעבירים מערך כפרמטר לפונקציה
לכן ניתן לכתוב בהצהרה שהפרמטר.את כתובת ההתחלה שלו
int* arr אוint arr[] הוא
ההתייחסות למערך בתוך הפונקציה היא כאל מצביע,בכל צורת כתיבה
void main()
{
int arr[] = {4,2, 8};
printf("In main: The array starts at %p\n", arr);
printArray(arr, sizeof(arr)/sizeof(arr[0]));
}
int[]: arr
???
4
1000
???
2
1004
???
8 1008
main -הזיכרון של ה
55
© Keren Kalif
)1( פונקציה הסוכמת איברי מערך:דוגמא
int sumArray(int* arr, int size)
{
int i;
int sum = 0;
for ( i=0 ; i < size ; i++ )
sum += arr[i];
// same as: sum += *(arr+i)
return sum;
}
void main()
{
int arr[] = {4,2,8};
printf("The sum is %d\”,
{
int*: arr
1000
2000
int: size
3
2004
int: i
???
0
1
2
3
2008
int: sum
???
14
0
4
6
2012
printArray הזיכרון של
int[]: arr
???
4
1000
???
2
1004
???
8 1008
main -הזיכרון של ה
sumArray(arr, sizeof(arr)/sizeof(arr[0])) (;
56
© Keren Kalif
)2( פונקציה הסוכמת איברי מערך:דוגמא
int sumArray(int* arr, int size)
{
int* p;
1012
int sum = 0;
for ( p=arr ; p < arr+size ; p++ )
sum += *p;
return sum;
}
int*: arr
1000
2000
int: size
3
2004
int*: p
1000
1004
1008
1012
???
2008
int: sum
???
14
0
4
6
2012
printArray הזיכרון של
int[]: arr
???
4
1000
???
2
1004
void main()
???
8 1008
{
main -הזיכרון של ה
int arr[] = {4,2,8};
printf("The sum is %d\n", sumArray(arr, sizeof(arr)/sizeof(arr[0])) );
{
57
© Keren Kalif
סכימת רק חלק מאיברי המערך:דוגמא
int sumArray(int* arr, int size)
{
int* p;
1012
int sum = 0;
for ( p=arr ; p < arr+size ; p++ )
sum += *p;
return sum;
}
int*: arr
1004
2000
int: size
2
2004
int*: p
1004
1008
1012
???
2008
int: sum
???
10
0
2
2012
printArray הזיכרון של
int[]: arr
???
4
1000
???
2
1004
void main()
???
8 1008
{
main -הזיכרון של ה
int arr[] = {4,2,8};
printf("The sum is %d\n", sumArray(arr+1, sizeof(arr)/sizeof(arr[0])-1) );
}
58
מדוע צריך להעביר לפונקציה את גודל המערך
(ולא להסתמך על )..sizeof
© Keren Kalif
arrהוא מטיפוס כתובת ,וגודלו של משתנה
מטיפוס כתובת הוא תמיד 4בתים...
לכן כאשר מתייחסים לשם המערך בפונקציה
לא ניתן לדעת את גודלו!
רק בפונקציה שבה מוקצה שטח הזיכרון של
המערך ניתן לדעת את גודלו ע"י !sizeof
4
)void printArraySize(int* arr
{
4
;)]int size = sizeof(arr)/sizeof(arr[0
;))printf("size=%d because sizeof(arr) is %d...\n", size, sizeof(arr
}
)(void main
{
;}int arr[] = {4,2,8
;)printArraySize(arr
}
59
© Keren Kalif
החזרת מערך מפונקציה
60
פונקציה יכולה להחזיר כל טיפוס ,פרט למערך (בינתיים)
ראינו שכאשר פונים לשם המערך ,פונים לכתובת ההתחלה
שלו
כאשר מעבירים מערך לפונקציה ,מעבירים רק את כתובת
ההתחלה שלו ,ולא עותק של כל המערך
ובאופן דומה ,כאשר מחזירים מערך שהוגדר בפונקציה ,חוזרת
כתובת ההתחלה שלו ,ולא עותק של כל המערך
הבעייתיות :כאשר יוצאים מהפונקציה שטח הזיכרון שלה
משתחרר ויש לנו מצביע לזיכרון שנמחק...
הפתרון :כאשר נלמד על הקצאות דינאמיות
© Keren Kalif
דוגמא- החזרת מערך מפונקציה
int[]: arr
#define SIZE 3
int* readArray()
{
int arr[SIZE], i;
printf("Please enter %d numbers: ", SIZE);
for (i=0 ; i < SIZE ; i++)
scanf("%d", &arr[i]);
return arr;
returning
}
void main()
{
int* arr, i;
arr = readArray();
printf("The array is: \n");
for (i=0 ; i < SIZE ; i++)
printf("%d ", arr[i]);
printf("\n");
???
3
2000
???
5
2004
???
6
2008
???
3
2012
readArray הזיכרון של
int: i
:warning הקומפיילר נותן
address of local variable or temporary
שפירושה שאנחנו מחזירים כתובת למשתנה בזיכרון שישתחרר
לעולם לא נחזיר מפונקציה
כתובת של משתנה
!שהוגדר מקומית בפונקציה
int*: arr
2000
???
1000
int: i
???
1004
main -הזיכרון של ה
}
61
© Keren Kalif
אריתמטיקה של מטריצות
כאשר מחברים לשם של מערך מספר ,iמקבלים את כתובת
האיבר הi -
כאשר מחברים לשם של מטריצה מספר ,iמקבלים את כתובת
האיבר הראשון בשורה הi -
)void printArr(int* arr, int size
{
;int i
)for (i=0 ; i < size ; i++
;)]printf("%d ", arr[i
;)"printf("\n
נקבל warningכי הפונקציה מצפה לקבל כתובת התחלה של מערך
}
)(void main
עושים למטריצה castingל int* -כדי לא לקבל
{
;} }int mat[2][3] = { {1,2,3}, {4,5,6
את ה :warning -למעשה אומרים לקומפיילר
;)printArr(mat, 6
להתייחס לכתובת כאל כתובת התחלה של מערך
;)printArr((int*)mat, 6
שליחת כתובת השורה השניה
;)printArr(mat+1, 6
;)printArr((int*)mat+1, 6
שליחת כתובת האיבר השני
}
62
© Keren Kalif
העברת מטריצה לפונקציה המקבלת מערך
#include <stdio.h>
#define SIZE 3
void printMatrix(int mat[][SIZE], int rows)
{
int i, j;
for (i=0 ; i < rows ; i++)
}
for (j=0 ; j < SIZE; j++)
printf("%4d", mat[i][j]);
printf("\n");
{
{
int getMax(int* arr, int size)
}
int i, max=arr[0];
for (i=1 ; i < size ; i++)
if (arr[i] > max)
max = arr[i];
return max;
{
void printArr(int* arr, int size)
}
int i;
for (i=0 ; i < size ; i++)
printf("%d ", arr[i]);
printf("\n");
{
63
© Keren Kalif
1.
2.
3.
4.
העברת מטריצה לפונקציה
)2( המקבלת מערך
void main()
}
int i, mat[SIZE][SIZE]=
{ {1,2,3}, {4,5,2}, {8,2,3} };
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. {
int[][3]: mat
printf("Matrix:\n");
סימון לקומפיילר להתייחס לכתובת
printMatrix(mat, SIZE);
ככתובת התחלה של מערך
printf("\nMatrix as arr:\n");
printArr((int*)mat, SIZE*SIZE);
printf("\n");
for (i=0 ; i < SIZE ; i++ )
printf("The max in line #%d is %d\n",
i+1, getMax(mat[i], SIZE) );
printf("\nThe max in the matrix is %d\n",
int: i
1
1000
1000
2
1004
1004
3
1008
1008
4
1012
1012
5
1016
1016
2
1020
1020
8
1024
1024
2
1028
1028
3
1032
1032
???
32
0
1
1036
1036
getMax((int*)mat, SIZE*SIZE));
64
© Keren Kalif
העברת מטריצה לפונקציה המקבלת מערך
65
מטריצה היא למעשה מערך דו-מימדי :שורות ועמודות
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
ניתן גם להסתכל עליה כמערך של מערכים ,ובכל איבר תשמר
כתובת ההתחלה של המערך המתאים
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
][0,0
לכן ניתן לשלוח כל איבר בה (שהוא מערך בפני עצמו)
לפונקציה המקבלת מערך חד-מימדי
© Keren Kalif
מערך של כתובות
ראינו כי מערך הוא אוסף של איברים מאותו טיפוס
ראינו שמשתנה המכיל כתובת הוא גם טיפוס
ניתן להגדיר מערך של כתובות:
][int* matrix
66
כל איבר במערך הוא כתובת
כל כתובת כזו יכולה להיות כתובת התחלה של מערך חד-מימדי
כאשר מעבירים מערך של כתובות לפונקציה נכתוב אותו
בהצהרה כך int** arr :או כךint* arr[] :
© Keren Kalif
1036
2000
int: rows
2
2004
int: cols
3
2008
1
0
???
2012
int**: mat
int: i
מערך של מצביעים
)1( דוגמא
2
3
0
1
???
2016
printMatrix הזיכרון של
int: j
void printMatrix(int** mat, int rows, int cols)
{
כל איבר במערך הוא כתובת
int i, j;
for ( i=0 ; i < rows ; i++ )
{
for ( j=0 ; j < cols ; j++)
printf("%d ", mat [i][j]);
printf("\n");
}
mat[ i ][ j ] = *(*(mat+i)+j))
}
:i=j=0 דוגמא עבור
void main()
*(*(1036+0)+0)=*(*1036)) = *1000=1
{
int arr1[]={1,2,3}, arr2[]={4,5,6}, arr3[]={7,8,9};
int* mat1[] = {arr1, arr2};
printf("matrix 1:\n");
printMatrix(mat1, 2, 3);
}
int[]: arr1
int[]: arr2
int[]: arr3
int*: mat1[]
???
1
1000
???
2
1004
???
3
1008
???
4
1012
???
5
1016
???
6
1020
???
7
1024
???
8
1028
???
9
1032
1000
???
1036
1012
??? 1040
main -הזיכרון של ה
67
© Keren Kalif
int**: mat
1036
2000
int: rows
2
2004
int: cols
3
2008
int**: pRows
1036
1040
1044
???
2012
int*: pCols
1000
1004
1008
1012
1016
1020
1024
???
2016
מערך של מצביעים
)2( דוגמא
int[]: arr1
printMatrix הזיכרון של
void printMatrix(int** mat, int rows, int cols)
{
1036+2*4=1044
int** pRows, *pCols;
for ( pRows=mat
; pRows < mat+rows
; pRows++ )
1000+3*4=1012
1012+3*4=1024
{
for ( pCols=*pRows ; pCols < *pRows+cols ; pCols++ )
printf("%d ", *pCols);
printf("\n");
}
}
void main()
{
int arr1[]={1,2,3}, arr2[]={4,5,6}, arr3[]={7,8,9};
int* mat1[] = {arr1, arr2};
printf("matrix 1:\n");
printMatrix(mat1, 2, 3);
}
int[]: arr2
int[]: arr3
int*: mat1[]
???
1
1000
???
2
1004
???
3
1008
???
4
1012
???
5
1016
???
6
1020
???
7
1024
???
8
1028
???
9
1032
1000
???
1036
1012
??? 1040
main -הזיכרון של ה
משום שזה משתנה המכילint** מטיפוסpRows
.את הכתובת של האיבר במערך אליו אנו רוצים לפנות
int** הוא מטיפוסint -מאחר והמערך מכיל כתובות ל
68
© Keren Kalif
פונקציה המשנה מצביע
void getMinMaxAddress(int arr[], int size, int* min, int* max)
}
int i;
min = max = &arr[0];
for (i=1 ; i < size ; i++)
}
if (arr[i] < *min)
min = &arr[i];
הפונקציה שינתה את ההעתקים
if (arr[i] > *max)
לכן צריך להעביר,של הכתובות
max = &arr[i];
..כתובת של כתובת
{
printf("Min at %p, Max at %p\n", min, max);
{
void main()
}
int arr[] = {5,6,3};
int* min=NULL, *max=NULL;
getMinMaxAddress(arr, sizeof(arr)/sizeof(arr[0]), min, max);
printf("Min at %p, Max at %p\n", min, max);
{
int*: arr
1000
2004
int: size
3
2008
int*: min
NULL
1000
1008
2012
int*: max
NULL
1000
1004
2016
getMinMaxAddress -הזיכרון של ה
???
5
1000
???
6
1004
???
3
1008
int*: min
NULL
???
1012
int*: max
NULL
???
1016
main -הזיכרון של ה
int[]: arr
69
© Keren Kalif
)2( פונקציה המשנה מצביע
int*: arr
1000
2004
int: size
3
2008
void getMinMaxAddress(int arr[], int size, int** min, int** max)
1012 2012
int**: min
}
int i;
1016 2016
int**: max
*min = *max = &arr[0];
for ( i=1 ; i < size ; i++ )
???
1
2
3
int: i
2020
}
getMinMaxAddress -הזיכרון של ה
if (arr[i] < **min)
*min = &arr[i];
if (arr[i] > **max)
???
5
int[]: arr
1000
*max = &arr[i];
{
???
6
1004
printf("Min at %p, Max at %p\n", *min, *max);
{
???
3
1008
void main()
}
int arr[] = {5,6,3};
int* min=NULL, *max=NULL;
{
int*: min
NULL
1000
1008
???
int*: max
NULL
1000
1004
???
1016
main -הזיכרון של ה
1012
getMinMaxAddress(arr, sizeof(arr)/sizeof(arr[0]), &min, &max);
printf("Min at %p, Max at %p\n", min, max);
70
© Keren Kalif
תרגול
71
© Keren Kalif
ביחידה זו למדנו:
72
מהו מצביע (פוינטר)
מוטיבציה למצביעים
אופרטורים * ו& -
אתחול מצביע
העברת פרמטר לפונקציה by pointer
מצביע const
הקשר בין מערך לכתובת
פעולות חיבור וחיסור עם כתובות
מצביע מטייל על מערך
העברת מערך לפונקציה
הבעייתיות בהחזרת מערך מפונקציה
מערך של כתובות
העברת מצביע לפונקציה לצורך שינוי הצבעתו