מבוא לשפת C תירגול 10: מצביעים, מערכים ומחרוזות
שבוע שעבר... מצביעים Call by reference 2
תוכנייה קשר מצביע-מערך )אריתמטיקה של מצביעים( העברת מערכים לפונקציה מחרוזות דוגמה 3
קשר מצביע-מערך 4
תזכורת: תמונת זכרון מערך int a[10] = {5, 8, 10, 0, 0, 0, 0, 0, 0, 0; דוגמה: 2000 2004 2008 2036 5 8 10 0 0 0 0 0 0 0 a[0] a[1] a[2] a[9] מה הערך של כל ביטוי? &a[0] *&a[2] &a[7] a sizeof(a) 5
שם המערך כמעט מצביע שם של מערך ללא ציון המקום, מחזיר את כתובת האיבר הראשון במערך..* [0]a& שקול ל- a על כתובת ניתן להפעיל אופרטור *&a[0] *a שקול ל- מסקנה: שם של מערך מתנהג שקול ל- כמעט אי אפשר לשנות את כתובתו של מערך בזיכרון a[0] כמו מצביע. 6
פעולות אריתמטיות של מצביעים,++,- פעולות חיבור וחיסור )+, מצביעים. מה יהיה הערך של p בסוף קטע הקוד? --( מוגדרות על 3188 52 int num int num = 52; int *p; p = # p = p + 1; הפעולות מוגדרות לפי גודל הטיפוס המוצבע. זה כמו להצביע למשתנה הבא בזיכרון: p = p + k; כתובת ישנה = כתובת חדשה + k * sizeof(*p) 7
מסקנה 1 להוסיף k לכתובת, זה כמו להתקדם k משתנים מאותו טיפוס קדימה: &a[k] שקול ל- a+k a[k] שקול ל- *&a[k] שקול ל- *(a+k) 8
מסקנה 2 אם מצביע p מצביע לכתובת של תחילת מערך a, ניתן לגשת לאיבר i גם ע"י :p]i[ int a[3]; int *p; p = a; &a[k] שקול ל- a+k - שקול ל p+k שקול ל- &p[k] a[k] p[k] שקול ל- *(p+k) שקול ל- *(a+k) שקול ל - 9
דוגמאות מה ההבדל בין שני קטעי הקוד? int a[3] = {5,8,10; int *p; p = a; /* or p = &a[0]*/ p = p + 1; int a[3] = {5,8,10; int *p; a = a + 1; p = a; /* or p = &a[0]*/ מה ידפיס קטע הקוד הבא עבור המשתנים מלמעלה? printf("%d\n", sizeof(a) ); printf("%d\n", sizeof(p) ); printf("%d\n", sizeof(*p) ); 10
העברת מערכים לפונקציה 11
העברת מערך חד-ממדי לפונקציה מעבירים לפונקציה את הכתובת של האיבר הראשון במערך. לדוגמה: void f1(int *a); דוגמה לקריאה: int a[10]; f1(a); f1(&a[0]); void f1(int a[]); void f1(int a[25]); הקומפיילר מתעלם מכל ערך שכותבים בסוגריים! כדי שלא נחרוג מגבולות המערך בתוך הפונקציה צריכים להעביר את גודלו של המערך 12
העברת גודל המערך #define ניתן להעביר את הגודל כפרמטר נוסף void print_vector(int *arr, int n) { int i; for (i=0; i < n; i++) printf("%d ",arr[i]); ניתן להגדיר את הגודל כקבוע ע"י #define N 20 שיטה 1: שיטה 2: void print_vector(int *arr) { int i; for (i=0; i < N; i++) printf("%d ",arr[i]); 13
השוואה בין השיטות שיטה 2 שיטה 1 גודל המערך הועבר כפרמטר גודל המערך מוגדר כקבוע לא ניתן להגדיר מערך עזר סטטי מאותו גודל בתוך הפונקציה עובדת עם מערכים מכל הגדלים ניתן להגדיר מערך עזר מאותו גודל בתוך הפונקציה עובדת רק עם מערכים מאותו גודל כמו הקבוע 14
מציאת מקסימום כתבו פונקציה המקבלת מערך וגודלו כפרמטרים, ומחזירה את ערכו של האיבר המקסימלי במערך. int find_max(int a[], int n) { int i; int max = a[0]; for (i=1; i < n; i++) { if (a[i] > max) { max = a[i]; למה אי אפשר לאתחל את max ב- 0? return max; 15
חידה בהינתן הפונקציה מהשקף הקודם, מה תדפיס התוכנית הבאה? int main(void) { int data[] = {6, 9, 7, 1, 3; int max1; int max2; max1 = find_max(data, 5); max2 = find_max(data+2, 3); printf("%d\n", max1); printf("%d\n", max2); return 0; 16
מערך דו-ממדי כפרמטר לפונקציה )1( חישוב הכתובת )למה לא צריך )?sizeof גישה לאיבר במערך חד מימדי a+5 שקול ל: &a[5] a[0][0] a[1][0] a[2][0] 2000 2004 2008 2020 2036 5 8 10 0 0 0 0 0 0 0 a[0] a[1] a[2] a[5] a[9] גישה לאיבר במערך דו מימדי: 2000 2004 2008 2020 2036 5 8 10 0 0 0 0 0 0 0 2040 2044 2048 2060 2076 0 9 11 0 2 3 0 48 0 0 2080 2084 2088 2100 2116 5 8 10 6 9 7 24 1 56 33 a[0][9] a[1][9] a[2][9] 17
מערך דו-ממדי כפרמטר לפונקציה )2( a[0][0] a[1][0] a[2][0] גישה לאיבר במערך דו מימדי: 2000 2004 2008 2020 2036 5 8 10 0 0 0 0 0 0 0 2040 2044 2048 2060 2076 0 9 11 0 2 3 0 48 0 0 2080 2084 2088 2100 2116 5 8 10 6 9 7 24 1 56 33 a[0][9] a[1][9] a[2][9] a+1*num_columns+2 שקול ל: [2][1]a& מסקנה: המהדר חייב דו מימדי! לדעת את מספר העמודות בגישה למערך 18
מערך דו-ממדי כפרמטר לפונקציה )3( לכן, העברת מערך דו מימדי לפונקציה מוגדרת באופן הבא : חייבים לציין את אורך השורה void f(int a[][4], int rows) מותר גם לציין את מספר השורות, הקומפיילר יתעלם ממנו. אך פרמטר rows מועבר כדי שבתוך הפונקציה לא נחרוג מגבולות המטריצה. פונקציה זו יודעת ש- a היא מטריצה, הפונקציה לגשת לאיבר i,j בעזרת: 0 i rows-1, 0 j 3 הקריאה לפונקציה זו תראה כך: לכן מותר בתוך a[i][j] int m[9][4]; f(m, 9); 19
מערך דו-ממדי כפרמטר לפונקציה )3( אם רוצים לכתוב פונקציה שמניחה אורך שורה קבוע, ניתן להעביר את המטריצה בצורה הבאה: חייבים לציין את אורך השורה void f(int a[][4], int rows) מותר גם לציין את מספר השורות, הקומפיילר יתעלם ממנו. אך פרמטר rows מועבר כדי שבתוך הפונקציה לא נחרוג מגבולות המטריצה. פונקציה זו יודעת ש- a היא מטריצה, הפונקציה לגשת לאיבר i,j בעזרת: 0 i rows-1, 0 j 3 הקריאה לפונקציה זו תראה כך: לכן מותר בתוך a[i][j] int m[9][4]; f(m, 9); 20
מערך דו-ממדי כפרמטר לפונקציה )4( אבל אם רוצים לכתוב פונקציה שעובדת עבור מטריצה בגודל כלשהו חייבים להעביר לפונקציה מצביע לאיבר הראשון, גובה ורוחב של המטריצה: void f(int *p, int rows, int cols) כעת, מבחינת הפונקציה, p הוא לא מערך דו-מימדי, אלא מצביע. לכן בתוך הפונקציה לא ניתן להשתמש ב-[ p[i][j, אלא יש לגשת לאיבר i,j בעזרת: j) *(p + i*cols + 0 i rows-1, 0 j cols-1 int m[9][4]; הקריאה לפונקציה זו תראה כך: ;(4 f(&m[0][0],,9 21
דוגמה: משחק החיים 22
משחק החיים את "משחק החיים" המציא ג'ון קונווי Conway( )John בשנת 1970. ידוע בשם.Conway's Game of Life הוא מדגים איך ניתן לדמות סביבה ביולוגית בעזרת תוכנת מחשב. למעשה, זה היה מחקר במדעי המחשב שהוביל לתובנות רבות על אוטומטים תאיים )תחום במדעי המחשב(, אבל בהמשך נעשה שימוש ברעיונות דומים לחקור התנהגות נמלים, מעוף ציפורים בלהקות, ועוד. 23
משחק החיים: הכללים בצלחת פטרי חיים להם חיידקים קטנים וידידותיים. כל חיידק תופס בדיוק ריבוע של מילימטר על מילימטר. כלומר, אם נניח נייר מילימטרי מתחת לצלחת, כל ריבוע או שיכיל חיידק, או שיהיה ריק. 24
משחק החיים: הכללים לכל ריבוע יש בין 0 ל- 8 שכנים: מימין, משמאל, למעלה, למטה, ובארבעת האלכסונים. לחיידק שבדוגמא יש 3 שכנים. 1 1 1 1 0 2 2 2 1 1 2 3 3 3 2 2 1 0 2 1 4 2 2 1 1 2 1 2 1 1 25
משחק החיים: הכללים בכל שניה נולדים חיידקים חדשים, ומתים חלק מהישנים. חיידק מת )משעמום( אם יש לו פחות משני שכנים. חיידק מת )מצפיפות( אם יש לו יותר מ- 3 שכנים. חיידק נולד בתא ריק אם יש לתא בדיוק 3 שכנים חיים. 1 1 1 1 0 2 2 2 1 1 2 3 3 3 2 2 1 0 2 1 4 2 2 1 1 2 1 2 1 1 26
משחק החיים: האתגר האתגר שמציב המשחק: איזה פיזור התחלתי של חיידקים יוביל למערכת שתשרוד לאורך זמן?... מערכת שתשרוד ללא שום שינוי... מערכת שתשרוד תוך שינוי מתמיד?... מערכת שתשרוד, ותגדל ללא הרף? )קיפאון(? Wikimedia Commons מקור : 27
דוגמא פשוטה הנה מערכת שתשרוד לנצח, ללא שום שינוי: לכל חיידק יש בדיוק שני שכנים. )לא צפוף, אין אף תא ריק עם 3 שכנים חיים. לא משעמם(. 28
מימוש המשחק איך נממש את משחק החיים בשפת C? את הלוח נייצג בעזרת מערך דו-ממדי של.int תא "חי" יכיל ערך 1, תא ריק יכיל ערך 0. פונקציה אחת תאתחל את המערך לצורה ההתחלתית שברצוננו לבדוק. בנוסף, נכתוב פונקציות: להדפסת מצב הלוח, לעידכון הלוח )מעבר של שנייה נריץ את שתי הפונקציות בלולאה... מי חי, מי מת ומי נולד?( 29
הגדרת הלוח ופונקצית האתחול #include <stdio.h> #define WIDTH 50 #define HEIGHT 20 #define NR_OF_STEPS 100 void init(int board[][width], int rows) { int x, y; for (y = 0; y < rows; y++) for (x = 0; x < WIDTH; x++) board[y][x] = 0; /* Scatter some live cells: */ board[10][25] = 1; board[10][26] = 1; board[10][27] = 1; board[11][25] = 1; board[12][26] = 1; נסו לבחור איתחולים אחרים ולראות מה התוצאה המתקבלת... אפשר גם לאתחל ע"י scanf ולקרוא אתחולים מתוך קבצים. 30
פונקציה להדפסת הלוח void print(int board[][width], int rows) { int x, y, *p_board=&board[0][0]; for (y = 0; y < rows; y++) { for (x = 0; x < WIDTH; x++) { if (*(p_board + y*width + x) == 1) printf("x"); else printf(" "); printf("\n"); printf("press any key to continue:\n"); getchar(); מצביע לתחילת הזיכרון המוקצה עבור המערך. מובטח שמערך תופס קטע רצוף בזיכרון. למעשה ניתן גם לכתוב p_board[y*width+x] לאחר ההדפסה אנו ממתינים לרגע, כדי לתת למשתמש אפשרות לבחון את התוצאה. 31
פונקציה לספירת מספר השכנים int count_neighbors(int board[][width], int rows, int y, int x) { int i, j; int result = 0; מה משמעותו של כל תנאי? for (i = -1; i <= 1; i++) if ((y+i >= 0) && (y+i < rows)) for (j = -1; j <= 1; j++) if ((x+j >= 0) && (x+j < WIDTH)) if ((i!= 0) (j!= 0)) result += board[y+i][x+j]; return result; לפני בדיקת כל שכן אנו בודקים שלושה תנאים )צבועים באדום(. 32
הצעד הבא... void step(int board[][width], int rows) { int x, y; int neighbors[height][width]; מדוע ניתן להגדיר מערך עזר פה? for (y = 0; y < rows; y++) for (x = 0; x < WIDTH; x++) neighbors[y][x] = count_neighbors(board, rows, y, x); for (y = 0; y < rows; y++) for (x = 0; x < WIDTH; x++) if (board[y][x] == 1) { /* Currently alive */ if (neighbors[y][x] < 2) board[y][x] = 0; /* Death by boredom */ else if (neighbors[y][x] > 3) board[y][x] = 0; /* Death by overcrowding */ else { /* Currently empty */ if (neighbors[y][x] == 3) board[y][x] = 1; /* Mazal Tov! */ החישוב מתבצע בשני שלבים נפרדים. מדוע? 33
ריצת התוכנית int main(void) { int i; int board[height][width]; init(board, HEIGHT); #define ע"י NR_OF_STEPSמוגדר for(i = 0; i < NR_OF_STEPS; i++) { print(board, HEIGHT); step(board, HEIGHT); return 0; 34
מחרוזות 35
מהי מחרוזת? מחרוזת היא רצף של תווים )ערכים מטיפוס )char שמסתיים ב-' 0 \'. התו המיוחד '0\' מסמן את סוף המחרוזת. זהו תו שערכו ה- ASCII )מספרי( הוא אפס; שונה מהתו '0', שערכו המספרי הוא 48. למעשה, מחרוזת היא מערך של char שמסתיים ב-' 0 \'. דוגמא של מחרוזת: char s[ ] = {'H', 'e', 'l', 'l', 'o', '!', '\0'; אפשר גם להגדיר את אותו המערך בצורה יותר נוחה: char s[ ] = "Hello!"; מחרוזות בשפת C נקראות,null-terminated strings בשל השימוש בערך אפס כדי לסמן את סוף המחרוזת. 36
תמיכה בשפת C למחרוזות char str[10]; scanf("%s", str);.scanf.printf char str[10] = "hello"; printf("%s", str); "some text" ב ע"י s% %s קליטת מחרוזת: 9 תווים מקסימום, הדפסת מחרוזת: ע"י מחרוזת קבועות: שימוש טיפול במחרוזות: ב ע"י ספריית במרכאות string.h כי יש לזכור שגם תו האפס תופס מקום במערך! int age; scanf("%d",&age); printf("do you have future? %s", (age<30)?"yes":"no"); 37
מה האורך? ציינו מה הגודל של המחרוזת ושל המערך: char str1[] = "My Cat"; char str2[10] = "My Cat"; char str3[] = { M, y,, C, a, t ; char str4[7] = { M, y,, C, a, t ; char str5[7] = { M, y, \0, C, a, t ; char str6[7] = { M, y, 0, C, a, t ; char str7[] = ""; 38
העברת מחרוזת לפונקציה כמו במערכים, אפשר להעביר לפונקציה מצביע לתו הראשון של המחרוזת. אבל בגלל שסוף מחרוזת תמיד מסומן ע"י '0\', אין צורך להעביר פרמטר של אורך. לדוגמה, מה תדפיס תוכנית הבאה? #include <stdio.h> void print_string(char *s) { printf("%s\n", s); 39 int main() { char str[] = "Hello World"; print_string(str); print_string(str+6); return 0;
תרגיל כתוב פונקציה המקבלת שתי מחרוזות s1 ו- s2 ובודקת האם s1 מסתיימת בתת מחרוזת s2. אם כן, הפונקציה תחזיר 1. אם לא, הפונקציה תחזיר 0. למשל עבור: s2="world!" ו-"! World s1="hello הפונקציה תחזיר 1. עבור: s2="hello" ו-"! World s1="hello הפונקציה תחזיר 0. עבור: World!" s2="hello ו-"! s1="world הפונקציה תחזיר 0. s1 מהי המיקום של הנקודה הזאת ב- s1? s2 גודל של s1 פחות הגודל של s2 40
פתרון #include <string.h> int is_ending_substring(char *s1, char *s2) { int len1 = strlen(s1); int len2 = strlen(s2); if (len2 > len1) { /* s2 is definitely not a substring */ return 0; if (!strcmp(s1+len1-len2, s2)) { return 1; return 0; שקול ל- &s1[len1-len2] 41