מערכים ומצביעים הקצאה דינאמית של מערכים דו-מימדיים

Slides:



Advertisements
Similar presentations
ממיבחניםC שאלות ++.
Advertisements

מבוא למדעי המחשב לתעשייה וניהול
קורס תכנות שיעור שנים-עשר: ניהול זיכרון 1. הקצאת זיכרון דינאמית עד עכשיו עשינו "הקצאה סטטית" הגדרנו את משתני התוכנית כבר כשכתבנו אותה הקומפיילר הקצה עבורם.
Pointers הרצאה קריטית. השאלות הפתוחות מה זה ה- & שמופיע ב scanf מדוע כשמעבירים מחרוזת ל scanf אין צורך ב & האם ניתן להכריז על מערך שגדלו אינו ידוע בתחילת.
1 מבוא למדעי המחשב הקצאה דינאמית. 2 הקצאת זיכרון דינאמית  כאשר אנו משתמשים במערכים, אנו מקצים אוטומטית את הזיכרון המקסימלי שנצטרך.  בפועל, אנו משתמשים.
תכנות תרגול 9 שבוע : הקשר בין מערכים למצביעים נרצה לעמוד על הקשר בין מערך למצביע מאחר ומערכים הם הכללה של משתנים הרי שברור שלמערך ולכל אחד מאיבריו.
תרגול 5 רקורסיות. רקורסיה קריאה של פונקציה לעצמה –באופן ישיר או באופן עקיף היתרון : תכנות של דברים מסובכים נעשה ברור ונוח יותר, מכיוון שזו למעשה צורת.
תכנות תרגול 4 שבוע : לולאות while לולאות while while (condition) { loop body } במקרה של קיום התנאי מתבצע גוף הלולאה ברגע שהתנאי לא מתקיים נצא.
מבוא לשפת C חידות ונקודות חשובות נכתב על-ידי יורי פקלני. © כל הזכויות שמורות לטכניון – מכון טכנולוגי לישראל.
מבוא למדעי המחשב תרגול 8 - מחרוזות שעת קבלה : יום שני 11:00-12:00 דוא " ל :
תוכנה 1 - חזרה שולי לב יהודי 2 Arrays and Strings מערך - אוסף משתנים בעלי שם משותף. הפנייה לכל איבר נעשית ע ” י אינדקס. ב -C מערך מוגדר.
מבוא למדעי המחשב © אריק פרידמן 1 מצביעים כמערכים דוגמה.
תכנות תרגול 6 שבוע : תרגיל שורש של מספר מחושב לפי הסדרה הבאה : root 0 = 1 root n = root n-1 + a / root n-1 2 כאשר האיבר ה n של הסדרה הוא קירוב.
מבוא למדעי המחשב מחרוזות, מצביעים וכתובות
11 Introduction to Programming in C - Fall 2010 – Erez Sharvit, Amir Menczel 1 Introduction to Programming in C תרגול
תכנות – שיעור 7. חזרה -מערכים נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים זהים נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים.
תכנות תרגול 14 שבוע:
תכנות תרגול 10 שבוע : הקשר בין מערכים למצביעים נרצה לעמוד על הקשר בין מערך למצביע מאחר ומערכים הם הכללה של משתנים הרי שברור שלמערך ולכל אחד מאיבריו.
תכנות תרגול 14 שבוע : רשימות מקושרות ישנו מבנה נתונים אשר מאפשר ישנו מבנה נתונים אשר מאפשר לנו לבצע את הוספת האיברים בצורה נוחה יותר. מבנה זה.
מבוא כללי למדעי המחשב תרגול 3. לולאות while לולאות while while (condition) { loop body } במקרה של קיום התנאי מתבצע גוף הלולאה ברגע שהתנאי לא מתקיים נצא.
קורס תכנות – סימסטר ב ' תשס " ח שיעור שישי: מערכים
מבוא כללי למדעי המחשב רשימות מקושרות
מבוא למדעי המחשב תרגול 6 - מערכים שעת קבלה : יום שני 11:00-12:00 דוא " ל :
תכנות תרגול 5 שבוע : הגדרת פונקציות return-value-type function-name(parameter1, parameter2, …) הגדרת סוג הערכים שהפונקציה מחזירה שם הפונקציהרשימת.
מערכים עד היום כדי לייצג 20 סטודנטים נאלצנו להגדיר עד היום כדי לייצג 20 סטודנטים נאלצנו להגדיר int grade1, grade2, …, grade20; int grade1, grade2, …, grade20;
עקרון ההכלה וההדחה.
מבוא למדעי המחשב תרגול 3 שעת קבלה : יום שני 11:00-12:00 דוא " ל :
תוכנה 1 - תרגול שיעור 10 Pointers (2) שולי לב יהודי
מבוא כללי למדעי המחשב הקצאת זיכרון דינאמית
מתמטיקה בדידה תרגול 2.
1 מבוא למדעי המחשב סיבוכיות. 2 סיבוכיות - מוטיבציה סידרת פיבונאצ'י: long fibonacci (int n) { if (n == 1 || n == 2) return 1; else return (fibonacci(n-1)
1 מבוא למדעי המחשב backtracking. 2 מוטיבציה בעיית n המלכות: נתון: לוח שחמט בגודל. המטרה: לסדר על הלוח n מלכות כך שאף אחת לא תאיים על השנייה. דוגמא: עבור.
מבוא למדעי המחשב תרגול 12 – הקצאת זיכרון דינאמית שעת קבלה : יום שני 11:00-12:00 דוא " ל :
11 Introduction to Programming in C - Fall 2010 – Erez Sharvit, Amir Menczel 1 Introduction to Programming in C תרגול
הגדרת משתנים יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר.
Practice session 3 תחביר ממשי ( קונקרטי ) ותחביר מופשט ( אבסטרקטי ) שיטות חישוב : Applicative & Normal Evaluation Partial Evaluation.
קורס תכנות שיעור שמיני: הקצאת זיכרון דינאמית, הצצה לייצוג ועיבוד תמונות 1.
Practice session 3.  תחביר ממשי ( קונקרטי ) ותחביר מופשט ( אבסטרקטי )  שיטות חישוב : Applicative & Normal Evaluation.
קורס תכנות שיעור עשירי: מיונים, חיפושים, וקצת סיבוכיות חישוב.
תכנות מכוון עצמים ושפת ++C וויסאם חלילי. TODAY TOPICS: 1. Function Overloading & Default Parameters 2. Arguments By Reference 3. Multiple #include’s 4.
מבנים קרן כליף. ביחידה זו נלמד :  מהו מבנה (struct)  איתחול מבנה  השמת מבנים  השוואת מבנים  העברת מבנה לפונקציה  מבנה בתוך מבנה  מערך של מבנים.
מבוא למדעי המחשב לתעשייה וניהול הרצאה 6. מפעל השעווה – לולאות  עד עכשיו  טיפלנו בייצור נרות מסוג אחד, במחיר אחיד  למדנו להתמודד עם טיפול במקרים שונים.
הגדרת משתנים יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016.
מספרים אקראיים ניתן לייצר מספרים אקראיים ע"י הפונקציה int rand(void);
Programming Arrays.
Computer Architecture and Assembly Language
מבוא למדעי המחשב סיבוכיות.
הקצאות דינאמיות בשילוב מבנים
מבוא למדעי המחשב – סמסטר א' תשע"ב
מצביעים קרן כליף.
SQL בסיסי – הגדרה אינדוקטיבית
מערכים קרן כליף.
קורס תכנות – סמסטר ב' תשס"ח
הקצאות דינאמיות קרן כליף.
פרוקטוז, C6H12O6 , חד-סוכר מיוחד
Introduction to Programming in C
מצביעים קרן כליף.
הקצאת זיכרון דינאמית מבוא כללי למדעי המחשב
Engineering Programming A
תרגול 8 תחומי הכרה פונקציות
מבוא כללי למדעי המחשב תרגול 6
מבוא למדעי המחשב – סמסטר א' תשע"ד
מערכים של מצביעים הקצאה דינאמית
Programming in C תרגול Introduction to C - Fall Amir Menczel.
תוכנה 1 תרגול 13 – סיכום.
מבוא למדעי המחשב מצביעים.
תירגול 8:מצביעים והקצאה דינאמית
Computer Programming תרגול 3 Summer 2016
Engineering Programming A
Computer Architecture and Assembly Language
Presentation transcript:

