Arrays and Strings מערך - אוסף משתנים בעלי שם משותף. הפנייה לכל איבר נעשית ע י אינדקס. ב- C מערך מוגדר בזיכרון רציף. האיבר הראשון נמצא בכתובת הנמוכה. לדוגמא, אם a מוגדר החל מכתובת :1000 char a[7]; Element a[0] a[1] a[2] a[3] a[4] a[5] Address 1000 1001 1002 1003 1004 1005 תוכנה - 1 חזרה שולי לב יהודי shulyl@tau.ac.il 30/11/03 Software 1 - Shuly Lev-Yehudi 2 Single-Dimension Arrays (cont.) Single-Dimension Arrays אין בדיקה של גבולות המערך! ניתן לכתוב מעבר לגבולות ללא שגיאת קומפילציה: int count[10], i; /* this causes count to be overrun */ for (i=0; i < 100; i++) count[i] = i; type var_name[size]; double balance[100]; לדוגמא: char p[10]; p[0] האינדקס של האיבר הראשון הוא 0. מגדיר מערך של characters עם 10 אלמנטים עד.p[9] השטח שמוגדר עבור מערך: total bytes = sizeof(base type) * size of array 30/11/03 Software 1 - Shuly Lev-Yehudi 4 30/11/03 Software 1 - Shuly Lev-Yehudi 3 Passing Single-Dimension Arrays to Functions לא ניתן להעביר תוכן של מערך שלם כארגומנט לפונקציה. ניתן להעביר מצביע לתחילת המערך: int i[10]; func1(i); בפונקציה, ניתן להגדיר את הפרמטר באחד מ- 3 אופנים: Generating a Pointer to an Array שם המערך ללא ציון אינדקס הוא מצביע למערך: int sample[10]; int *p; p = sample;.sample יקבל את כתובת האיבר הראשון של p 30/11/03 Software 1 - Shuly Lev-Yehudi 6 30/11/03 Software 1 - Shuly Lev-Yehudi 5
Return an Array from a Function int main(void) char *mystr; mystr = getstr(); printf("%s\n", mystr); return 0; char *getstr() char str[20]; str[0] = 'a'; str[1] = 'b'; str[2] = '\0'; return(str); ביציאה מהפונקציה השטח של str משוחרר ולכן ב- main יודפס "זבל". Passing Single-Dimension Arrays to Functions void func1(int *x) /* pointer */... void func1(int x[10]) /* sized array */... void func1(int x[]) /* unsized array */... גם זה יעבוד - יעבור מצביע - לא יוצר 32 איברים: void func1(int x[32]) /* sized array */ צריך להקצות את str דינמית ע"י: malloc(20); char *str = 30/11/03 Software 1 - Shuly Lev-Yehudi 8 30/11/03 Software 1 - Shuly Lev-Yehudi 7 String Manipulation Functions Name Functions strcpy(s1,s2) Copies s2 into s1 strcat(s1,s2) Concatenates s2 onto the end of s1 strlen(s1) Returns the length of s1 strcmp(s1,s2) Returns 0 if s1 and s2 are the same; less than 0 if s1<s2; greater than 0 if s1>s2 strcmp מחזירה false אם המחרוזות שוות. לכן כדי לבדוק שוויון: if (!strcmp(s1,s2)) printf( equal strings\n ); #include <string.h> כדי להשתמש בפונקציות: Strings String הוא מערך של characters המסתיים ב- null שמצוין ע י `\0`. לכן גודל המערך צריך להיות גדול ב- 1 מאורך המחרוזת. למשל למחרוזת באורך 10: char str[11]; ניתן להגדיר קבוע מחרוזת ע י גרשיים: there hello בקבוע אין צורך להוסיף.null 30/11/03 Software 1 - Shuly Lev-Yehudi 10 30/11/03 Software 1 - Shuly Lev-Yehudi 9 Indexing Pointers 30/11/03 Software 1 - Shuly Lev-Yehudi 12 Arrays of Strings מוגדר ע י מערך דו-מימדי: char str_array[30][80]; מגדיר מערך עם 30 מחרוזות, כל אחת באורך של עד 79 תווים. כדי לגשת למחרוזת אחת: gets(str_array[2]); שקול ל: gets(&str_array[2][0]); אך צורת הכתיבה הראשונה מקובלת יותר. שם מערך ללא אינדקס הוא מצביע לאיבר הראשון במערך. לכן, הביטוי הבא הוא :true p == &p[0] באותו אופן, ניתן להוסיף אינדקס למצביע, כאילו שהוגדר כמערך: int *p, i[10]; p = i; p[5] = 100; /* assign using index */ *(p+5) = 100; /* assign using pointer arithmetic */ שתי הפקודות מציבות 100 באיבר השישי של i. 30/11/03 Software 1 - Shuly Lev-Yehudi 11
Pointers Array Initialization Memory address 1000 1001 1002 1003 1004 Variable in memory 1003 מה זה?Pointer משתנה המכיל כתובת של משתנה אחר int i[10] = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; [0]i יקבל את הערך 1 והלאה עד [9]i שיקבל את הערך 10. איתחול מערך של :characters char str[15] = I like the sea ; זה שקול ל: char str[15] = I,, l, i, k, e,, t, h, e,, s, e, a, \0 ; 1005 30/11/03 Software 1 - Shuly Lev-Yehudi 14 30/11/03 Software 1 - Shuly Lev-Yehudi 13 The Pointer Operators & מחזיר את הכתובת של משתנה בזיכרון: m = &count; שם ב- m את הכתובת של.count אין כל קשר לערך של המשתנה!count * מחזיר את הערך שנמצא בכתובת שמופיעה אחריו. הוא המשלים של &. לדוגמא: q = *m; ישים את הערך של count בתוך q. כלומר, את הערך ש- m מצביע עליו. Pointer Variable משתנה שצריך להכיל ערך של מצביע צריך להיות מוגדר כך: type *name; type מציין את סוג המשתנים שה- pointer יכול להצביע עליהם. כל מצביע יכול להצביע לכל משתנה מסוג כלשהו, אולם.base נעשה לפי ה- type pointer arithmetic 30/11/03 Software 1 - Shuly Lev-Yehudi 16 30/11/03 Software 1 - Shuly Lev-Yehudi 15 The Pointer Operators(cont.) double x, y; int *p; /* The next statement causes p (which is an integer pointer) to point to a double. */ p = &x; /* The next statement does not operate as expected */ y = *p; לא תהייה השמה של ערך x ל- y. כיוון ש- p מוגדר כמצביע ל- int, רק 4 בתים יועברו ל- y ולא 8 בתים שמכילים מספר.floating point ב- ++C אי אפשר לבצע השמה כזו ללא casting (שגיאת קומפילציה). 30/11/03 Software 1 - Shuly Lev-Yehudi 18 The Pointer Operators(cont.).& - bitwise AND לא להתבלבל עם כפל - * ועם האופרטורים * ו-& (של מצביעים) קודמים לכל האופרטורים האריתמטיים, למעט מינוס אונרי (5-) שהוא בעל קדימות זהה. צריך לדאוג שהמצביע אכן תמיד מצביע לערך מסוג מתאים. אם הגדרנו מצביע ל- int, ה- compiler מניח שכל כתובת שהוא יכיל היא כתובת של ערך מסוג.int 30/11/03 Software 1 - Shuly Lev-Yehudi 17
Pointer Arithmetic 2 פעולות אפשריות: חיבור וחיסור. דוגמא: p1 מצביע ל- int וערכו 2000. נניח ש- int הוא באורך 4 בתים. אחרי הפעולה p1++ p1, יכיל 2004 ולא.2001 בכל פעם ש- p1 מקודם, הוא יצביע ל- integer הבא. p1 = p1+12; ניתן לחבר ערך כלשהו, למשל: p1 יצביע לאלמנט ה- 12 (מהסוג של p1) אחרי זה שהוא מצביע כעת. ניתן להחסיר ערך של מצביע מערך של מצביע אחר כדי למצוא את מספר האלמנטים (מה- type base שלהם) הנמצאים ביניהם. לא ניתן: לכפול, לחלק, לחבר שני מצביעים, לבצע פעולות לוגיות.double או float לחבר או לחסר ערכי,(bitwise) 30/11/03 Software 1 - Shuly Lev-Yehudi 20 Pointer Expressions Pointer Assignments int x; int *p1, *p2; p1 = &x; p2 = p1; printf( %p, p2); /* print the address of x, not x s value */ 30/11/03 Software 1 - Shuly Lev-Yehudi 19 Pointer Comparisons Example: if (p < q) printf( p points to lower memory than q\n ); בד כ שימושי כאשר מספר מצביעים מצביעים לאובייקטים משותפים, כמו במערך. דוגמא - מחסנית :stack 30/11/03 Software 1 - Shuly Lev-Yehudi 22 Pointer Arithmetic - example: char *ch = 3000; short *i = 3000; כל פעולות האריתמטיקה על מצביע נעשות בהתאם ל- type base שלו Last-in, First-out רשימת איברים שהגישה אליהם היא בשיטת.((LIFO 2 פעולות: push() - מכניסה ערך למחסנית pop() - מוציאה ערך מהמחסנית בדוגמא, הערכים שהמשתמש מקליד,מוכנסים למחסנית. אם הוקלד 0, מוציאים ערך מהמחסנית.(pop) התכנית מסתיימת כשמתקבל ערך 1-. ch ch +1 ch+2 ch+3 ch+4 ch+5 3000 3001 3002 3003 3004 3005 30/11/03 Software 1 - Shuly Lev-Yehudi 21 i i + 1 i + 2 Stack example (cont.): pop(void) if (p1 == tos) printf( Stack underflow ); exit(1); p1--; return *(p1 + 1); הערה: בפקודת ה- return הסוגריים הכרחיים. בלעדיהם היה: return *p1 + 1; הערך שהיה חוזר הוא הערך שבכתובת p1 ועוד 1, ולא הערך בכתובת p1+1 (שהיא 4 בתים אחרי p1). 30/11/03 Software 1 - Shuly Lev-Yehudi 24 Stack example: #include <stdio.h> #include <stdlib.h> #define SIZE 50 void push(int i); void pop(void); int *tos, *p1, stack[size]; int value; tos = stack; /* tos points to the top of the stack */ p1 = stack; /* initialize p1 */ do printf( Enter value: ); scanf( %d, &value); if (value!= 0) push(value); else printf( value on top is%d\n, pop()); while (value!= -1); void push(int i) p1++; if (p1 == (tos + SIZE)) printf( Stack overflow ); exit(1); *p1 = i; 30/11/03 Software 1 - Shuly Lev-Yehudi 23
Arrays of Pointers (cont.) void display_array(int *q[ ]) int t; for (t=0; t < 10; t++) printf ( %d, *q[t]); q אינו מצביע ל- integers, אלא מצביע למערך של מצביעים ל- integers. Arrays of Pointers int *x[10] מערך של 10 מצביעים ל- int : הכנסת כתובת המשתנה var לאיבר השלישי במערך: x[2] = &var; למציאת הערך של :var *x[2] העברת מערך של מצביעים לפונקציה - קריאה לפונקציה עם שם המערך ללא אינדקס. למשל, פונקציה המקבלת מערך x תראה כך: 30/11/03 Software 1 - Shuly Lev-Yehudi 26 30/11/03 Software 1 - Shuly Lev-Yehudi 25 Using Structures Pointers 30/11/03 Software 1 - Shuly Lev-Yehudi 28 Arrays of Pointers (cont.) שימוש נפוץ במערך של מצביעים הוא להחזקת מצביעים ל- strings. לדוגמא, פונקציה המקבלת קוד שגיאה ומדפיסה הודעה מתאימה: void syntax_error(int num) static char *err[] = Cannot open file\n, Read error\n, Write error\n, Media Failure\n ; printf ( %s, err[num]); מציאת כתובת ה- structure ע י & לפני שם המשתנה: struct bal float balance; char name[80]; person; struct bal *p; /* declare a structure pointer */ p = &person; ואז שם את כתובת person במצביע p. גישה לשדה ב- structure נעשית ע י האופרטור <-: p->balance האופרטור. (dot) משמש כדי לגשת לשדה כאשר פועלים ישירות על ה- structure (ולא ע י מצביע). העברת כתובת של שדה תעשה כך: func(&person.x); func2(person.name); func3(&person.name[2]) 30/11/03 Software 1 - Shuly Lev-Yehudi 27 Multiple Indirection (Pointers to Pointers) Pointer address Single Indirection Variable value Pointer Pointer Variable address address value Multiple Indirection 30/11/03 Software 1 - Shuly Lev-Yehudi 30 The Operator -> An Example: display a timer struct my_time int hours; int minutes; int seconds; ; struct my_time systime; systime.hours = 0; systime.minutes = 0; systime.seconds = 0; for ( ; ; ) update(&systime); display(&systime); void update(struct my_time *t) t->seconds++; if (t->seconds == 60) t->seconds = 0; t->minutes++;. void display(struct my_time *t) printf( %02d:, t->hours); printf( %02d:, t->minutes); printf( %02d:, t->seconds); 30/11/03 Software 1 - Shuly Lev-Yehudi 29
Initializing Pointers אחרי שמצביע הוגדר אך לפני שהושם לו ערך, הוא מכיל ערך לא ידוע ( זבל ). אם ננסה להשתמש במצביע לפני שיש לו ערך חוקי, סביר שהתכנית תעוף (וכנראה גם מערכת ההפעלה)! מוסכמה: מצביע שכרגע אינו מצביע למיקום בר-תוקף בזיכרון, מאותחל ל- null (שהוא 0). מצביע שערכו null אינו מצביע לכלום. נשתמש בערך null כדי לציין למשל, סוף של מערך של מצביעים. Multiple Indirection (cont.) הגדרת מצביע למצביע: **newbalance; float newbalance אינו מצביע למספר floating-point אלא מצביע למצביע ל- float. כדי לגשת לערך עצמו יש להפעיל את אופרטור ה-* פעמיים: int x, *p, **q; x = 10; p = &x; q = &p; printf( %d, **q); /* print the value of x */ 30/11/03 Software 1 - Shuly Lev-Yehudi 32 30/11/03 Software 1 - Shuly Lev-Yehudi 31 Initializing Pointers (cont.) Initializing Pointers (cont.) ניתן לאתחל מצביע ל- char במחרוזת כאילו שהוא מערך: char *p = hello world ; ה- compiler שומר את כל קבועי המחרוזות ב- table string ולכן הפקודה תכניס ל- p את הכתובת של הקבוע.string כפי שהוא מאוחסן ב- table hello world Search (char *p[], char *name) register int i; for (i=0; p[i]; ++i) if (!strcmp(p[i], name)) return; return -1; כאן, ערך null מציין את סוף המערך. הלולאה רצה כל עוד לא נמצא name ולא הגענו למצביע שערכו.null 30/11/03 Software 1 - Shuly Lev-Yehudi 34 30/11/03 Software 1 - Shuly Lev-Yehudi 33 Pointers to Functions (cont.) Pointers to Functions void check (char *a, char *b, int (*cmp)(const char *, const char *)); char s1[80], s2[80]; int (*p)(const char *, const char *); p = strcmp; gets(s1); gets(s2); check(s1, s2, p); void check (char *a, char *b, int (*cmp)(const char *, const char *)) printf( testing for equality\n ); if (!(*cmp)(a,b)) printf( equal ); else printf( not equal ); הביטוי (*cmp)(a,b) קורא ל- strcmp. ניתן לקרוא ל- check גם כך: check(s1, s2, strcmp); למרות שפונקציה אינה משתנה, יש לה מקום פיזי בזיכרון שניתן לשמור אותו במצביע. כתובת של פונקציה היא נקודת הכניסה לפונקציה ולכן ניתן להשתמש במצביע לפונקציה כדי לקרוא לה. כתובת של פונקציה היא שם הפונקציה ללא סוגריים או ארגומנטים (בדומה לכתובת של מערך). דוגמא: 30/11/03 Software 1 - Shuly Lev-Yehudi 36 30/11/03 Software 1 - Shuly Lev-Yehudi 35
ב( Dynamic Allocation Functions (cont.) Dynamic Allocation Functions הקצאה דינמית - הקצאת שטח בזמן ריצה. זיכרון דינמי מוקצה על ה- heap - שטח הנמצא בין התכנית והשטח הקבוע שלה,(data) לבין ה- stack. פונקציות עיקריות: - malloc מקצה שטח, - free משחררת. # include stdlib.h צריך את השורה: prototype: void *malloc(size_t number_of_bytes); מחזירה מצביע ל- void שפרושו שניתן לשים את הערך החוזר במצביע מכל סוג שהוא. בהצלחה - מוחזר מצביע ל- byte הראשון בשטח שהוקצה. בכשלון - כאשר אין מספיק מקום - מוחזר.null char *p ; p = malloc(1000); לדוגמא: אין צורך ב- cast, הערך מוסב אוטומטית לפי ה- type בצד שמאל -++C לא נעשית הסבה אוטומטית!). הקצאת.integers 50 ה- sizeof נחוץ ל- portability : int *p; p = malloc(50 * sizeof(int)); 30/11/03 Software 1 - Shuly Lev-Yehudi 38 30/11/03 Software 1 - Shuly Lev-Yehudi 37 Problems with Pointers Dynamic Allocation Functions (cont.) שימוש בערך לא טוב של מצביע יכול לגרום לכתיבה לשטח אחר. טעויות נפוצות: uninitialized pointer: /* this program is wrong */ int x, *p; x = 10; *p = x;!null לבדוק האם הערך החוזר אינו יש free מחזירה למערכת שטח שהוקצה קודם. prototype: void free(void *p); p הוא מצביע לשטח שהוקצה קודם. חשוב לא לקרוא ל- free עם ארגומנט לא נכון! p לא אותחל ולכן הערך 10 נכתב במקום לא ידוע בזיכרון. הבעיה משמעותית בתכנית גדולה. 30/11/03 Software 1 - Shuly Lev-Yehudi 40 30/11/03 Software 1 - Shuly Lev-Yehudi 39 Problems with Pointers (cont.) Problems with Pointers (cont.) Misunderstanding of how to use a pointer: /* this program is wrong */ int x, *p; x = 10; p = x; printf( %d, *p); printf לא תדפיס את הערך 10 אלא ערך כלשהו אחר. לתיקון יש לכתוב: Incorrect assumptions about variables locations: אי אפשר לדעת היכן המשתנים ממוקמים בזיכרון. לכן אם ננסה להשוות בין מצביעים שאינם מצביעים לאובייקט משותף, נקבל תוצאה לא צפויה: char s[80], y[80]; לא נכון להניח ש- s char *p1, *p2; ו- y מוקצים ברצף. p1 = s; p2 = y; if (p1 < p2)... p = &x; 30/11/03 Software 1 - Shuly Lev-Yehudi 42 30/11/03 Software 1 - Shuly Lev-Yehudi 41
argc and argv Arguments to main() מעבירים ערכים ל-() main ע"י ה- command line :arguments program_name command_line_arguments argc ו- argv מוגדרים built-in ובהם מתקבלים הארגומנטים. argc מכיל את מספר הארגומנטים והוא,int ערכו תמיד לפחות 1 כיוון ששם התכנית הוא הארגומנט הראשון. argv הוא מצביע למערך של מצביעים ל- character, כל איבר במערך מצביע לארגומנט. כל הארגומנטים הם strings התכנית צריכה להמיר מספרים ל- format המתאים. Problems with Pointers (cont.) באותו אופן לא נכון לאתחל את המערכים first ו- second במספרים 0 עד 19 (למרות שבחלק מה- compilers זה יעבוד): int first[10], second[10]; int *p, i; p = first; for (i=0; i < 20; i++) *p++ = i; 30/11/03 Software 1 - Shuly Lev-Yehudi 44 30/11/03 Software 1 - Shuly Lev-Yehudi 43 argc, argv ברוב סביבות העבודה הארגומנטים מופרדים ע"י רווח או.tab פסיק, נקודה-פסיק אינם נחשבים מפרידים. לדוגמא: run Spot, run אלו הן 3 מחרוזות: Herb,Rick,Fred וזוהי מחרוזת אחת: Alon Ronit וגם זו: argc, argv /* The name of the program is name */ void main(int argc, char *argv[ ]) if (argc!= 2) printf ( You forgot to type your name.\n ); exit(1); printf( Hello %s, argv[1]); > name Tom > Hello Tom 30/11/03 Software 1 - Shuly Lev-Yehudi 46 30/11/03 Software 1 - Shuly Lev-Yehudi 45 argc, argv Example (cont.) /* if the string display is the second argument, the countdown will also be displayed on the screen. */ if (argc == 3 &&!strcmp(argv[2], display )) disp = 1; else disp = 0; for (count=atoi(argv[1]); count; --count) if (disp) printf( %d\n, count); putchar( \a ); /* this will ring the bell in most computers */ printf ( Done\n ); 30/11/03 Software 1 - Shuly Lev-Yehudi 48 argc, argv - Example /* countdown the program counts down from a starting value (which is specified on the command line) and beeps when it reachs 0. */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> void main(int argc, char *argv[ ]) int disp, count; if (argc < 2) printf( You must enter the length of the count.\n ); exit(1); 30/11/03 Software 1 - Shuly Lev-Yehudi 47
Example /*return for each one of the code sections: 1. if it is not legal 2. legal - but not working right 3.legal and working */ typedef struct complex float real, img; complex; What Does main() Return? הפונקציה main() מחזירה int לתהליך שקורא לה שהוא בד"כ מערכת ההפעלה. החזרת ערך מ-() main שקולה לקריאה ל-() exit עם אותו ערך אם לא מוחזר ערך הערך שחוזר לתהליך הקורא אינו מוגדר (רוב ה- compilers יחזירו 0, אך לא תמיד). ניתן להגדיר את main() כ- void אם איננה מחזירה ערך, אולם אם פונקציה איננה מחזירה ערך וגם לא מוגדרת כ- void יתקבל.warning 30/11/03 Software 1 - Shuly Lev-Yehudi 50 30/11/03 Software 1 - Shuly Lev-Yehudi 49 Example (cont.) complex * complex_init2(complex *p_c, float _real, float _img) p_c->real = _real; p_c->img = _img; return p_c; complex * complex_init3(float _real, float _img) complex c; c.real = _real; c.img = _img; return c; 30/11/03 Software 1 - Shuly Lev-Yehudi 52 Example (cont.) complex * complex_init (float _real, float _img) complex c; c.real = _real; c.img=_img; return &c; complex * complex_init1 (complex c, float _real, float _img) c.real = _real; c.img=_img; return &c; 30/11/03 Software 1 - Shuly Lev-Yehudi 51 Example (cont.) struct complex complex_init4(complex *p_c,float _real,float _img) struct complex c; c.real = _real; c.img = _img; return c; complex complex_init5(float _real,float _img) complex c = _real,_img; return *(&c); 30/11/03 Software 1 - Shuly Lev-Yehudi 53