מערכים

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

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

מערך סטטי

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

המערך מוגדר בדרך הבאה:

type TMyArray = array[0..9] of Integer;

ההגדרה כאן מגדירה טיפוס למערך סטטי, עם 10 תאים (מתחילים מ0 במקרה הזה ומסתיימים בתא 9) שיכילו ערך של מספר שלם.

במידה ונרצה להשתמש בטווח הערכים האפשריים של טיפוס נתונים (נגיד הטיפוס הבוליאני הרגיל בפסקל המכיל 2 ערכים בלבד 0 ו 1), ניתן להגדיר את המערך גם בצורה הבאה:

type TMyArray = array[Boolean] of Integer;

השימוש במערך יתבצע בצורה הבאה:

var MyArray : TMyArray;

ויש לנו משתנה שהוא מטיפוס המערך שהגדרנו. השימוש במשתנה יתבצע בצורה הבאה:

MyArray[0] := 1;

כמו כמעט כל מערך שאציג כאן, ריצה מלאה על כל המערך תתבצע בצורה הבאה:

for i := low(MyArray) to High(MyArray) do
...MyArray[i] ...

השימוש בפונקציה low מחזירה לנו את הערך זיכרון הנמוך ביותר של המשתנה שלנו (במקרה הזה תא מספר 0, אבל זה יעבוד גם אם התא שלנו מתחיל ב 100 או 1). הפונקציה high מחזירה לנו את הערך הגבוהה של הזכרון שלנו, כאשר במקרה הזה, את תא מספר 9, אבל כאמור זה יכול להיות כל תא שהוא בו הגודל של המערך מסתיים.

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

מערך דינאמי

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

שימו לב כי ההגדרה של מערך דינאמי תמיד תדרוש מאיתנו להגדיר את המערך בתור טיפוס, אחרת נגדיר סוג אחר של מערך (עליו נדבר בכותרת הבאה) שנקרא מערך פתוח.

ההגדרה של מערך דינאמי תראה בצורה הבאה:

type TMyDynamicArray = array of Integer;

במידה ונרצה להגדיר מטריצה נגדיר אותה בצורה הבאה:

type TMyDynamicMatrix = array of array of Integer;

מה שאמרנו כאן הוא שהמערך הוא מערך של מספרים שלמים, ובכך יצרנו מטריצה בהגדרה שלנו. אם נשתמש בהגדרה הראשונה שהגדרנו (כלומר הגדרת הווקטור), לא נוכל ליצור מימנו מטריצה, אבל ניתן יהיה להשתמש בטיפוס עצמו בתור הטיפס של TMyDynamicMatrix, ובכך ניתן יהיה גם להשיג מטריצה. כלומר:

type TMySecondDynamicMatrix = array of TMyDynamicArray;

הסיבה לכך כמובן ברורה. הגדרנו ווקטור של ווקטור, דבר שיוצר מטריצה.

השימוש במערכים כמובן יהיו בצורות הבאות:

var

MyDynamicArray : TMyDynamicArray;

MyDynamicMatrix : TMyDynamicMatrix;

MySecondDynamicMatrix : TMySecondDynamicMatrix;

הגדרת הגודל של המשתנים תתבצע באמצעות הפרוצדורה SetLength בצורה הבאה:

SetLength(MyDynamicArray, 10);
SetLength(MyDynamicMatrix, 10);
SetLength(MyDynamicMatrix[0], 10);

השימוש בפרוצדורה בעצם קובע את כמות התאים ל10 תאים (כלומר מ 0 ועד 9). שימו לב שהשימוש בפרוצדורה תמיד יתחיל את המערך בתא 0. הקריאה השלישית של הפרוצדורה, אומרת ששורה במטריצה היא גם באורך של 10 תאים, כלומר אנחנו מתחילים בתא 0 בשורה ו0 בעמודה.

הריצה על המערך תהיה זהה לדוגמא של המערך הסטטי, כלומר:

for i := low(MyDynamicArray) to High(MyDynamicArray) do
... MyDynamicArray[i] ...

כך שהשימוש לא משתנה.

מערך פתוח

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

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

השימוש במערך פתוח יכול להראות טיפה שונה מאשר הצורה שנשתמש בשאר המערכים. בפרוצדורות נראה את ההגדרה בצורה הבאה:

procedure GetData(Data : array of Integer; ToHandle : Boolean);