מערכים ומצביעים הקצאה דינאמית של מערכים דו-מימדיים מכללת אורט כפר-סבא תכנות מערכות בשפת C מערכים ומצביעים הקצאה דינאמית של מערכים דו-מימדיים 09.11.14 אורי וולטמן uri.weltmann@gmail.com

חידה לחימום לילך ויוסי נכנסים לחדר. על ראש כל אחד מהם שמים כובע. צבע כל כובע הינו אדום או כחול (50% סיכוי לכל צבע). כיון שצבעו של כל כובע יכול להיות כחול או אדום, ייתכן שצבעי שני הכובעים שווים. ייתכן גם, כמובן, שצבעיהם שונים. לאחר השמת הכובעים על הראשים, כל אחד מביט על הכובע של השני, ורושם על פתק ניחוש של צבע הכובע שלו. הרישום הוא באותו הזמן, ואין סימנים ביניהם. מטרתם בניחוש היא שאחד מהם ינחש נכונה. ניתן לקבוע חוק ניחוש ללילך וחוק ניחוש ליוסי, שינחו את הניחוש של כל אחד. החוקים יכולים להיות זהים, או שונים זה מזה, וייתכן שבחלק מן המקרים לילך תנחש נכון ובחלק מן המקרים יוסי ינחש נכון. מהו החוק ללילך, ומהו החוק ליוסי כך שתמיד אחד מהם ינחש נכון?

