|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
קורס:"שפת C"
מתוך הספר: C - מדריך מקצועי
הגדרת מערךמערך משמש לאחסון מספר משתנים מאותו טיפוס בסדרה רצופה בזיכרון. כל איבר במערך הוא משתנה ללא שם - ההתייחסות אליו היא באמצעות האינדקס שלו, כלומר מיקומו ביחס לתחילת המערך. ניתן לקרוא את הערך של כל איבר במערך, לשנות את ערכו, להדפיס אותו, לקלוט לתוכו ערך מהקלט - כלומר ניתן לבצע עליו על פעולות כאילו היה משתנה פשוט. איברי המערך יכולים להיות מטיפוס פשוט, כגון: שלם, ממשי, תו. כמו כן הם יכולים להיות מטיפוס מורכב יותר (מצביע, רשומה, מערך) כפי שנראה בהמשך. בנוסף, טיפוס האיבר יכול להיות מוגדר משתמש (ע"י typedef) - לדוגמא, טיפוס המחרוזת String. תחביר: הגדרת מערך דומה להגדרת משתנה, בתוספת סוגריים מרובעות וציון מספר האיברים, כלומר גודל המערך, בתוך הסוגריים: <טיפוס-איבר> <שם-המערך> [<גודל-המערך>]; לדוגמא, הגדרת מערך שלמים בגודל 5: int integers[5]; במערך מגודל n, הערכים מאוחסנים במערך החל מאינדקס 0 ועד ל- n-1. לכן המערך integers ייראה כך:
הגישה לאיבר באינדקס i במערך מבוצעת ע"י שם המערך, בתוספת האינדקס בסוגריים: <שם-המערך>[i] לדוגמא, נציב לאיבר הראשון (אינדקס 0) ערך 23, ולאיבר האחרון (אינדקס 4) ערך 11: integers[0] = 23; integers[4] = 11; באופן דומה, נדפיס את האיבר שבאינדקס 3 (האיבר הרביעי) כך: printf("integers[3] = %d", integers[3]); נקדם הערך שבתא האחרון ב- 1: integers[4]++; /* now integers[4] is 12 */ תכנית למניית מספר הספרות בקלטאם בתכנית מסויימת נרצה למנות את מספר המופעים של כל ספרה בקלט, נצטרך לשם כך 10 משתנים. יהיה נוח ופשוט יותר להשתמש במערך. נגדיר מערך שלמים בן 10 מקומות int digits[10]; ובכל פעם שנזהה ספרה i בקלט (0<= i <=9) נקדם את האיבר המתאים במערך ע"י: digits[i]++; קוד התכנית: /* file: digits.c */ #include <stdio.h> void main () { int c,i; int digits[10]; /* initialize array */ for (i=0; i<10; i++) digits[i]=0; while ((c=getchar())!=EOF) { if (c >= '0' && c <= '9') digits[c-'0']++; } printf("digits="); for (i=0; i<10; ++i) printf("%d",digits[i]); } התכנית הורצה עם הקלט הבא: The 2nd wolrd war started in 1939
והפלט: digits=0111000002
הסבר: ההגדרה int digits[10] מכריזה על digits כמערך שלמים בן 10 מקומות: int digits[10];
זו הסיבה שלולאת ה- for המאפסת את המערך מתחילה מ- 0 ומסתיימת מקום אחד לפני הערך המציין את גודל המערך: for (i=0; i<10; i++) digits[i]=0; גוף הלולאה יתבצע רק עבור ערכי i בתחום 0..9. בהגעה ל- 10 יוצאים מהלולאה ללא ביצועה. בשלב הבא נקרא תו מהקלט, וכל עוד הוא אינו סוף קובץ while ((c=getchar())!=EOF) בודקים אם הוא ספרה ע"י הביטוי if (c>='0' && c<='9') הבדיקה מסתמכת על כך שבטבלת התווים במחשב מיוצגות הספרות החל ממקום מסוים ('0') ברציפות לאורך 10 מקומות (עד '9'). ראה/י טבלת תווי ASCII בנספח. לחילופין, ניתן להחליף ביטוי זה בביטוי: if (isdigit(c)) תוך הוספת הכללה לקובץ ctype.h. בביטוי digits[c-'0']++; החיסור c-'0' נותן את אינדקס הספרה המתאימה ומקדם את הערך במערך. לדוגמא, עבור הערך c='3' תוצאת ההפרש digits['3'-'0']++ היא digits[3]++ שמשמעותה קידום הערך שבתא 3 במערך. אתחול מערךניתן לאתחל את ערכי המערך ב- 2 אופנים: 1. אתחול בהגדרה. לדוגמא, מערך ממשיים מסוג double: double a[4] = {45.33, 12.21, 98.89, 2.5E10}; כאן מבוצעת ההצבה לאיברי המערך בזמן הגדרתו. במקרה זה ניתן גם להגדיר את המערך ללא ציון המימד, לדוגמא: double a[] = {45.33, 12.21, 98.89, 2.5E9}; המהדר "מבין" עפ"י רשימת איברי האתחול מהו גודל המערך המבוקש.
2. אתחול ע"י הצבה מפורשת: int main() { double a[4]; a[0] = 45.33; a[1] = 12.21; a[2] = 98.89; a[3] = 2.5E9; } כאן אנחנו מציבים ערכים במפורש לתאי המערך a :
בדרך כלל פעולות על מערכים מבוצעות בלולאות. לדוגמא, התכנית הבאה מגדירה מערך גלובלי: #include <stdio.h> #define ARRAY_SIZE 7 int array[ARRAY_SIZE]; /* define a global array */ המערך מוגדר ע"י קבוע קדם-מעבד. ניתן כעת לכתוב פונקציה לאיתחול המערך מהקלט: void input() { int i; for (i=0; i<ARRAY_SIZE; i++) { scanf("%d",&array[i]); } } הסבר: הפונקציה עוברת בלולאה על איברי המערך הגלובלי וקוראת ערכים לתוכו ע"י scanf("%d",&array[i]);
array[i] הוא האיבר ה- i במערך, והקידומת & מציינת את כתובתו עבור scanf. הרצה לדוגמא של הפונקציה הנ"ל עם הקלט: 2 7 -1 0 19 666 -29 תרשים המערך לאחר ביצוע הפונקציה:
כללים
פעולות על מערכיםמציאת ערך מקסימוםהפונקציה find_max המובאת בעמ' 170 מוצאת ומחזירה את המספר הגדול ביותר במערך. בדיקת קיום של ערך מסוים במערךהפונקציה exist המובאת בעמ' 170 בודקת קיום של ערך הניתן לה כפרמטר במערך. מיון איברי המערך - שיטת מיון "בועות"כדי למיין את איברי המערך נגדיר פונקציה בשם sort שתבצע את המיון ע"י שיטת מיון בועות (Bubble sort). בשיטה זו משווים בין כל 2 איברים סמוכים ואם יש צורך מחליפים בין ערכיהם. לדוגמא, נניח שנתון מערך של 7 שלמים עם הערכים הבאים:
אנו מעוניינים למיין את המערך בסדר עולה. נבצע את המיון בלולאה עם מספר חזרות. חזרה ראשונה:
כפי שניתן לראות, בסוף החזרה הראשונה נמצא האיבר המקסימלי (100) בסוף המערך. בחזרה השנייה יש לבצע כנ"ל על 5 האיברים הראשונים בלבד:
בסוף החזרה השנייה נמצא האיבר השני בגדלו (21) במקומו. באופן זהה ממשיכים בביצוע חזרות עד למיון המלא של המערך. שמו של אלגוריתם מיון זה בא לו מתכונה זו: בכל שלב מוצף האיבר המקסימלי לסוף המערך בדומה לבועה. שאלה: כמה חזרות נדרשות למיון מערך בגודל n ? תשובה: מכיוון שבכל חזרה מוצף איבר אחד למקומו, אזי במקרה הגרוע ביותר - האיבר המינימלי נמצא בסוף המערך - נצטרך n-1 חזרות להזזתו מהסוף להתחלה. נראה כעת מימוש של מיון בועות ע"י הפונקציה sort: /* sort the array in ascending order */ void sort() { int i,j; int temp; for(i=0; i<ARRAY_SIZE - 1; i++) { for(j=0; j<ARRAY_SIZE - 1 - i; j++) { if(array[j] > array[j+1]) { temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } } הפונקציה מבצעת מעבר בלולאה כפולה על איברי המערך:
באופן כללי, אחרי החזרה ה- i -ית האיבר ה- i בגדלו נמצא במקומו. לכן, בלולאה הפנימית אין צורך לעבור ולהשוות את כל האלמנטים - די לעבור על מספר האלמנטים פחות i. התכנית במלואה מובאת בעמ' 174-176 וכוללת את הפונקציות:
העברת מערך כפרמטר לפונקציה ושיקולי מודולריותבתכנית האחרונה הגדרנו את המערך כמשתנה גלובלי כדי לאפשר שיתופו בין מספר פונקציות. לו היינו מגדירים אותו כמקומי בפונקציה main למשל, הוא לא היה מוכר בפונקציות האחרות. כאשר מועבר נתון פשוט כפרמטר לפונקציה נוצר שיכפול שלו בפונקציה הנקראת. שינוי ערכו בפונקציה זו לא משפיע על ערכו אצל הפונקציה הקוראת. לעומת זאת מערכים המועברים כפרמטרים לא משוכפלים בפונקציה הנקראת. הפונקציה מתייחסת לאותו מקום בזיכרון בו הוגדר המערך בפונקציה הקוראת. לדוגמא, הפונקציה הראשית בתכנית הבאה מעבירה את המערך כפרמטר לפונקציה המקדמת את ערכי כל איברי המערך ב- 1 : #include <stdio.h> void increment_array(int arr[], int size) { int i; for(i=0; i<size; i++) arr[i]++; } void main() { enum { ARRAY_SIZE=5 }; int array[ARRAY_SIZE] = {6,9,2,5,18}; int i; increment_array(array, ARRAY_SIZE); for( i=0; i<ARRAY_SIZE; i++) printf("%d ", array[i]); } פלט התכנית: 7 10 3 6 19
הסבר: הפונקציה increment_array() מבצעת קידום של כל איברי מערך השלמים ב- 1. לצורך כך, היא מקבלת את המערך ואת גדלו כפרמטרים: void increment_array(int arr[], int size) { int i; for(i=0; i<size; i++) arr[i]++; } הפונקציה increment_array() פועלת על המשתנה הפורמלי arr, המצביע למערך האקטואלי שמועבר בפונקציה הקוראת main(). הפונקציה עוברת על כל איבריו ומקדמת אותם ב- 1. הפונקציה הקוראת, main(), מגדירה את המערך array כמקומי. לצורך ציון גדלו, מוגדר הקבוע ARRAY_SIZE כ- enum : enum { ARRAY_SIZE=5 }; int array[ARRAY_SIZE] = {6,9,2,5,18};
בהמשך, מבצעים קריאה לפונקציה increment_array תוך העברת המערך וגדלו כפרמטרים: increment_array(array, ARRAY_SIZE); לבסוף מודפסים איברי המערך: for( i=0; i<ARRAY_SIZE; i++) printf("%d ", array[i]); פונקציות מודולריותהעברת המערך כפרמטר לפונקציה היא שיטת תכנות מודולרית יותר ועדיפה על פני הגדרתו כמשתנה גלובלי ממספר סיבות:
נחזור לתכנית המטפלת במערכים: נגדיר את המערך כמקומי ב- main, שתעביר אותו לשאר הפונקציות כפרמטר - /* file: array2.c */ #include <stdio.h>
/* initialize the array by input from the user */ void input(int arr[], int size) { int i; printf("\nEnter %d numbers:", size); for (i=0; i<size; i++) { scanf("%d",&arr[i]); } } /* output array elements */ void output(int arr[], int size) { int i; printf("\nArray elements:"); for (i=0; i<size; i++) { printf("%d\t",arr[i]); } } ...
int main() { enum {ARRAY_SIZE=7}; int num; int array[ARRAY_SIZE]; /* initialize the array */ input(array, ARRAY_SIZE); /* find max number in the array */ printf("\nThe maximum number is %d", find_max(array, ARRAY_SIZE)); /* is a given number exist in the array */ printf("\nEnter a number to find in the array:"); scanf("%d",&num); if(exist(array, ARRAY_SIZE, num)) printf("\nThe number %d exist in the array", num); else printf("\nThe number does not exist in the array"); /* sort the array */ printf("\nSorting the array..."); sort(array, ARRAY_SIZE); output(array, ARRAY_SIZE); return 0; } יש לשים לב לנקודות החשובות בגירסה זו של התכנית:
מערכים רב-ממדייםניתן להגדיר מערך ממספר ממדים. לדוגמא, הגדרת מערך דו-ממדי בגודל 5 על 10 : int matrix[5][10]; המימד הראשון המצוין, 5, הוא מימד השורות והמימד השני, 10, הוא מימד העמודות: לדוגמא, תכנית המדפיסה מטריצה שבאלכסונה הראשי '1' -ים ובכל שאר איבריה אפסים: /* file: matrix.c */ #include <stdio.h> #define LINES_NO 5 #define COLUMNS_NO 10 void main() { char matrix[LINES_NO][COLUMNS_NO]; int i,j; for(i=0; i<LINES_NO; i++) for(j=0; j<COLUMNS_NO; j++) if(i==j) matrix[i][j] = '1'; else matrix[i][j] = '0'; for(i=0; i<LINES_NO; i++) { for(j=0; j<COLUMNS_NO; j++) putchar(matrix[i][j]); putchar('\n'); } } פלט התכנית: 1000000000 0100000000 0010000000 0001000000 0000100000 אתחול מערך רב-ממדיכמו מערך חד-ממדי, ניתן לאתחל מערך רב ממדי ע"י לולאה כנ"ל או ע"י אתחול בהגדרה. דוגמאות: 1. מטריצת ממשיים בגודל 2x3 : float matrix1[][3] = { {1.0, 1.5, 2.0}, {3.0,3.5,4.0}};
ציון המימד הראשון אופציונלי, את השני חובה לציין. 2. מערך תווים תלת-ממדי בגודל 2x2x3 : char array3D[][2][3] = { {{'1', '2', '3'}, {'a', 'b', 'c'}}, {{'4','5','6'},{'e','f','g'}} };
המימד הראשון הוא אופציונלי, את השני והשלישי חובה לציין. ככלל, אתחול בזמן הגדרה מחייב לציין את n-1 הממדים האחרונים של מערך ממימד n. העברת מערך רב-ממדי כפרמטר לפונקציהאם מערך דו-ממדי מוגדר כך: float array[3][4]; פונקציה המקבלת אותו כפרמטר תוגדר כך: void func(float array[][4]); או כך: void func(float array[3][4]);
ציון המימד הראשון הוא אופציונלי, אולם את המימד השני של המערך חובה לציין כדי שהמהדר ידע כיצד לתרגם בפונקציה func את האינדקסים. לדוגמא: void func(float array[][4]) { array[1][3] = 4.56f; }
מערך רב-ממדי מיוצג בשפת C ע"י פרישתו למערך חד-ממדי:
המהדר חייב לדעת שמימד העמודות של המערך הוא 4 כדי לדעת את מיקום האיבר array[1][3]. כלומר, מיקום האיבר array[1][3] בפונקציה שמגדירה את המימד השני של המערך כ- 4 מחושב ע"י array[1*4 + 3]. בתכנית הדוגמא המובאת בעמ' 181 כוללת שני מערכים דו-מימדיים (מטריצות) של תווים:
עיין/י בקוד התכנית. מיון שורות ועמודות מערך רב-ממדילשורות של מטריצה ניתן להתייחס כאל מערך רגיל מכיוון שהמטריצה נפרשת בזיכרון למערך חד ממדי שורה-שורה. לדוגמא, נניח שמוגדרת המטריצה הבאה: #define COLUMNS_NO 3 char matrix[4][COLUMNS_NO] = {{'f', 'a', 'h'} ,{'w', 'c', 'k'}, {'g', 'o', 'l'}, {'d', 'j', 'i'}}; פונקציה למיון שורה אחת מהמטריצה ניתן לכתוב כך: void sort_line(char line[], int columns_no) { int i,j; char temp; for(i=0; i<columns_no - 1; i++) for(j=0; j<columns_no-1-i; j++) if(line[j] > line[j+1]) { temp = line[j]; line[j] = line[j+1]; line[j+1] = temp; } } מתוך התכנית המשתמשת הפונקציה תיקרא כך, למשל: sort_line(matrix[2], COLUMNS_NO); קריאה זו תגרום למיון שורה 2 במטריצה. תרגיל: מה יבוצע אם במקום הפרמטר COLUMNS_NO יועבר לפונקציה sort_line הערך 6: sort_line(matrix[2], 6); מיון עמודותבפונקצית המיון של העמודות, הואיל והם לא נפרשים באופן רציף בזיכרון - יש לקבל כפרמטר את המטריצה, את מספר השורות הכולל ואת אינדקס העמודה למיון:
פונקצית המיון של עמודה במטריצה: void sort_column(char matrix[][COLUMNS_NO], int lines_no, int col) { int i,j; char temp; for(i=0; i<lines_no - 1; i++) { for(j=0; j<lines_no-1-i; j++) { if(matrix[j][col] > matrix[j+1][col]) { temp = matrix[j][col]; matrix[j][col] = matrix[j+1][col]; matrix[j+1][col] = temp; } } } } והפונקציה תיקרא כך, למשל: sort_column(matrix, 4, 1); הוראה זו תגרום למיון עמודה 1 במטריצה. יש לשים לב שגודל העמודות חייב להיות ידוע לפונקציה, בכדי שהיא תתורגם נכון, לכן מספר העמודות מוגדר כקבוע ומצוין כמימד שני. לעומת זאת, מספר השורות מועבר כפרמטר. תכנית המיון הכוללתקוד תכנית המיון הכוללת מובאת בעמ' 185-187. סיכום
תרגילי סיכוםבצע/י את תרגילי הסיכום שבעמ' 188.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||