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

מהו מערך?

עריכה

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

בתרשים הבא, לדוגמה, מוצג המשתנה grade, שהוא כעין תיבה (המורכבת משני בתים), ובתוכה כעת המספר 80. בתרשים גם מוצג המערך grades, מערך באורך ארבע, שהוא כעין שורה של ארבע תיבות (שכל אחת מהן מורכבת משני בתים), ובתיבות כעת המספרים 90, 80, 56, ו-100.

 
משתנה ומערך

הגדרת מערך

עריכה

הגדרת מערך צריכה להיות מהצורה:

<type> <name>[<size>];

כאשר type הוא סוג המשתנה של אברי המערך, name הוא השם שאנו בוחרים למערך כולו, ו-size (שחייב להיות שלם חיובי) הוא מספר האיברים שמכיל המערך.

לדוגמה, נתבונן בקטע הקוד הבא:

int array1[30];
char array2[50];
double array3[1];
  1. השורה הראשונה מכריזה על מערך שלמים array1 בעל 30 איברים.
  2. השורה השניה מכריזה על מערך תווים array2 בעל 50 מקומות.
  3. השורה השלישית מכריזה על מערך מספרי נקודה צפה array3 בעל מקום אחד.

גישה לאברי מערך

עריכה

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

array1[2] = 5;

בדוגמה זו, המשתנה שהאינדקס שלו הוא 2 במערך array1 שהוגדר קודם מקבל את הערך 5.


בשפת C נהוג כי האינדקס הראשון במערך הוא 0 ולא 1. כלומר,

array1[0]

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

array1[30]

נחרוג בכך מגבולות המערך, כי האיבר מס' 29 הוא האיבר האחרון בו.


 

שימו לב:

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

יתרונות מערכים על פני משתנים רבים

עריכה

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


 

עכשיו תורכם:

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


פתרון

אפשר לכתוב זאת כך:

#include <stdio.h>

int main()
{
  int numbers[10];
  int i;

  for(i = 0; i < 10; i++)
  {
    int temp;
  
    scanf("%d",&temp);
    
    numbers[i] = temp;
  }

  return 0;
}

או, לאחר קימפול קוד, הוא ייראה כך:

#include <stdio.h>

int main()
{
  int numbers[10];
  int i;

  for(i = 0; i < 10; i++)
    scanf("%d",&numbers[i]);

  return 0;
}


 

עכשיו תורכם:

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


פתרון
#include <stdio.h>

int main()
{
  int numbers[10];
  int i;

  for(i = 0; i < 10; i++)
    scanf("%d",&numbers[i]);

  for(i = 9; i >= 0; i--)
    printf("%d\n",numbers[i]);

  return 0;
}


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

int main()
{
  int num0, num1, num2, num3, num4, num5, num6, num7, num8, num9;

  scanf("%d",&num0);
  scanf("%d",&num1);
  scanf("%d",&num2);
  scanf("%d",&num3);
  scanf("%d",&num4);
  scanf("%d",&num5);
  scanf("%d",&num6);
  scanf("%d",&num7);
  scanf("%d",&num8);
  scanf("%d",&num9);

  printf("%d\n",num9);
  printf("%d\n",num8);
  printf("%d\n",num7);
  printf("%d\n",num6);
  printf("%d\n",num5);
  printf("%d\n",num4);
  printf("%d\n",num3);
  printf("%d\n",num2);
  printf("%d\n",num1);
  printf("%d\n",num0);

  return 0;
}


נוכל לראות יתרונות בולטים לגרסה המשתמשת במערכים:

  • במקום להשתמש בעשרה משתנים שונים די להגדיר משתנה יחיד, תחת השם numbers.
    • אם התוכנית הייתה צריכה לעבוד עם מספר גדול מספיק של של משתנים, נניח 100, לא היה מעשי להגדיר משתנה לכל אחד מהם.
    • לעתים לא יודעים מראש את מספר המשתנים (כמו, לדוגמה, המקרה שבו גם מספר זה מתקבל בקלט). הפועל היוצא הוא שלא היינו יודעים כמה משתנים להגדיר. עם זאת, כשנגיע לניהול זיכרון דינאמי, נראה שאפשר להקצות מערכים בגדלים הנקבעים בזמן הריצה (כמו מקלט, לדוגמה).
  • הקוד המשתמש בלולאות קצר יותר. כך לדוגמה, הדפסת האיברים על ידי לולאה (2 שורות), קצרה יותר מהדפסתם בצורה פרטנית (10 שורות).