מצביעים ומערכים כידוע, מערך הוא רצף של תאים בזיכרון. מצהירים על מערך באופן הבא: int A[8]; כעת, נגדיר מצביע ל-int: int *p; ונוכל לבצע השמה לתוך המצביע, בעזרת האופרטור '&': p = &A[0]; אם נבצע את ההוראה הבאה: printf (“%d”, *p); אז יוצג כפלט תוכנו של התא ש-p מצביע עליו (התא הראשון במערך A). כלומר: יוצג כפלט המספר 6. 6 3 1 7 8 25 18 21

מצביעים ומערכים ניתן לומר שאם p מצביע לתא מסוים במערך (כלומר: מכיל את כתובתו בזיכרון של תא זה), אז p+1 הוא הכתובת של האיבר הבא במערך, מכיוון שאיברי המערך נמצאים בזיכרון ברצף. במקרה שלנו, p+1 הוא הכתובת של תא A[1]; ואילו p+2 הוא הכתובת של תא A[2]. באופן כללי: הביטוי p+i מצביע לתא ה-i שאחרי p. לכן, אם p מצביע לתא הראשון במערך (p = &A[0]), הרי שהביטוי *(p+1) שקול ל-A[1], והביטוי *(p+2) שקול ל-A[2], ובאופן כללי *(p+i) שקול ל-A[i]. 6 3 1 7 8 25 18 21

מצביעים ומערכים מה יבצע קטע הקוד הבא? int A[8]; int *p; p = &A[0]; *(p+7) += 1; את ההשמה השנייה ניתן היה גם לרשום: *(p+7)++. את ההשמה הראשונה ניתן היה גם לרשום: p = A, וזאת מפני ששם המערך מתפקד ככתובת של התא הראשון שלו. 6 3 1 7 8 25 18 21

