קורס מבוא למדעי המחשב סמסטר א' תשס"ח
שיעור עשירי: מבנים http://online.shenkar.ac.il/moodle
טיפוסי משתנים בשפת C •
הכרנו במהלך הקורס את טיפוסי המשתנים הבסיסיים שניתן לייצג בשפת :Cמספר שלם/ממשי ,תו ,מחרוזת ,ומערך.
•
כשכתבנו תוכניות לפתרון בעיות כלשהן ,יצגנו את נתוני התוכנית ע"י משתנים מהטיפוסים האלה.
•
כידוע ,בבעיות מציאותית יכולים להיות אובייקטים מורכבים ,שלא בהכרח מתאימים ליצוג ע"י מספר ,תו ,או אפילו מערך. • •
1
למשל בבעיות גיאומטריות מדובר על נקודות ,ישרים ,מצולעים ,וכדומה. מינהלת-האוניברסיטה מטפלת בפקולטות ,קורסים ,סטודנטים ,וכו'.
טיפוסי משתנים בשפת C •
כשכותבים תוכנות לפתרון בעיות מציאותיות ,נוח שיהיו לנו טיפוסי משתנים שמייצגים את האובייקטים המציאותיים האלה. •
•
•
למשל ,לטיפול במספרים מרוכבים היינו רוצים טיפוס שייצג מספר מרוכב. לפתרון בעיות גיאומטריות נירצה למשל טיפוס שמתאר נקודה במישור וטיפוסים שמתארים מצולעים שונים. במערכת המידע של האוניברסיטה נירצה טיפוסי-משתנים עבור סטודנט ,קורס ,וכו' )שכל אחד מהם כולל כמה שדות-מידע רלוונטיים(.
יצירת טיפוסי משתנים חדשים
2
•
שפת Cמאפשרת לנו להגדיר טיפוסי משתנים חדשים שיתאימו לבעיה שאנחנו רוצים לפתור.
•
טיפוס חדש כזה נקרא "מבנה" ) .(structureהפקודה שתשמש אותנו לכך היא .struct
•
בדרך-כלל מבנה כולל כמה משתנים ,שנקראים "שדות" )דוגמא בשקף הבא(.
הגדרת מבנה חדש – דוגמא •
• •
נקודה במישור מיוצגת על-ידי שתי קואורדינטות X ,ו .Y -הגדרת מבנה שייצג נקודה תיראה למשל כך: { struct point ;double x ;double y ;} ההגדרה הזו לא תופיע בתוך פונקציה ,אלא בתחילת הקובץ )בד"כ אחרי שורות ה.(#include - כעת ניתן להגדיר בתוכנית משתנים מהסוג הזה ,כפי שניראה בשקף הבא.
הגדרת מבנה חדש – המשך הדוגמא •
•
דוגמא להגדרת משתנים מהטיפוס החדש: )(int main { struct point P1,P ,P2 ;2 ;return 0 } בכך הגדרנו שתי נקודות )שני משתנים מסוג מבנה ,(point שלכל אחת מהן יש שני שדות.
P1
3
P2
x
x
y
y
הגדרת מבנה חדש – המשך הדוגמא ניתן לגשת לכל אחד מהשדות של מבנה ולכתוב/לקרוא אליו/ממנו .למשל: )(int main { struct point P1,P ,P2 ;2 ;P1.x = 6 ;P1.y = 7 P2 P1 ;P2.x = 4 ;P2.y = 2 x 4 x 6 printf(“%g printf (“%g\\n”, P1 ;)P1.y y 2 y 7 ;return 0 )יודפס (7 }
נושאי השיעור היום מבנים: • • • • • •
הגדרה ואיתחול פעולות על מבנים מבנים ופונקציות מבנים ומצביעים מבנה בתוך מבנה מערכים של מבנים
הקצאה דינאמית :הקצאת משתנים במהלך ריצת התוכנית
4
מבנים -תיאור פורמלי •
•
•
הגדרת מבנה )טיפוס משתנה חדש(: {שם הטיפוס החדש struct {שם ;שם-שדה טיפוס ….. ;שם-שדה טיפוס ;} הגדרת משתנה מהסוג החדש שהגדרנו: ;שם משתנה שם הטיפוס החדש struct פנייה לשדה של משתנה שהוא מבנה נעשית ע"י
.
שם השדה שם המשתנה
הגדרת מבנה חדש -הדוגמא הקודמת
P1
5
P2
6
x
4
x
7
y
2
y
>#include<stdio.h { struct point ;double x ;double y ;} )(int main { struct point P1,P ,P2 ;2 ; P 1 .x = 6 ; P 1 .y = 7 ; P 2 .x = 4 ; P 2 .y = 2 printf(“%g\\n”, P1 printf(“%g ;)P1.y ;return 0 }
איתחול מבנים •
גם משתנים מטיפוס חדש אפשר לאתחל בשורת ההגדרה. למשל בהתייחס לדוגמא הקודמת: struct point P = {6 ;}{6,7 • השדה xשל המשתנה Pיאותחל ל 6-והשדה yל.7-
•
באופן כללי ,האיתחול הוא לפי סדר השדות בהגדרת המבנה, כלומר האיבר הראשון ברשימת האיתחול יכנס לשדה הראשון, השני לשני ,וכן הלאה )אם האיתחול הוא חלקי ,השאר יאותחל באפסים(.
מבנים – הגדרה • • • •
6
מהם מבנים הגדרת מבנים איתחול מבנים שאלות?
פעולות על מבנים •
השמה מתבצעת בצורה הרגילה: • •
•
P1 = P P2 ;2 התוכן של P2פשוט מועתק לתוך ) .P1העתקה שדה-שדה( אם אחד השדות הוא מערך אז גם הוא מועתק )שינוי שלו ב- P1לא ישפיע על .(P2
•
פעולות השוואה )== והאחרות( ופעולות חשבון אינן פועלות עבור מבנים )זה לא יתקמפל(. • כלומר נצטרך לכתוב פונקציות עבור הפעולות שנירצה ליישם )למשל נשווה את כל אחד מהשדות כדי לבצע השוואה(.
דוגמא ::פונקציה להשוואת מבני point דוגמא (int equal equal(struct )struct point p, struct point q { return ( (p.x )(p.x == q.x q.x) && ((p.y ))p.y == q.y ))q.y }
אם הנקודות זהות יוחזר ,1ואחרת 0
7
דוגמא לשימוש במבנים :נכתוב פונקציה שמחשבת מרחק בין שתי נקודות
•
double dist( struct point p, struct point q) { double d; d = (p.x (p.x – q.x)*( q.x)*(p.x p.x – q.x) q.x) + ((p.y p.y– –q.y)*( q.y)*(p.y p.y--q.y); q.y); return sqrt( sqrt(d); }
המשך- דוגמת שימוש במבנים …. int main() { struct point p,q p,q;; printf(“ printf (“Enter Enter x and y coord. coord. of the first point\ point\n”); scanf(“% scanf (“%lf%lf lf%lf”, ”, & &p.x,&p.y p.x,&p.y); ); printf(“ printf (“Enter Enter x and y coord. coord. of the second point\ point\n”); scanf(“% scanf (“%lf%lf lf%lf”, ”, & &q.x,&q.y q.x,&q.y); ); printf(“ printf (“Their Their distance is %g\ %g\n”, dist(p,q dist(p,q)); )); return 0;
}
8
פונקציות ומבנים •
איך מבני הנקודות מועברים לפונקציה?
פונקציות ומבנים
9
•
איך מבני הנקודות מועברים לפונקציה?
•
הם מועברים על ידי העתקת הערכים של השדות שלהם לפרמטרים של הפונקציה. • כמו במשתנים רגילים ) ,(call by valueכמו עבור .int • שינוי הנקודה בפונקציה לא ישנה את הנקודה ב.main -
•
פונקציה יכולה גם להחזיר מבנה ,כמו כל משתנה אחר )גם במקרה הזה הערכים יועתקו(.
פונקציות ומבנים – נקודה לתשומת-לב •
אם אחד השדות של המבנה הוא מערך ,ומעבירים את המבנה הזה לפונקציה ,אז המערך מועתק )לא רק כתובת המערך מועברת(.
•
כלומר אם נשנה בפונקציה מערך שהוא שדה של מבנה ,אז לא תהיה לזה השפעה על המערך המקורי.
•
למשל אם נגדיר את המבנה הבא: struct int_array_10 int_array_10 { [int array array[10 ;]10 ;] ;}
פונקציות ומבנים – נקודה לתשומת-לב אז הפונקציה הבאה לא תשפיע על המשתנה שמועבר אליה: (void zero_array zero_array(struct int_array_10 )int_array_10 A { ;int i ;for(ii=0; i<10 (for )10; i++ מה שמאופס הוא העתק של המערך ,כי מערך =]A.array[[i A.array ]=0 ;0 בתוך מבנה מועתק בהעברה לפונקציה } כדי לשנות את המערך המקורי נשתמש במצביע ,כפי שניראה בהמשך )אושנעביר לפונקציה מצביע למבנה או שנגדיר במבנה מצביע במקום מערך(
10
מבנים -פעולות •
השמה בין מבנים מותרת )מתבצעת העתקה שדה-שדה( פעולות השוואה ופעולות חשבון לא עובדות ניתן להעביר מבנים לפונקציה ולהחזיר ממנה מבנה ,והם מועתקים אליה וממנה ,כמו טיפוס-משתנה רגיל )כמו (int מערך בתוך מבנה מועתק בהעברה לפונקציה ובחזרה ממנה
•
שאלות?
• • •
מבנים ומצביעים •
אם המבנה שלנו מורכב מהרבה מאוד משתנים ,אז העתקת כל המבנה לתוך הפרמטרים של הפונקציה דורשת זמן וזיכרון רבים.
•
כמו-כן ,כמו במשתנים רגילים ,לפעמים נירצה לשנות יותר ממבנה אחד מתוך הפונקציה )ולא רק להחזיר מבנה אחד(.
•
כדי לטפל בשני הנושאים האלה נוכל לשלוח לפונקציה מצביעים למבנים ,כמו ששלחנו מצביעים למשתנים מסוגים אחרים. •
11
בקריאה לפונקציה ישוכפל רק המצביע לכל מבנה ,ובעזרת המצביע נוכל לשנות את המבנה המקורי.
מבנים ומצביעים בדיוק כמו טיפוסי-משתנים שהכרנו בעבר ,אפשר להגדיר מצביע לטיפוס שהוא מבנה )כתובת ההתחלה של מבנה היא כתובת השדה הראשון שלו( .למשל:
P
P.x
P.y
5 6
struct point P={5 ;}P={5,6 ;struct point *ptr ;ptr ; ptr = &P
ptr
מצביעים ומבנים – גישה לשדות המבנה •
בדוגמא הזאת ,כדי להגיע לשדות של Pדרך ptrשמצביע על ,P אפשר להשתמש ב * -כרגיל: *( • (*ptr ptr). P.שקול ל).xx = 3; - P.x x=3 *( • (*ptr ptr). P.שקול ל).yy = 7; - P.y y=7
)צריך סוגריים כי אחרת לנקודה יש קדימות(
12
מצביעים ומבנים – גישה לשדות המבנה •
בדוגמא הזאת ,כדי להגיע לשדות של Pדרך ptrשמצביע על ,P אפשר להשתמש ב * -כרגיל: *( • (*ptr ptr). P.שקול ל).xx = 3; - P.x x=3 *( • (*ptr ptr). P.שקול ל).yy = 7; - P.y y=7
)צריך סוגריים כי אחרת לנקודה יש קדימות( •
אבל בדרך-כלל נשתמש לצורך זה בסימון מקוצר :חץ >- ptr P.שקול לptr->x = 3; - P.x x=3 ptr P.שקול לptr->y = 7; - P.y y=7
•
כלומר משמעות החץ היא גישה לשדה במבנה שמצביעים עליו.
• •
מצביעים ומבנים -דוגמא • פונקציית ההשוואה כשמועברים מצביעים: )int equal( struct point *p, struct point *q { return ((p((p->x == qq->x) && (p(p->y == q;))q->y } equal(&P1 equal(&P 1,&P ,&P2 הקריאה לפונקציה ע"י )2 )הגישה עם חץ היא לשדה של המבנה שמצביעים עליו( במקום:
(int equal equal(struct )struct point p, struct point q
equal(P1 equal(P 1, P2 הקריאה לפונקציה ע"י )P2
{
return ( (p (p.x == q.x) && (p ))(p.y == q.y )הגישה עם נקודה היא לשדה של מבנה(
13
}
מצביעים ומבנים – עוד דוגמא נחשב את המרחק כשמועברים מצביעים ולא המבנים עצמם: double dist(struct )dist(struct point *p, struct point *q { double d,dx,dy ;;d,dx,dy dx = p ;p-->x - q->x dy = p ;p-->y - q->y d = dx dx**dx + dy dy**dy ;;dy (return sqrt ;)sqrt(d
} איך תיראה הקריאה ב?main-
\printf(“The distance is %g &(%g\n”, dist ))dist(&P1,&P2
מבנים ומצביעים • • •
14
כמו עם טיפוסי-משתנים רגילים גישה לשדות של המבנה המוצבע ע"י >- שאלות?
מבנים – כתיבה מקוצרת שמו של הטיפוס החדש שהגדרנו הוא struct point אפשר לחסוך את כתיבת המילה structבאמצעות הפקודה ,typedefשמאפשרת לתת שם חדש לטיפוס-משתנה: { struct point ;double x ;double y ;} typedef struct point point_t ;;point_t
• •
שם חדש טיפוס קיים
מבנים – כתיבה מקוצרת אפשר גם לכתוב את שתי השורות יחד באופן הבא: { typedef struct point ;double x ;double y } point_t ;;point_t •
15
בזכות ה typedef -נוכל לכתוב point_tבמקום לכתוב ) struct pointזה שקול לחלוטין( .למשל: double dist(point_t )dist(point_t *p, point_t *q
עוד על typedef •
נציין שהפקודה typedefיכולה לשמש גם כדי לתת שם חדש לטיפוס קיים שאיננו מבנה.
•
למשל אם נרצה לתת שם מיוחד ל- unsigned int אפשר לכתוב: typedef unsigned int UINT ;;UINT שם חדש
•
שם קיים
עכשיו בכל מקום בתוכנית שנרצה נוכל להשתמש בUINT- כדי להגדיר .unsigned int ;unsigned int ui UINT uiשקול לui; - ;;ui
מבנה בתוך מבנה
16
•
שדות של מבנה יכולים להיות בעצמם מבנים אחרים.
•
לדוגמא ,נאמר שנירצה להגדיר מבנה שמתאר מלבן במישור שמקביל לצירים.
מבנה בתוך מבנה -דוגמא •
מלבן שמקביל לצירים נקבע על-ידי שני קודקודים נגדיים כאלה:
p q
מבנה בתוך מבנה אפשר להגדיר את זה על-ידי הקואורדינטות של שני הקודקודים: { struct rect double xl, xh, xh, yl ;yl,, yh ;yh ;} אבל יותר נוח וברור להגדיר ע"י 2נקודות: { struct rect ;point_t p ;point_t q ;} typedef struct rect rect_t ;;rect_t
17
מבנה בתוך מבנה -אתחול •
גם מבנה שיש בתוכו מבנה אפשר לאתחל בשורת ההגדרה: rect_t r = { {5,6} , {10, ;} }10,2
q
p
כרגיל ערך האיתחול הראשון משוייך לשדה הראשון ,השני לשני, וכן הלאה )ובאיתחול חלקי השאר מאותחל לאפסים(. •
אפשר כמובן גם לאתחל כל שדה ישירות אחרי ההגדרה: ;r.p.x = 5 ;r.p.y = 6 ;r.q.x = 10 ;10 ;r.q.y = 2
מערך של מבנים •
כמו טיפוסי משתנים אחרים ,אפשר גם להגדיר מערך של מבנים.
•
למשל ,אפשר להגדיר מערך של נקודות: )(int main { point_t point_arr ;]point_arr[[20 ;]20 … }
18
מערך של מבנים -דוגמא •
נכתוב פונקציה שמקבלת מערך של מלבנים ,ומחזירה מצביע למלבן עם האלכסון הארוך ביותר.
•
שם הפונקציה יהיה MaxRect הפרמטרים שהיא תקבל :מערך של מלבנים )כלומר הכתובת של המלבן הראשון במערך( ,וגודל המערך. הערך המוחזר :מצביע למלבן
• •
תכנון הפונקציה MaxRect
19
•
הפונקציה תעבור על המערך ותחשב לכל מלבן את אורך האלכסון שלו.
•
היא תשמור את האורך המקסימלי שנמצא עד עכשיו ואת אינדקס המלבן שבו הוא נמצא.
•
אם האלכסון הנוכחי גדול יותר מהמקסימלי שהיה עד עכשיו, נעדכן את המקסימום ואת האינדקס.
•
בסוף יוחזר מצביע באמצעות האינדקס ששמרנו.
איך ניראה מערך של מלבנים בזיכרון המלבנים נשמרים ברצף בזיכרון rect_t rect_t rect_t rect_t rect_t
כל מלבן מכיל שני שדות מסוג נקודה point_t p point_t q
כל נקודה מכילה שני שדות מסוג double double x double y double x double y
rect_t rect_t
מלבן עם אלכסון מקסימלי – MaxRect (rect_t* MaxRect *rect_t [MaxRect(rect_t arr מועברים המערך וגודלו )arr[ ], int size { double max=0 ;max=0 אורך האלכסון המקס' עד כה מאותחל לאפס לאפס אינדקס המלבן המקס' עד כה מאותחל int i, max_index =max_index ;=0 for (i )(i = 0 ; i < size ; i++ עוברים בלולאה על כל המלבנים { נבדק אורך האלכסון. (if ((dist dist(arr [arr[[i].p, arr )arr[i].q) > max { אם מצאנו ארוך יותר אז max = dist(arr [dist(arr[[i]. p, arr מעדכנים את המקסימום ;)arr[i].q ;max_index = i והאינדקס } } מחזירים מצביע למלבן המקסימלי ;return arr + max_index ;max_index )כתובת ההתחלה ועוד האינדקס שלו( }
20
אתחול מערך של מבנים •
גם מערך של מבנים אפשר לאתחל בשורת ההגדרה ,על-ידי כתיבת ערכי איתחול לכל מבנה לפי הסדר.
•
לדוגמא עבור מערך של מלבנים: [rect_t arr ]arr[20 20] = { {{ 3,4} , {{5 5,6}} , {{ 4,7} , {9 }}{9,10 } … 10}} ,
• מבנה בתוך מבנה • מערך של מבנים • שאלות?
21
מבנים – עוד דוגמא מבנים יכולים כאמור להכיל שדות מסוגים שונים. דוגמא למבנה שמייצג סטודנט )בהנחה ששמו באורך של פחות מ 40 -תווים(: {struct student ;int id char name[4 ;]name[40 ;double average ;} ;typedef struct student student_t ;student_t
• •
מבנים -סטודנט נוכל למשל להגדיר מערך של הסטודנטים בכיתה: )נניח ש CLASS_SIZE -הוא קבוע שמכיל את מס' הסטודנטים בכיתה(
•
22
# define CLASS_SIZE 50 )(int main { ;]student_t class[CLASS_SIZE …. } נכתוב לדוגמא פונקציה שמדפיסה את שמות הסטודנטים המצטיינים ומחזירה את מספרם
דוגמא :פונקציה למציאת המצטיינים int top_students )] [top_students((student_t students { ;int i, cnt = 0 )for(ii = 0; i < CLASS_SIZE; i++ (for { if (students[i (students[i]. ].average )average > 90 )90 { printf(“%s printf (“%s\\n”, students[i ;)students[i].name ;cnt++ cnt ;++ } } כאמור מחזירים את מספר המצטיינים ;return cnt ;cnt }
מבנים -סיכום
23
•
שימוש במבנים מאפשר הגדרת טיפוסי משתנים חדשים שקרובים לעולם-הבעיה שאנחנו פותרים.
•
אפשר להשתמש בהם כמו בטיפוסים רגילים מבחינת הגדרת מערך של מבנים ,מצביעים על מבנה ,העברה לפונקציה )למעט העובדה שמערכים מועתקים( ,וכו'.
•
לא ניתן להשתמש בפעולות החשבון וההשוואה הרגילות על מבנים -יש להגדיר פונקציות עבורן בהתאם לצורך )רק פעולת ההשמה מוגדרת ,ובה מתבצעת גם העתקת מערכים(.
•
ע"י typedefניתן לחסוך את כתיבת המילה .struct
קורס תכנות – שיעור עשירי :מבנים
שאלות נוספות?
24