מבוא לתכנות ולמדעי המחשב בשפת C/ניהול זיכרון, מצביעים ומבנים: הבדלים בין גרסאות בדף

תוכן שנמחק תוכן שנוסף
יצירת דף עם התוכן "{{תבנית:ניווט מבוא|12|14}} == מצביעים וניהול זיכרון == השיעור נמצא [http://orimosenzon.com/intro2cs_2012/memory_and_poi..."
 
שורה 3:
== מצביעים וניהול זיכרון ==
השיעור נמצא [http://orimosenzon.com/intro2cs_2012/memory_and_pointers.pdf במסמך pdf זה]
 
 
השיעור שבמסמך מכיל תוכן הקשור למבנים, struct-ים ב c.
 
הנה הסבר מפורט יותר על מבנים ואיך משתמשים בהם.
 
 
== struct - מבנה ==
ב - C ניתן להגדיר טיפוס המורכב ממספר טיפוסים אחרים. טיפוס כזה נקרא struct או מבנה. לדוגמה:
<syntaxhighlight>
#include <stdio.h>
 
struct AA {
int a;
char c;
};
 
int main() {
struct AA b;
b.a = 14;
b.c = 'F';
 
printf("%d \n",b.a);
printf("%c \n",b.c);
 
return 0;
}
</syntaxhighlight>
'''פלט:'''
<syntaxhighlight>
14
F
</syntaxhighlight>
הטיפוס struct AA מורכב מ int ו char. כל משתנה מסוג struct AA, מכיל גם מספר וגם תו. בדוגמה זו, הגדרנו משתנה מטיפוס זה בשם b ולכן b מכיל מספר ותו. בכדי לפנות לחלק של המספר, כתבנו b.a ובכדי לפנות לחלק של התו, כתבנו b.c. מכנים את a ו c '''השדות''' של ה struct ולעיתים גם כ '''members''' שלו.
 
שימו לב שהטיפוס הוא struct AA ולא רק AA.
 
האיור הבא מראה איך נראה המשתנה b בזיכרון, כאשר הוא מוקצה על המחסנית:
[[קובץ:stuct_b.png|200px|center]]
 
== מערך של מבנים ==
מכיוון שמבנה הוא טיפוס לכל דבר, ניתן להגדיר גם מערכים של מבנים. לדוגמה:
 
<syntaxhighlight>
#include <stdio.h>
 
struct AA {
int number;
char character;
};
 
int main() {
struct AA arr[10];
int i;
for(i=0; i<10; ++i) {
arr[i].number = i;
arr[i].character = (char) ( (int)'A'+ i);
}
for(i=0; i<10; ++i)
printf("arr[%d] = <%d,%c> \n", i, arr[i].number, arr[i].character);
 
return 0;
}
</syntaxhighlight>
 
'''פלט:'''
<syntaxhighlight>
arr[0] = <0,A>
arr[1] = <1,B>
arr[2] = <2,C>
arr[3] = <3,D>
arr[4] = <4,E>
arr[5] = <5,F>
arr[6] = <6,G>
arr[7] = <7,H>
arr[8] = <8,I>
arr[9] = <9,J>
</syntaxhighlight>
 
בדוגמה זו, שינינו את שמות השדות ל number ו character, הגדרנו מערך של struct AA, מילאנו את כל ערכיו ואז הדפסנו את תכולתו.
 
הנה דוגמה נוספת לשימוש במערך של מבנים. בדוגמה זו אנו שומרים נתונים של אנשים שונים:
 
<syntaxhighlight>
#include <stdio.h>
#include <stdlib.h>
 
struct Person {
int id;
char *name;
};
 
int main() {
char* names[] = {"Asif","Agam","Tzion","Yasmin","Ela","Maya","Yael"};
 
int N = sizeof(names)/sizeof(char*);
 
struct Person* persons = (struct Person*) malloc (N*sizeof(struct Person));
 
int i;
for(i=0; i<N; ++i) {
persons[i].id = 1000+i;
persons[i].name = names[i];
}
 
for(i=0; i<N; ++i)
printf("%s %d\n",persons[i].name, persons[i].id);
 
free(persons);
return 0;
}
 
</syntaxhighlight>
'''פלט:'''
<syntaxhighlight>
Asif 1000
Agam 1001
Tzion 1002
Yasmin 1003
Ela 1004
Maya 1005
Yael 1006
</syntaxhighlight>
 
כמובן, בדוגמה מציאותית, יהיו בכל מבנה שדות רבים נוספים.
 
== העברה של מבנה כפרמטר לפונקציה ==
כמו כל משתנה, גם כאשר מעבירים מבנה כפרמטר לפונקציה, נוצר העתק שלו. לדוגמה:
<syntaxhighlight>
#include <stdio.h>
 
struct AA {
int number;
char character;
};
 
void foo(struct AA param) {
printf("in foo: %d %c \n",param.number, param.character);
param.number = 17;
param.character = 'B';
printf("in foo: %d %c \n",param.number, param.character);
}
 
int main() {
struct AA myStruct;
myStruct.number = 1;
myStruct.character = 'A';
 
printf("in main: %d %c \n",myStruct.number, myStruct.character);
 
foo(myStruct);
 
printf("in main: %d %c \n",myStruct.number, myStruct.character);
 
return 0;
}
 
 
</syntaxhighlight>
 
'''פלט:'''
<syntaxhighlight>
in main: 1 A
in foo: 1 A
in foo: 17 B
in main: 1 A
</syntaxhighlight>
 
כפי שאפשר לראות מהדוגמה, הפרמטר הוא העתק בלתי תלוי במבנה המקורי. שינוי של ההעתק לא גורם לשינוי של המבנה המקורי.
 
== מצביע למבנה ==
 
מכיוון שמבנה הוא טיפוס לכל דבר, ניתן להגדיר עבורו מצביע. דרך המצביע אפשר להתייחס למבנה ולשדות שלו. לדוגמה:
<syntaxhighlight>
#include <stdio.h>
struct AA {
int number;
char character;
};
 
int main() {
struct AA s;
struct AA *p;
p = &s;
 
(*p).number = 17;
printf("s.number: %d \n",s.number);
 
// *p.number = 12; // will not compile because interpreted as:
// *(p.number) = 12
 
p->number = 78; // same as: (*p).number = 78
printf("s.number: %d \n",s.number);
return 0;
}
</syntaxhighlight>
 
'''פלט:'''
<syntaxhighlight>
s.number: 17
s.number: 78
</syntaxhighlight>
 
כפי שניתן לראות, אפשר לגשת אל שדות המבנה בכתיב מפורש, באופן הבא:
<syntaxhighlight>
(*p).number
</syntaxhighlight>
המשתנה המוצבע ע"י p הוא מבנה ו number הוא אחד השדות של אותו מבנה. הסוגריים בכתיב זה הם הכרחיים מכיוון שאחרת הקומפיילר יבין את הביטוי באופן הבא:
<syntaxhighlight>
*(p.number) = 17
</syntaxhighlight>
כלומר, כאילו p הוא מבנה בעל שדה בשם number, השדה number הוא מצביע ואנו מעוניינים במה ש number מצביע עליו. הקומפיילר ידווח על שגיאה כי p איננו מבנה.
 
מכיוון שהכתיב המופרש, עם הסוגריים, קצת מסורבל, C מציעה כתיב מקוצר, שקול:
<syntaxhighlight>
p->number
</syntaxhighlight>
זאת הדרך המקובלת לפנות לשדה של מבנה מתוך מצביע למבנה.
 
== typedef ==
typedef היא פקודה ב C המאפשרת לתת שם נרדף לטיפוס קיים. לדוגמה:
<syntaxhighlight>
#include <stdio.h>
 
typedef int MyInt;
 
int main() {
MyInt a;
a = 5;
printf("%d\n",a);
return 0;
}
</syntaxhighlight>
בדוגמה זו, MyInt הוא שם נוסף לטיפוס int. המשתנה a בתוכנית הוא למעשה משתנה מסוג int.
 
התחביר של הפקודה הוא:
<syntaxhighlight>
typedef <שם חדש לאותו הטיפוס> <טיפוס קיים>
</syntaxhighlight>
 
הפקודה מאפשרת לתת שמות קצרים לטיפוסים מסובכים. לדוגמה:
<syntaxhighlight>
typedef int** AOP; // Array of Pointers
 
int main() {
AOP arr = (AOP) malloc (sizeof(int*) *100);
// ..
 
</syntaxhighlight>
אנו נשתמש ב typdef על מנת לתת שם מקוצר לטיפוסים של מבנים. לדוגמה:
<syntaxhighlight>
struct Person {
int id;
char *name;
};
 
typedef struct Person Person;
 
int main() {
Person per;
per.name = "Avi";
Person* p = &per;
//...
 
</syntaxhighlight>
המטרה פשוט להשתמש בשם Person עבור הטיפוס struct Person.
 
קיימת גם כתיבה מקוצרת לאותה מטרה:
<syntaxhighlight>
typedef struct Person {
int id;
char *name;
} Person;
 
int main() {
Person per;
per.name = "Avi";
Person* p = &per;
//...
</syntaxhighlight>
 
אנו נשתמש בכתיב זה על מנת להמנע מכתיבת המילה struct שוב ושוב.
 
{{תבנית:ניווט מבוא|12|14}}