סוגי מצביעים עתה ניתן להבין, מדוע אנו עושים את ההבחנה בין מצביע ל-int, מצביע ל-float, מצביע ל-char, וכו'. הסיבה היא שאם נרשום p+1, צריך לדעת בכמה בתים צריך להתקדם בזיכרון. למשל, אם p הוא מצביע ל-int, אז p+1 (בסביבת עבודה שבה int תופס 2 בתים) זו כתובת של תא בזיכרון שנמצא 2 בתים אחרי p. אם p יהיה מצביע ל-long, אז p+1 יהיה כתובת של תא בזיכרון שנמצא 4 בתים אחרי p. אם p הוא מצביע ל-char, אז p+1 זו כתובת שגדולה בבית אחד מהכתובת של p.

תרגיל האם קטע התכנית הבא חוקי? מה יהיה הפלט של קטע התכנית הבא? int arr[5] = {19,44}, *p; p = arr; *(p+1) = 6; p[2] = 100 + p[4] + p[0]; מה יהיה הפלט של קטע התכנית הבא? int arr[5] = {1,2,3,4,5}; char *p; while (*p != 5) { printf (“%d ”, *p); p++; } הסבירו מדוע ניתן להחליף את השורה השנייה ל: char *p = arr. הסבירו כיצד ניתן לקצר את גוף הלולאה להוראה אחת.

מצביעים ומערכים ההבדל בין שם של מערך לבין מצביע, הוא שבעוד שמצביע הוא משתנה וניתן לשנות את ערכו, שם המערך הוא כתובת קבועה. לדוגמא: #include <stdio.h> int main() { int mar[100], num; int *ptr = mar; ptr++; /* חוקי */ mar++; /* שגיאה! */ ptr = &num; /* חוקי */ mar = &num; /* שגיאה! */ return 0; }

מערך של מצביעים עקבו, שורה אחרי שורה, אחרי התכנית הבאה: #include <stdio.h> int main() { int arr[20], x, y; int *ptr[10]; ptr[0] = &x; ptr[1] = &arr[3]; ptr[2] = arr + 5; ptr[3] = &y; ptr[4] = ptr[3]; ptr[5] = &x; *ptr[5] = 17; return 0; }

מערכים רב-מימדיים נניח שיש בתכנית שלנו הצהרה על מערך רב-מימדי: int mat[5][4]; מה המשמעות? פירוש החלק המודגש בקו תחתי: "אנחנו מצהירים על מערך בגודל חמישה איברים ושמו mat". אבל מהו טיפוס כל איבר? פירוש החלק המודגש בקו תחתי: "טיפוס כל איבר הוא מערך של ארבעה מספרים שלמים".

מערכים רב-מימדיים את המערך הדו-מימדי mat ניתן למלא בנתונים, כאילו היה מדובר בחמישה מערכים שונים, שכל אחד מהם הוא בעצמו מערך בגודל 4: int mat[5][4] = {{13,25,16,22}, {6,2,2,19}, {4,0,3,31}, {22,-9,33,22}, {5,5,5,1}}; ניתן להבין זאת כאילו אתחלנו בבת אחת, חמישה מערכים בגודל 4 תאים כל אחד: mat[0] = {13,25,16,22}; mat[1] = {6,2,2,19}; mat[2] = {4,0,3,31}; mat[3] = {22,-9,33,22}; mat[4] = {5,5,5,1};

מערכים רב-מימדיים לחלופין, מכיוון שמערכים מאוחסנים בצורה רציפה בזיכרון, ניתן היה לאתחל את המערך הדו-מימדי על-ידי הצבת בלוק הנתונים הבא: int mat[5][4] = {13,25,16,22,6,2,2,19,4,0,3,31,22,-9,33,22,5,5,5,1}; איך הקומפיילר מפרש את שם המערך mat? באותו אופן ששם המערך פורש עבור מערך חד-מימדי – בתור הכתובת של האיבר הראשון: &mat[0][0]. איך הקומפיילר מפרש את הביטוי mat+1? ידוע לקומפיילר שבמערך יש 4 עמודות, ולכן mat+1 פירושו הכתובת בזיכרון הנמצאת 4*sizeof(int) בתים אחרי הכתובת mat. כלומר, זוהי כתובת הזיכרון של האיבר המופיע הראשון בשורה השנייה: &mat[1][0].

