Download presentation
Presentation is loading. Please wait.
1
מצביעים וכתובות מבוא כללי למדעי המחשב
2
נקודה לתשומת-לב: הגרשיים
תו קבוע מסויים רושמים בתוך גרש בודדת. למשל: ‘a’ או ‘?’ מחרוזת קבועה מסויימת רושמים בתוך גרשיים כפולים. למשל: “HELLO” אם רושמים “a” אז המשמעות היא מחרוזת שיש בה את התו ‘a’ ואחריו את התו ‘\0’. כלומר זה שונה מאשר לרשום ‘a’. אז כשעושים פעולות על מחרוזות צריך להקפיד על גרשיים כפולים, למשל: strcmp(input,”a”) ולא: strcmp(input,’a’) ‘a’'0\'
3
מחרוזות – נקודה לתשומת-לב
כשמכניסים למחרוזת ערך ע"י פעולה על מחרוזת שלמה, אז תו-הסיום '0\' מוכנס אוטומטית אחרי תווי המחרוזת (בלי שרושמים אותו במפורש). למשל: char str[ ]=“Hello”; scanf(“%s”, str); scanf(“%40s”, str); gets(str); אם מכניסים למחרוזת תו-תו, לא ע"י פעולה על מחרוזת שלמה, אז צריך להוסיף במפורש את תו-הסיום כדי לעשות לאחר-מכן פעולות על המחרוזת בשלמותה. למשל: char str[ ]={‘H’,’e’,’l’,’l’,’o’,’\0’};
4
מחרוזות – עוד נקודה לתשומת-לב
זיכרו שה- Enter שמוכנס בסיום הקלט נשאר בזיכרון-הקלט של המחשב. כלומר אם נקלוט תו אחרי שקלטנו מחרוזת אז הוא יכיל את ‘\n’ (ולא נתבקש לרשום תו נוסף): scanf(“%s”, str); abcd scanf(“%c”, &tav); כמו שראינו בעבר, ניתן להתגבר על כך ע"י זה שנגיד לפקודת-הקלט הבאה לדלג על ‘\n’: scanf(“\n%c”, &tav); e
5
נושאי השיעור היום: כתובות ומצביעים
מהן כתובות בזיכרון פעולות עם כתובות מהם מצביעים ומה אפשר לעשות איתם
6
כתובות בזיכרון אמרנו בעבר שהזיכרון של המחשב מורכב מתאים (בתים) רבים. במחשבים הנוכחיים מדובר במיליונים ואפילו מיליארדים. לכל תא בזיכרון יש כתובת (מספר תא). כל משתנה נשמר בזיכרון החל מכתובת מסויימת (הוא יכול לתפוס יותר מתא אחד, בהתאם לטיפוס המשתנה). למשל, כשמגדירים משתנה על-ידי: int i; המחשב מקצה עבור המשתנה i מקום בזיכרון בכתובת שהוא בוחר. הוא שומר טבלה של כתובות המשתנים.
7
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i; } משתנה כתובת ? i 7500 כתובת כלשהי שהמחשב בחר כתובת כלשהי שהמחשב בחר
8
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i; i=10; } משתנה כתובת 10 i 7500 ערך המשתנה מתעדכן בזיכרון
9
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i=10; } משתנה כתובת 10 i 7500 אותו דבר אם הערך ניתן באיתחול
10
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i=10; char c=‘A’; } 10 משתנה כתובת 7500 i c ‘A’ 9200 כך גם עבור משתנים מסוגים אחרים
11
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i=10; char c=‘A’; } 10 משתנה כתובת 7500 i c 65 9200 כזכור, למעשה נשמר ערך האסקי של התו
12
כתובות בזיכרון – גודל המשתנה
בטבלת הכתובות נשמר נתון נוסף, שהוא מספר התאים (בתים) שמשמשים ליצוג המשתנה הזה. למשל, char מיוצג על-ידי בית אחד, int על-ידי ארבעה, ועבור double שמונה (ברוב הקומפיילרים). כשאנחנו מתייחסים בתוכנית לשם של משתנה, המחשב בודק בטבלה איפה המשתנה הזה נמצא בזיכרון וכמה מקום הוא תופס, וכך הוא יודע להביא לנו את הערך שלו.
13
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i=10; char c=‘A’; } משתנה כתובת גודל 10 i c 7500 65 9200 המחשב מקצה 4 תאים (בתים). intעבור אם המספר קטן אז הספרות הראשונות יהיו מאופסות.
14
כתובות – איך מתייחסים אליהן
שפת C מאפשרת לנו לדעת מהי הכתובת בזיכרון שבה נשמר משתנה מסויים. הפעולה & נותנת את הכתובת של המשתנה. למשל &i היא הכתובת בזיכרון של המשתנה i. בדוגמא הקודמת &i נותן 7500.
15
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
int i=10; } משתנה כתובת גודל 10 i 7500 &i זה 7500
16
כתובות – איך ניגשים אליהן
אמרנו שהפעולה & נותנת את הכתובת של משתנה. למשל &i היא הכתובת בזיכרון של המשתנה i. בדוגמא הקודמת &i היה נותן 7500. הפעולה * ניגשת לערך שנמצא בכתובת מסויימת. למשל *(4000) זה הערך שנמצא בכתובת 0040. *(&c) זה הערך של המשתנה c (כי זה הערך שנמצא בכתובת של(c .
17
כתובות בזיכרון - דוגמא התוכנית הזיכרון טבלת הכתובות int main() {
char c=‘A’; } משתנה כתובת 65 c 9200 *(9200) זה 65
18
כתובות – מה אפשר לעשות איתן
לא ניתן לשנות כתובת של משתנה. כלומר לא ניתן לכתוב למשל: &i=5000. ניתן לשנות את הערך שנמצא בכתובת מסויימת. למשל: *(&c)=‘A’;. זה כמו לכתוב c=‘A’;. מסוכן לשנות את הערך שנמצא בכתובת כלשהי שאיננה כתובת של משתנה. למשל *(4000)=123;. אנחנו עלולים לשנות מקום בזיכרון ששייך למערכת ההפעלה, ולגרום לתוכנית לעוף. לכן נשנה רק ערכים של משתנים שהגדרנו.
19
בשביל מה כל זה טוב? הרי כדי לשנות ערך של משתנה אפשר פשוט לשים בו ערך. למשל i=10;. אז בשביל מה נירצה לדעת מה הכתובת שלו? למה שנירצה לשים ערך בכתובת מסויימת ולא להשתמש פשוט בשם המשתנה?
20
בשביל מה נשתמש בכתובות אמרנו שכל משתנה מוכר רק בתוך הפונקציה שהוא הוגדר בה. נשתמש בכתובות כדי לשנות ערך של משתנה מתוך פונקציה אחרת, שהוא לא הוגדר בה. בפרט, אם רוצים שפונקציה תשנה כמה משתנים (ולא רק תחזיר ערך אחד), אז אפשר להעביר אליה את הכתובות שלהם. זה מאפשר לה לשנות באופן ישיר את ערכיהם בזיכרון.
21
דוגמא שהכרנו השתמשנו בהעברת כתובת בפונקציה scanf, שמקבלת את כתובת המשתנה שאליו יוכנס הקלט. למשל: int grade; scanf(“%d”, &grade); המשתנה grade לא מוגדר בפונקציה scanf והיא גם לא מחזירה לתוכו ערך. הפונקציה scanf מקבלת את הכתובת של המשתנה בזיכרון, וזה מאפשר לה לכתוב לתוכו את הקלט (בעזרת הפעולה * שהכרנו קודם). קידוד הקלט (למשל %d) אומר ל- scanf גם מה גודל המשתנה הזה בזיכרון, כלומר לכמה תאים הקלט אמור להיכנס.
22
כתובות בזיכרון - פעולות
מציאת הכתובת של משתנה עם הפעולה & גישה לערך של משתנה לפי כתובתו עם הפעולה * זה יאפשר לנו לשנות יותר ממשתנה אחד בפונקציה שאלות?
23
עבודה עם כתובות - מצביעים
יש ב-C טיפוסי-משתנים לשמירת כתובות בזיכרון. הם נקראים "מצביעים" או "פויינטרים". אפשר להגדיר למשל: char *my_pointer; int *pointer1; באופן כללי, כדי להגדיר משתנה מסוג מצביע, רושמים את סוג המשתנה שעליו מצביעים, וכותבים * לפני שם המשתנה. עוד דוגמאות: double *my_pointer1; float *ptr; char משתנה לשמירת כתובת של int משתנה לשמירת כתובת של
24
עבודה עם כתובות - מצביעים
יש ב-C טיפוסי-משתנים לשמירת כתובות בזיכרון. הם נקראים "מצביעים" או "פויינטרים". אפשר להגדיר למשל: char *my_pointer; int *pointer1; באופן כללי, כדי להגדיר משתנה מסוג מצביע, רושמים את סוג המשתנה שעליו מצביעים, וכותבים * לפני שם המשתנה. שימו לב שזה שימוש אחר בכוכבית (להגדרת משתנה מטיפוס מצביע), נוסף לשימוש שראינו קודם (לגישה לערך בכתובת). char משתנה לשמירת כתובת של int משתנה לשמירת כתובת של
25
מצביעים – משמעות מצביע מכיל למעשה הפניה ("חץ") למשתנה אחר. לדוגמא:
int i=10; int *my_pointer; my_pointer = &i; int הגדרת מצביע לשמירת כתובת של iמוכנסת אליו הכתובת בזיכרון של המשתנה 10 i my_pointer כתובת 300 (לדוגמא) 300
26
גישה למשתנה על-ידי מצביע
כאמור, כדי להתייחס לערך שנמצא בכתובת מסויימת, משתמשים בסימן *. ניראה קטע תוכנית שמדגים את זה: int i=10; int *my_pointer; my_pointer = &i; *my_pointer = 100; printf(“The value of i is now %d”, i); intהגדרת מצביע על iשמים במצביע את כתובת המשתנה שמים 100 במקום שהפויינטר מצביע עליו יודפס המספר 100 100 i my_pointer_to_int
27
למה יש מצביע שונה לכל טיפוס?
סוג המצביע אומר מה גודל המשתנה שמצביעים עליו, ואיך רוצים להתייחס לתוכן הכתובת הזאת (למשל שלם או ממשי). אז כשמתייחסים לערך שעליו מצביעים, המחשב יודע בכמה בתים הוא מיוצג ואיך, ומביא/מאחסן את הערך הנכון. ניראה בעתיד איך זה מאפשר גם לעשות "חישובי-כתובות". אם נירצה לגשת ל- int שנמצא בזיכרון מייד אחרי i, אז נוכל להוסיף 1 למצביע על i, והמחשב ידע בכמה בתים להתקדם בהתאם לגודל של int
28
מצביעים וכתובות – נקודות לתשומת-לב
נשים לב שלקבועים וביטויים אין כתובת. למשל, לא נכתוב: &5 &(i*2) רק למשתנים יש כתובת. כיוון שלא ניתן לשנות כתובת של משתנה (המחשב קובע אותה), לא ניתן לבצע עליה השמה: &a=&b.
29
מצביעים – דוגמא נתאר פונקציה שמקבלת כתובות של שני משתנים מסוג int, ומחליפה בין הערכים שלהם. (עד עכשיו לא יכולנו לכתוב פונקציה להחלפת ערכים של שני משתנים מסוג int, כי שינוי משתנים מסוג int בפונקציה לא משפיע על ערכם המקורי, ופונקציה יכולה להחזיר רק ערך אחד). void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } השימוש למשל על-ידי: swap(&i, &j); first ערך המשתנה שכתובתו הועברה אל יוחלף עם ערך המשתנה שכתובתו הועברה אל second
30
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j);
31
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j);
32
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); 10 i 20 j
33
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); first second 10 i 20 j
34
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); first second temp 10 i 20 j
35
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); first second temp 10 10 i 20 j
36
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); first second temp 10 20 i 20 j
37
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); first second temp 10 20 i 10 j
38
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); 20 i 10 j
39
דוגמא: פונקציה להחלפה בין ערכי משתנים
void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } int main() int i=10, j=20; swap(&i,&j); בלי מצביעים לא יכולנו לשנות יותר ממשתנה אחד על-ידי פונקציה 20 i 10 j
40
עוד על פונקציות ומצביעים
פונקציה יכולה להחזיר מצביע (כלומר כתובת של משתנה). אבל לא נחזיר כתובת של משתנה שהוגדר בתוך הפונקציה, כי אחרי שהפונקציה מסתיימת המשתנה כבר לא נמצא שם (גישה לשם תגרום לתעופה).
41
עוד על פונקציות ומצביעים
למשל: int *address_of _the_higher_number(int *a, int*b) { if(*a > *b) return a; return b; }
42
עוד על פונקציות ומצביעים
למשל: int *address_of _the_higher_number(int *a, int*b) { if(*a > *b) return a; return b; } int main() { int *ptr, i=10, j=0; ptr = address_of _the_higher_number(&i, &j); return 0; מוחזרת כתובת המשתנה שמכיל את הערך הגדול יותר מבין השניים והשימוש ניראה כך למשל:
43
עוד על פונקציות ומצביעים
אמרנו שהערך שמוחזר מפונקציה יכול להיות מצביע: int *address_of _the_higher_number(int *a, int*b); אבל החזרת כתובת משתנה שהוגדר בתוך הפונקציה תגרום לשגיאה. כי המשתנים שמוגדרים בתוך הפונקציה נעלמים עם סיומה. למשל הפונקציה הבאה תגרום לשגיאה: int *give_pointer_to_zero() { int i=0; return &i; } i
44
מצביעים ומערכים הגדרת מערך מקצה בזיכרון מקומות רצופים עבור התאים שלו.
בנוסף, מוקצה משתנה ששמו הוא שם-המערך, שמכיל את כתובת התא הראשון של המערך (לא ניתן לשנות את ערכו). לכן אפשר לגשת לתאי מערך גם ע"י חישוב הכתובת שלהם. למשל *(array+5) שקול ל- array[5]. המחשב יודע לאיזה כתובת לגשת כשעושים את החשבון הזה, כי הוא יודע כמה מקום תופס כל תא במערך לפי הטיפוס. array[0] array[9] . כתובת 500 (למשל) 500 array
45
העברת מערך לפונקציה (האמת...)
כאשר מועבר מערך לפונקציה, בעצם מועבר מצביע למערך זו הסיבה שאין צורך לכתוב את גודל המערך בהגדרת הפונקציה. לדוגמא int calc_sum(int my_array[100], int size) שקול ל int calc_sum(int *my_array, int size)
46
מצביעים ומערכים אי-אפשר לשנות כתובת של מערך (היא נקבעת כשהוא מוגדר).
לכן לא ניתן לכתוב array=array1, או array=0. אפשר לשים כתובת של מערך בתוך מצביע. אפשר לכתוב: char *array_ptr; array_ptr=array; (השורה האחרונה שקולה לשורה:array_ptr = &array[0]; ) כלומר, משתנה מערך הוא סוג של מצביע (אבל לא להיפך). מערך הוא למעשה מצביע שמקבל כתובת בהגדרה ולא ניתן לשנות אותה. (שמים במצביע את כתובת התא הראשון במערך)
47
גישה לתאי-מערך לפי הכתובת
אם הגדרנו למשל: int array[10]; int *array_ptr; array_ptr=array; אז אפשר לגשת לתאי-המערך גם לפי הכתובת שלהם, כמו משתנים אחרים. 3 הפעולות הבאות עושות אותו דבר (שמות 100 בתא מס' 5 במערך): array[5]=100; *(array+5)=100; *(array_ptr+5)=100; - המחשב יודע כמה בתים להתקדם כשאנחנו מבקשים להתקדם ב-5 תאים קדימה, כי הגדרנו שטיפוס הערכים הוא int והוא יודע כמה בתים כל int תופס (כאמור, ערכי המערך שמורים בזיכרון ברצף). (מצביע על התא הראשון במערך) גישה לתא שנמצא 5 תאים אחרי התא הראשון
48
גישה למערך לפי כתובת – דוגמא נוספת
נאמר שמוגדרים: int i,array[10]={0}; int *ptr; אז 3 הקטעים הבאים עושים בדיוק אותו דבר (מדפיסים את המערך): for (i=0; i<10; i++) printf(“%d ”, array[i]); printf(“%d ”, *(array+i)); for (ptr=array; ptr <= &array[9]; ptr++) printf(“%d ”, *ptr); 1) הדפסה על-ידי גישה רגילה לתאי המערך 2) הדפסה על-ידי גישה לכל תא לפי הכתובת שלו יחסית לתא הראשון 3) הדפסה ע"י קידום מצביע מכתובת התא הראשון עד כתובת התא האחרון
49
מערכים ופונקציות - הסבר
אמרנו בעבר שכשמעבירים מערך לפונקציה שינויים שנעשה בפונקציה ישפיעו על המערך המקורי. זה נובע מכך שלפונקציה מועברת הכתובת בזיכרון של המערך המקורי, והיא פועלת ישירות על ערכיו (ולא על העתק שלהם). למשל, כפי שאמרנו, הפונקציה הבאה תאפס מערך שמועבר אליה: void zero_array(int a[ ], int size) { int i; for(i=0 ; i<size; i++) a[i]=0; }
50
מערכים ופונקציות - הסבר
אמרנו בעבר שכשמעבירים מערך לפונקציה שינויים שנעשה בפונקציה ישפיעו על המערך המקורי. זה נובע מכך שלפונקציה מועברת הכתובת בזיכרון של המערך המקורי, והיא פועלת ישירות על ערכיו (ולא על העתק שלהם). למשל, כפי שאמרנו, הפונקציה הבאה תאפס מערך שמועבר אליה: void zero_array(int *a, int size) { int i; for(i=0 ; i<size; i++) a[i]=0; } נציין שאם פונקציה מצפה לקבל מצביע, אפשר להעביר אליה מערך מהסוג הזה, כי בשני המקרים מה שמועבר הוא כתובת בזיכרון.
51
מערכים ופונקציות למשל הפונקציות שהזכרנו על מחרוזות (כמו strcmp, strlen) מוגדרות למעשה עבור מצביעים מסוג char *. באחריותנו להפעיל אותן באמת על מחרוזת (כלומר רצף תווים שמסתיים ב- '0\') ולא סתם על מצביע לתו או מערך של תווים (נקבל תוצאות שגויות אם אין את תו הסיום). כמו-כן, כזכור, אם פונקציה מוגדרת על מערך בגודל מסויים, ניתן להעביר אליה גם מערך בגודל אחר של אותו טיפוס (כי כאמור מה שמועבר זה רק כתובת ההתחלה). שוב, זה באחריותנו שתתבצע בפונקציה פעולה הגיונית ושהיא תדע מה גודל המערך ולא תחרוג ממנו.
52
"מצביע על כלום" לפעמים נירצה לסמן שמצביע לא מצביע לשום משתנה (למשל לפני שהתחלנו להשתמש בו). מקובל לעשות זאת ע"י זה שנכניס אליו את הכתובת 0 (שאיננה כתובת אפשרית של משתנה מטיפוס כתובת). למעשה, במקום לרשום 0 מקובל לרשום NULL. זהו קבוע שערכו 0, שמוגדר ע"י #define בספריה stdlib.h. למשל: #include<stdlib.h> int *ptr=NULL; לא לבלבל בין הכתובת NULL לבין תו סיום מחרוזת ‘\0’
53
דוגמא: חיפוש תו במחרוזת
#include<stdlib.h> char *my_strchr(char *str, char tav) { while((*str != ‘\0’) && (*str != tav)) str++; if (*str == tav) return str; return NULL } מקבלת מצביע לתחילת מחרוזת ותו מתקדמים עד שמצאנו את התו או הגענו לסוף המחרוזת אם מצאנו מוחזר מצביע להופעה הראשונה אם לא מצאנו מחזירים NULL
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.