פוינטרים, מערכים, מחרוזות ו־scanf¶
1. מה זה Pointer (מצביע)?¶
הPointer (בעברית: מצביע) הוא משתנה ששומר כתובת בזיכרון – במקום לשמור ערך כמו int x = 5, המצביע שומר איפה בזיכרון יושב הערך.
נכריז על מצביע כך:
במשתנה הזה נשמור כתובת של משתנה מסוג int. כלומר – אנחנו מצביעים על מקום שבו יש מספר שלם בגודל 4 בתים בזיכרון.
איך שומרים כתובת של משתנה?¶
אפשר לקחת כתובת של משתנה קיים עם הסימן & (and):
פעולת ה& נקראת "הreference" פעולת הreference יודעת להגיד מה הכתובת של משתנה מסוים שהגדרנו בעבר.
איך ניגשים לערך שעליו מצביע המצביע?¶
משתמשים בכוכבית כדי לגשת לתוכן שאליו מצביע המצביע:
פעולת ה* נקראת "הdereference" פעולת הdereference שקולה ל [] באסמבלי ויודעת להגיד מה הערך שיש מאחורי פוינטר מסוים על פי הגודל שלו.
כלומר, אם הגדרנו פוינטר מסוג int, כאשר נעשה לו dereference נקבל ערך של 4 בתים.
דוגמה מלאה:¶
#include <stdio.h>
int main() {
int x = 10;
int a = &x;
int* p = &x;
printf("x = %d\n", x); // מדפיס את הערך של x
printf("a = %d\n", a); // מדפיס את הערך של x
printf("p = %p\n", p); // מדפיס את הכתובת של x
printf("*p = %d\n", *p); // מדפיס את הערך שאליו מצביע p
return 0;
}
2. מה זה מערך (Array)?¶
מערך הוא אוסף של משתנים מאותו סוג, שמסודרים אחד אחרי השני בזיכרון.
מגדירים מערך כך:
אפשר לגשת לכל איבר בעזרת אינדקס:
חשוב לדעת:¶
כאשר אנחנו מגדירים מערך – שם המערך הוא מצביע לאיבר הראשון בו!
כלומר – מערכים ופוינטרים מאוד קשורים. למעשה:
דוגמה:¶
3. מחרוזות (Strings)¶
ב־C אין טיפוס string. מחרוזת היא מערך של char שמסתיים ב־NULL TERMINATOR ('\0').
כך מגדירים מחרוזת:¶
הקומפיילר שומר את המחרוזת ב־data segment ושם מצביע לתחילת המחרוזת במשתנה str.
למעשה:
זה בדיוק כמו להגדיר מחרוזת.
הדפסה:¶
strlen – אורך מחרוזת:¶
נוכל להשתמש בפונקציה בשם "strlen"ש מוגדרת בlibc כדי לקבל את האורך של מחרוזת, שימו לב לעשות include לstring.h- קובץ שמגדיר את הפונקציה בlibc.
הפונקציה פעולת באמצעות לולאה שעוברת על כל התווים של המחרוזת שלנו עד שהיא מגיעה למספר 0. (כי כל מחרוזת מסתיימת ב0.)
4. קריאת קלט מהמשתמש – scanf¶
בניגוד ל־printf שמדפיס, scanf קורא קלט מהמשתמש.
תחביר:
שימו לב! חובה לשים & לפני המשתנה – כי
scanfמשנה אותו בזיכרון, ולכן צריך את הכתובת של המשתנה
דוגמאות:¶
int a;
scanf("%d", &a); // קולט מספר שלם
char c;
scanf(" %c", &c); // קולט תו אחד (שימו לב לרווח לפני %c)
char str[100];
scanf("%s", str); // קולט מילה (ללא רווחים)
חשוב:¶
-
scanf("%s", str)לא צריך & כיstrכבר מצביע. -
scanfעוצר בקריאת מחרוזת כשיש רווח, טאב או אנטר.
5. דוגמה משולבת – מצביעים, מערכים ו־scanf¶
#include <stdio.h>
int main() {
int arr[3];
int* p = arr;
printf("הכנס 3 מספרים:\n");
scanf("%d", &arr[0]);
scanf("%d", &arr[1]);
scanf("%d", &arr[2]);
printf("arr[0] = %d, *(p+0) = %d\n", arr[0], *(p + 0));
printf("arr[1] = %d, *(p+1) = %d\n", arr[1], *(p + 1));
printf("arr[2] = %d, *(p+2) = %d\n", arr[2], *(p + 2));
return 0;
}
מה זה ** בפוינטרים? – ולמה argv הוא char**¶
ראינו כבר שפוינטר (*) הוא משתנה ששומר כתובת של ערך כלשהו.
אז מה זה **?
המשתנה הזה הוא פוינטר לפוינטר. כלומר – הוא שומר כתובת של משתנה, שגם הוא מצביע לכתובת של ערך מסוג char.
במקרה של argv, הוא מייצג מערך של מחרוזות – כל מחרוזת היא מצביע (char*), והמכלול כולו הוא מצביע למערך של מצביעים → ולכן char**.
דוגמה להבנה:¶
אם הרצתם תוכנה כך:
אז בתוך main:
-
argcיהיה 3 (כי יש 3 מילים:./program,hello,world) -
argv[0]="./program" -
argv[1]="hello" -
argv[2]="world"
כל אחד מ־argv[i] הוא בעצם char* – מצביע למחרוזת.
דוגמה:¶
#include <stdio.h>
int main(int argc, char** argv) {
printf("כמות הארגומנטים: %d\n", argc);
printf("שם התוכנה: %s\n", argv[0]);
printf("הארגומנט הראשון (אם קיים): %s\n", argv[1]);
return 0;
}
אם תפעילו את הקוד עם:
תקבלו:
*= מצביע לערך**= מצביע למצביעargv= מצביע למערך של מצביעים למחרוזות- ככה תוכנית C יכולה לקרוא את הארגומנטים שהועברו אליה בשורת הפקודה
בהמשך, נשתמש בזה כדי לכתוב תוכנות שמגיבות למה שהמשתמש כותב ב־Terminal.