תרגיל בהינתן ההצהרה הבאה: מה יהיה הפלט שיוצג בעקבות ההוראות הבאות: int mat[5][4] = {{13,25,16,22}, {6,2,2,19}, {4,0,3,31}, {22,-9,33,22}, {5,5,5,1}}; מה יהיה הפלט שיוצג בעקבות ההוראות הבאות: printf (“%d\n”, mat[0][0]); printf (“%d\n”, mat); printf (“%d\n”, mat + 1); printf (“%d\n”, mat[2]); printf (“%d\n”, mat[2][0]); printf (“%d\n”, mat[2][3]); printf (“%d\n”, (*(mat[2] + 3)); printf (“%d\n”, *(*(mat + 2) + 3));

מערכים רב-מימדיים כשם שבמערכים חד-מימדיים, הביטויים הבאים שקולים: vector[i] *(vector + i) כך במערכים דו-מימדיים, הביטויים הבאים שקולים: matrix[i][j] *(*(matrix + i) + j) אילו חישובים מבצע הקומפיילר כדי לחשב את ערכו של הביטוי *(*(matrix + i) + j) , בהנחה ש-matrix הוגדר כמערך של ROWS שורות וCOLS- עמודות? הוא מפרש את שם המערך matrix בתור כתובת בזיכרון. הוא מוסיף לה i * COLS * sizeof(…) בתים. הוא מוסיף לכתובת שנתקבלה עוד j * sizeof(…) בתים. נשים לב שהקומפיילר נזקק למספר עמודות המערך (המימד השני).

מערכים רב-מימדיים נביט בתכנית הבאה. #include <stdio.h> void foo (int a[][]) { int i,j; for (i = 0; i < 5; i++) for (j = 0; j < 4; j++) printf ("%d ", a[i][j]); } int main() int mat[5][4] = {{13,25,16,22}, {6,2,2,19}, {4,0,3,31}, {22,-9,33,22}, {5,5,5,1}}; foo(mat); return 0; נביט בתכנית הבאה. התכנית לא תעבור קומפילציה, ותופיע הודעת השגיאה: "invalid use of array with unspecified bounds" (לגבי השורה עם ה-printf). הסיבה לכך היא שצריך לדעת את מספר עמודות המערך הדו-מימדי על מנת להיות מסוגלים להגיע אל כל איבר בו. אם נוסיף לכותרת הפונקציה את המימד השני, אז הבעיה תיפטר והתכנית תרוץ. במערכים רב-מימדיים מסדר גבוה (תלת-מימדיים ומעלה), צריך לציין בכותרת הפונקציה את כל המימדים למעט הראשון. a[][4])

מצביעים ומערכים מה משמעות שתי ההגדרות הבאות: typedef int vector[4]; typedef vector matrix[5]; השורה הראשונה מגדירה את vector להיות מערך של 4 שלמים, והשורה השנייה מגדירה את matrix להיות מערך של חמישה vector. כלומר: מערך של חמישה מערכים בני ארבעה איברים. שלושת ההצהרות הבאות שקולות: matrix m; vector m[5]; int m[5][4]; ומה תעשה ההצהרה הבאה:vector *p ? תגדיר את p להיות מצביע ל-vector, קרי: מצביע למערך של ארבעה תאים מטיפוס שלם. על כן ההשמות הבאות חוקיות: p = &m[0] , p = m .

מצביעים ומערכים האם ניתן לכתוב קוד השקול לשתי ההוראות הבאות, אך מבלי להשתמש בפקודה typedef? typedef int vector[4]; vector *p ניתן לעשות זאת, אם כי הקוד שנקבל פחות קריא: int (*p)[4]; נשים לב שהוראה זו אינה שקולה להוראה הבאה: int *p[4]; ההוראה הקודמת מגדירה את p להיות מצביע למערך של ארבעה שלמים, ואילו ההוראה האחרונה מגדירה את p להיות מערך של ארבעה מצביעים לשלמים.

מערכים של מחרוזות מכיוון שבשפת C מחרוזת היא מערך של תווים (המכיל את התו המיוחד '\0'), הרי שמערך של מחרוזות הוא למעשה מערך דו-מימדי. נביט בהצהרה הבאה: char names[5][6] = {“Dan”,”John”,”Eden”,”Billy”,”Dena”}; השם הארוך ביותר במערך (“Billy”), אורכו חמישה תווים. מדוע, אם כך, בחרנו להגדיר את המימד השני של המערך להיות בגודל 6? זוהי תמונת הזיכרון שתיווצר בעקבות ההצהרה: D a n \0 J o h n \0 E d e n \0 B i l l y \0 D e n a \0

מערכים של מחרוזות מה יהיה הפלט של התכנית הבאה? #include <stdio.h> int main() { char names[5][6] = {“Dan”,”John”,”Eden”,”Billy”,”Dena”}; int i; for (i = 0; i < 5; i++) puts(names[i]); return 0; } את מערך השמות ניתן היה להגדיר גם כך: char *names[5] = {“Dan”,”John”,”Eden”,”Billy”,”Dena”}

מצביעים ומערכים כאמור, שני הביטויים הבאים הם שקולים: vector[i] *(vector + i) בנוסף, גם שני הביטויים הבאים שקולים: matrix[i][j] *(*(matrix + i) + j) בכל מקום בו משתמשים בסימון של מערך (גישה לאיבר על-ידי סוגריים מרובעים), ניתן להשתמש במצביעים. ההבדל המשמעותי היחידי בין מערכים למצביעים הוא שמצביעים הם משתנים (variables) ניתן להציב בהם כתובות שונות ולגרום להם להצביע על מקומות שונים בזיכרון (p = &num). לעומת זאת, מערכים הם קבועים (constants) ואומנם ניתן לשנות את תוכן תאי המערך, אך לא ניתן להציב בתוך מערך כתובת זיכרון חדשה (arr = &arr2... אסור!).

תרגיל נתונים המשתנים הבאים בזיכרון המחשב: int a[] = {6,5,4,3,2,1}; char *b[] = {"Trust","i","seek","and","i","find","in","you"}; char *c = "hAppY"; char *d[] = {b[a[2]], b[a[0] - a[5]]}; int e[2][3] = {{0,3,4},{1,2,7}}; int f[] = {b[3][2] - b[4][0] , a[2]+a[4] , e[1][2] - e[0][1]}; char *g = b[3]; חשבו את ערכם של הביטויים הבאים: c[1] 2[a] f[2] + *(f+1) *(f+a[5]) **(d+1) (g+1)[1] *(*(b+a[0])+e[0][0])

המשך התרגיל מגדירים טיפוס חדש: ונתון קטע הקוד הבא: struct quack { int x; double y; char *s; int a[3]; }; ונתון קטע הקוד הבא: struct quack h, *k; h.x = 1; h.y = 4.7; h.s = b[3]; h.a[0] = a[2]; h.a[1] = a[1]; h.a[2] = a[2]; k = &h; חשבו את ערכם של הביטויים הבאים: h.s[1] (*k).s[2] k->s[2] h.x + k->y *(f + h.x) *(h.s + k->x)

שקף התאוששות מהתרגיל הקשה

הקצאה דינאמית למדנו בעבר כיצד ניתן להשתמש בפקודות כגון malloc או calloc על מנת להקצות דינאמית זיכרון עבור מערך. מעבירים ל-malloc, למשל, פרמטר יחיד המציין את הגודל בבתים של בלוק הנתונים אותו מעוניינים להקצות, והפונקציה malloc תקצה בזיכרון בלוק מתאים, ותחזיר את הכתובת של תחילת הבלוק. בגרסאות ישנות של שפת C, לפני פרסום התקן ANSI-C בשנת 1989, הפונקציה malloc הייתה מחזירה משתנה מטיפוס char *, והיה צריך לעשות לו הסבה (casting) בצורה מפורשת לטיפוס המבוקש. לדוגמא: p = (int *)malloc(20*sizeof(int)) כיום, בכל הקומפיילרים שנבנו לפי תקן ANSI-C, הפונקציה malloc מחזירה מצביע מהסוג void *, שמומר אוטומטית לטיפוס המבוקש. במידה ואין מספיק זיכרון פנוי, או שפעולת הפונקציה נכשלת מכל סיבה שהיא, הפונקציה malloc מחזירה NULL. תמיד צריך לבדוק את הערך המוחזר על-ידי הפונקציה!

הקצאה דינאמית של מערך דו-מימדי מעוניינים להקצות בצורה דינאמית מערך דו-מימדי. מהן הדרכים שעומדות לרשותנו לעשות זאת? יש 3 שיטות שונות: בראשונה משתמשים אם ידוע מראש המימד השני (מס' העמודות). בשנייה משתמשים אם שני המימדים אינם ידועים, ואין חשיבות לכך שתאי המערך יוקצו בזיכרון בצורה רציפה. בשלישית משתמשים אם שני המימדים אינם ידועים, ויש חשיבות לכך שתאי המערך יוקצו בזיכרון בצורה רציפה.

שיטה א': אם מספר העמודות ידוע מראש #include <stdio.h> #include <stdlib.h> #define COLS 5 int main() { int nrows; /* מספר השורות במערך הדו-מימדי */ int i,j; /* אינדקסים לשורה ולעמודה */ int (*a)[COLS]; /* שלמים COLS מצביע למערך של */ printf (“How many rows? “); scanf (“%d”, &nrows); a = malloc(nrows * COLS * sizeof(int)); if (a == NULL) return 1; for (i = 0; i < nrows; i++) for (j = 0; j < COLS; j++) scanf (“%d“, &a[i][j]); return 0; } אם בזמן הקומפילציה ידוע מראש מספר העמודות במערך (המימד השני של המערך הדו-מימדי), ניתן להקצות זיכרון באופן הבא: סטודנט טען כי בתכנית נוצרת דליפת זיכרון (memory leak), שכן את הזיכרון שהוקצה דינאמית לא שחררנו בעזרת free. האם הוא צודק?

הקצאה דינאמית של מערך דו-מימדי השיטה בה נקטנו להקצאת זיכרון דינאמית של מערך דו-מימדי פועלת רק אם מספר העמודות ידוע בזמן הקומפילציה. אם מעוניינים ששני מימדי המערך ייקבעו בזמן הריצה (למשל: על-ידי קלט מהמשתמש), צריכים לפעול בצורה אחרת. אם מעוניינים להקצות דינאמית מערך דו-מימדי של nrows שורות ו-ncols עמודות, אפשר לנהוג בשיטה הבאה: ניצור (ע"י הקצאה דינאמית) מערך של nrows איברים מטיפוס 'מצביע למערך'. נעבור על כל איברי המערך החד-מימדי, ובכל אחד מאיבריו נציב את הכתובת המתקבלת כתוצאה מהקצאה דינאמית של מערך חד-מימדי בגודל ncols. כעת נקבל מבנה דינאמי שניתן להתייחס אליו כאל מערך דו-מימדי.

שיטה ב': מצביע למערך של מצביעים למערך #include <stdio.h> #include <stdlib.h> int main() { int nrows, ncols; int i,j; int **a; printf (“How many rows and columns? “); scanf (“%d”, &nrows); scanf (“%d”, &ncols); a = malloc(nrows * sizeof(int *)); if (a == NULL) return 1; ... a

שיטה ב': מצביע למערך של מצביעים למערך ... for (i = 0; i < nrows; i++) { a[i] = malloc(ncols * sizeof(int)); if (a[i] == NULL) return 1; } for (i = 0; i < nrows; i++) for (j = 0; j < ncols; j++) scanf (“%d”,&a[i][j]); return 0; a

הקצאה דינאמית של מערך דו-מימדי *(*(a + i) + j) a a[i][j] =

הקצאה דינאמית של מערך דו-מימדי לצורך הקצאת המערך הדו-מימדי, היה צריך להשתמש ב-nrows+1 קריאות ל-malloc (קריאה אחת עבור מערך חד-מימדי של מצביעים לשורות, וקריאה אחת עבור nrows מערכים חד-מימדיים שכל אחד מהם מייצג שורה). לאחר סיום ההקצאה הדינאמית, אנחנו נתעלם מהמבנה המורכב שמסתתר מאחורי הקלעים (a הוא מצביע למערך של מצביעים לתוך מערך של שלמים...), ונעבוד עם a כאילו היה מערך דו-מימדי רגיל: a[2][1] = 5, וכו'. הבדל משמעותי בין המערך הדו-מימדי המתקבל כתוצאה מהקצאה דינאמית כזו, לבין מערך דו-מימדי רגיל, הוא שמערך דו-מימדי רגיל יושב בזיכרון בצורה רציפה (כלומר: מדובר ברצף של תאי זיכרון אחד אחרי שני). למערך דו-מימדי רגיל, אפשר, אם רוצים, להתייחס כאל מערך חד-מימדי ארוך.

הקצאה דינאמית של מערך דו-מימדי דרך אחרת להקצות דינאמית מערך דו-מימדי, שתבטיח שהמערך הדו-מימדי יאוחסן בזיכרון בצורה רציפה, היא להקצות קודם מערך חד-מימדי ארוך. לאחר מכן, נקצה מערך חד-מימדי של מצביעים, ונציב בכל מצביע כתובת של איבר מתוך המערך החד-מימדי (כתובתו של האיבר הראשון בכל 'שורה').

שיטה ג': הקצאה בצורה רציפה #include <stdio.h> #include <stdlib.h> int main() { int nrows, ncols; int i,j; int **a; int *b; printf (“How many rows and columns? “); scanf (“%d”, &nrows); scanf (“%d”, &ncols); b = malloc(nrows * ncols * sizeof(int)); if (b == NULL) return 1; ... b a

שיטה ג': הקצאה בצורה רציפה ... a = malloc(nrows * sizeof(int *)); if (a == NULL) return 1; for (i = 0; i < nrows; i++) a[i] = b + (i * ncols); for (j = 0; j < ncols; j++) scanf (“%d”, &a[i][j]); return 0; } b a

שיטה ג': הקצאה בצורה רציפה a[i][j] = a *(*(a + i) + j)

הקצאה דינאמית של מערך דו-מימדי לצורך הקצאת המערך הדו-מימדי בצורה רציפה, היה צריך להשתמש פעמיים ב-malloc: קריאה אחת לצורך הקצאת מערך חד-מימדי ארוך באורך nrows*ncols. קריאה שנייה לצורך הקצאת מערך של nrows מצביעים (שכל אחד מהם יצביע על תחילת 'שורה' במערך). לאחר סיום ההקצאה הדינאמית, אנחנו נתעלם מהמבנה המורכב שמסתתר מאחורי הקלעים (a הוא מצביע למערך של מצביעים לתוך מערך של שלמים...), ונעבוד עם a כאילו היה מערך דו-מימדי רגיל: a[2][1] = 5, וכו'. בשיטה זו, ניתן לנצל את העובדה שהמערך מאוחסן בזיכרון באופן רציף ולעבד אותו גם בצורה זו, אם מעוניינים בכך. יתרון נוסף של שיטה זו הוא שפשוט יותר לשחרר את הזיכרון שהקצנו (בעזרת free).