לדלג לתוכן

פוינטרים, מערכים, מחרוזות ו־scanf

1. מה זה Pointer (מצביע)?

הPointer (בעברית: מצביע) הוא משתנה ששומר כתובת בזיכרון – במקום לשמור ערך כמו int x = 5, המצביע שומר איפה בזיכרון יושב הערך.

נכריז על מצביע כך:

int* ptr;

במשתנה הזה נשמור כתובת של משתנה מסוג int. כלומר – אנחנו מצביעים על מקום שבו יש מספר שלם בגודל 4 בתים בזיכרון.

איך שומרים כתובת של משתנה?

אפשר לקחת כתובת של משתנה קיים עם הסימן & (and):

int x = 5;
int* ptr = &x;

פעולת ה& נקראת "הreference" פעולת הreference יודעת להגיד מה הכתובת של משתנה מסוים שהגדרנו בעבר.

איך ניגשים לערך שעליו מצביע המצביע?

משתמשים בכוכבית כדי לגשת לתוכן שאליו מצביע המצביע:

int y = *ptr; // y יהיה שווה 5

פעולת ה* נקראת "ה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)?

מערך הוא אוסף של משתנים מאותו סוג, שמסודרים אחד אחרי השני בזיכרון.

מגדירים מערך כך:

int arr[5]; // מערך של 5 מספרים שלמים

אפשר לגשת לכל איבר בעזרת אינדקס:

arr[0] = 1;
arr[1] = 2;

חשוב לדעת:

כאשר אנחנו מגדירים מערך – שם המערך הוא מצביע לאיבר הראשון בו!

int* ptr = arr; // ptr == &arr[0]

כלומר – מערכים ופוינטרים מאוד קשורים. למעשה:

arr[i] == *(arr + i)

דוגמה:

int arr[3] = {1, 2, 3};
printf("%d\n", *(arr + 1)); // מדפיס 2

3. מחרוזות (Strings)

ב־C אין טיפוס string. מחרוזת היא מערך של char שמסתיים ב־NULL TERMINATOR ('\0').

כך מגדירים מחרוזת:

char* str = "hello";

הקומפיילר שומר את המחרוזת ב־data segment ושם מצביע לתחילת המחרוזת במשתנה str.

למעשה:

char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};

זה בדיוק כמו להגדיר מחרוזת.

הדפסה:

printf("%s\n", str);

strlen – אורך מחרוזת:

נוכל להשתמש בפונקציה בשם "strlen"ש מוגדרת בlibc כדי לקבל את האורך של מחרוזת, שימו לב לעשות include לstring.h- קובץ שמגדיר את הפונקציה בlibc.

#include <string.h>
int len = strlen(str);

הפונקציה פעולת באמצעות לולאה שעוברת על כל התווים של המחרוזת שלנו עד שהיא מגיעה למספר 0. (כי כל מחרוזת מסתיימת ב0.)

4. קריאת קלט מהמשתמש – scanf

בניגוד ל־printf שמדפיס, scanf קורא קלט מהמשתמש.

תחביר:

scanf("%d", &x);

שימו לב! חובה לשים & לפני המשתנה – כי 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.

במקרה של argv, הוא מייצג מערך של מחרוזות – כל מחרוזת היא מצביע (char*), והמכלול כולו הוא מצביע למערך של מצביעים → ולכן char**.

דוגמה להבנה:

אם הרצתם תוכנה כך:

./program hello world

אז בתוך main:

int main(int argc, char** argv)
  • 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;
}

אם תפעילו את הקוד עם:

./a.out amit

תקבלו:

כמות הארגומנטים: 2
שם התוכנה: ./a.out
הארגומנט הראשון (אם קיים): amit
  • * = מצביע לערך
  • ** = מצביע למצביע
  • argv = מצביע למערך של מצביעים למחרוזות
  • ככה תוכנית C יכולה לקרוא את הארגומנטים שהועברו אליה בשורת הפקודה

בהמשך, נשתמש בזה כדי לכתוב תוכנות שמגיבות למה שהמשתמש כותב ב־Terminal.