אתחול מערך

עריכה

לעתים, כאשר מייצרים מערך, יש לתת ערך התחלתי לאיבריו. לדוגמה, נניח שמייצרים מערך של שלמים בגודל 3, ורוצים להכניס לו את האיברים 12, 22, ו-33.

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

int nums[3];

nums[0] = 12;
nums[1] = 22;
nums[2] = 33;

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

int nums[3] = {12, 22, 33};

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

char nums[] ={"Goodbye"};    ==>   nums[7]

int nums[] ={3, 5, 65, 189, 54};  ==> nums[5]

מחרוזות - מערכים של תווים

עריכה

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

זהירות בטיפול במערכים

עריכה

שימוש שגוי במערכים עלול לגרום לתוצאות קשות. (כולל מחיקת נתונים/מידע)

גלישה מגבולות המערך

עריכה

נתבונן בקטע הקוד הבא:

/* An array of 3 places. */
int nums[3];

/* This is an error: the array does not have 11 places. */
nums[10] = 12;

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

השלכות הגלישה ממערך

עריכה

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

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

מערכים בגודל לא-ידוע מראש

עריכה

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

#include <stdio.h>
#include <stdlib.h> // for malloc() and free()

/* Queries the user for how many 
	numbers he wants reversed. */
int find_how_many_numbers()
{
  int num_numbers;

  printf("Please enter how many numbers to reverse: ");
  scanf("%d", &num_numbers);
  
  return num_numbers;
}

int main()
{
  int num_numbers = find_how_many_numbers();
  int *numbers = (int*)malloc(sizeof(int) * num_numbers);
  int i;

  for(i = 0; i < num_numbers; i++)
    scanf("%d",&numbers[i]);

  for(i = num_numbers - 1; i >= 0; i--)
    printf("%d\n",numbers[i]);

  free(numbers);

  return 0;
}

בקוד זה, חשוב לשים לב לשורות הבאות:

  int num_numbers = find_how_many_numbers();
  int *numbers = (int*)malloc(sizeof(int) * num_numbers);

השורה הראשונה מאתחלת את num_numbers לערך המוחזר מ-find_how_many_numbers (שהיא פונקציה שמבקשת מהמשתמש להקליד את מספר המספרים שברצונו להפוך, ומחזירה את המספר שהקליד). השורה השניה מייצרת מערך בגודל מספר זה באמצעות הקצאה דינמית.

מערכים רב ממדיים

עריכה

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

להלן דוגמה למערך דו-מימדי:

int matrix_2d[10][3];

זהו מערך של 10 מערכים, כל אחד בעלי 3 איברים. כדי לגשת לאיבר השני (שמיקומו 1 - נזכר כי במערך האינדקס הראשון הוא 0) של המערך השלישי (שמיקומו 2), נרשום

/* Access to the second element of the third array. */
matrix_2d[2][1] = 2;

גם כאן, כמובן, יש להיזהר מגלישה:

/* Error! out of range */
matrix_2d[2][10] = 2;

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

/* A 6 dimensional array. */
int matrix_6d[5][8][2][9][3][1];

משתני שורת הפקודה

עריכה
 

שימו לב:

מומלץ לעבור קודם על נושא המצביעים

ורק אז לחזור לנושא זה.

משתני שורת פקודה, הם משתנים שהמשתמש יכול להעביר יחד עם הרצת התוכנה. לדוגמא: cd desktop. כאן אנו מפעילים את התוכנה CD עם משתנה שהוא התיקיה אליה אנחנו רוצים לעבור (CD = Change Directory). ב - C, ניתן לקבל את המשתנים המועברים משורת הפקודה אל התכנית עצמה על - ידי מערך מסוים שאותו אנו צריכים להצהיר כארגומנט של הפונקציה main. עד כה, main שהכרנו לא קיבלה שום ארגומנטים והיא נראתה כך:

int main()

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

int main(int argc, char* argv[])

דוגמא לתוכנית שמשתמשת במשתנים של שורת הפקודה ומדפיסה אותם בלולאת FOR.

#include <stdio.h>

int main(int argc, char* argv[])
{
	int i;

	for(i=0;i<argc;i++)
		printf("%s",argv[i]);
   return 0;
}
הפרק הקודם:
פונקציות
מערכים
תרגילים
הפרק הבא:
מחרוזות