הפרוצדורה GetData מקבלת 2 פרמטרים. הראשון הוא Data אשר הוא מערך פתוח (שימו לב שההגדרה של המערך התבצאה בתוך ההגדרת של הפרוצדורה), והפרמטר השני הוא ToHandle שהוא משתנה מטיפוס בוליאני. בניגוד ל ג'אוה וC, כאן ניתן להבחין בין מערך פתוח לבין משתנה רגיל (ולכן שמתי את המשתנה Data בתור המשתנה הראשון). השימוש בפרוצדורה יראה בצורה הבאה:

GetData([10, 15, 0, 8], False);

כמו שניתן לראות, יש סוגריים מרובעים סביב הפרמטר הראשון, דבר שיוצר את ההפרדה בה ניתן לדעת איפה הנתונים של הData לבין הנתונים של ToHandle.

אם ההגדרה של המערך הפתוח תהיה הפרמטר האחרון, ונשתמש בהcalling convention של C, אז ההגדרה תהיה זהה לרעיון של var args של C . אותו הדבר במידה ואני מייצא את הפרוצדורה בספרייה משותפת (או מייבא את הפרוצדורה).

עוד שימוש בתור פרמטרים של מערך פתוח יראה בצורה הבאה:

procedure GetData(Data : Array of Const);

שימו לב למשהו מעניין. לא הגדרנו טיפוס למערך. זה אומר שניתן להעביר למערך כל טיפוס הקיים בפסקל, כלומר:

GetData([10.1, 'Hello World', TObject, MyArray, 10]);

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

במידה ונהדר את הפרוצדורה השלמה, המהדר יהדר את זה ללא שום בעיה. כיצד זה יתכן ?

ובכן ה const מומר לטיפס משתנה בשם TVarRec. הטיפוס הוא בעצם רשומה המבצעת פעולת union על המידע, ובהתאם לסוג המידע של התא, הוא ידע לתת לנו את התוכן בצורה הנכונה. שימו לב שכל טיפוס שהוא לא ידע לזהות עד הסוף, הוא יתנהג אליו כאילו הוא מצביע, ולכן נקבל את הכתובת זכרון של התוכן ולא את הטיפוס האמיתי שלו.

השימוש ב TvarRec מתבצע בצורה הבאה:

For i:=0 to High(Data) do
begin
write (’Argument ’,i,’ has type ’);
case Data[i].vtype of
vtinteger :
Writeln (’Integer, Value :’,Data[i].vinteger);
vtboolean :
Writeln (’Boolean, Value :’,Data[i].vboolean);
vtchar :
Writeln (’Char, value : ’,Data[i].vchar);
vtextended :
Writeln (’Extended, value : ’,Data[i].VExtended^);
vtString :
Writeln (’ShortString, value :’,Data[i].VString^);
vtPointer :
Writeln (’Pointer, value : ’,Longint(Data[i].VPointer));
vtPChar :
Writeln (’PCHar, value : ’,Data[i].VPChar);
vtObject :
Writeln (’Object, name : ’,Data[i].VObject.Classname);
vtClass :
Writeln (’Class reference, name :’,Data[i].VClass.Classname);
vtAnsiString :
Writeln (’AnsiString, value :’,AnsiString(Data[I].VAnsiStr
else
Writeln (’(Unknown) : ’,Data[i].vtype);
end;
end;

ניתן לדבר עוד הרבה על מערך פתוח, אבל אני מקווה שהצלחתי להסביר את הבסיס עליו.

מצביע כמערך

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

למעשה שיטה זו קיימת הרבה מאוד שנים (משנות ה80), אך מרבית האנשים אינם מכירים את הנושא. כיצד זה עובד ?

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

var nzString : PChar;

position : Pointer;

המשתנה position הוא מסוג Pointer, המכיל כתובת זכרון של מצביע וזהו. מקביל ל * void בשפת C.

השלב הבא יהיה לאתחל את הזכרון:

nzString := GetMem(1024);

עכשיו נתחיל להתעסק עם המשתנה בתור מערך:

position := nzString;

while (position < nzString + 1023) do

begin

nzString[position] := 'H';

inc(position);

end;

צריך להיזהר לא לגרום ל Buffer Overflow או למיקום לא נכון של הזכרון, ממש כמו הבעיות שיש לנו עם C בשימוש הזה.

שימו לב שהמשתנה שהגדרנו הוא Null Terminated String (ככה שאם כבר הולכים לתת דוגמא כמו C, אז נעשה את זה עד הסוף🙂 ).

מערך מטיפוס Variant

כמו שכבר הסברתי בעבר, יש טיפוס מסוג Variant, אשר יכול להיות כמט כל טיפוס אפשרי, כולל מערכים.

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

סיכום

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

2 מחשבות על “מערכים

  1. פינגבק: מערכי variant « לראות שונה

  2. פינגבק: מערך כתכונה | לראות שונה

כתיבת תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s