מ JAVA -לC - קרן כליף © Keren Kalif ביחידה זו נלמד: 2 רקע לשפת C תוכנית ראשונה בשפת C הגדרת משתנים בשפת C קלט ופלט בשפת C פונקציות מערכים סוגי משתנים © Keren Kalif רקע כללי לשפת C שפת Cתוכננה לראשונה בשנים 1973-1969על ידי דניס ריצ'י ממעבדות בל על מערכות UNIX הצורך בשפה עלה לשם כתיבת מערכת הפעלה 3 רוב הגרעין ) )Kernelשל מערכת ההפעלה לינוקס כתוב ב ,C -כמו גם הרבה תוכנות אחרות המהוות את מערכת ההפעלה GNU/Linux באותה עת הייתה קיימת שפת Bובאמצעותה נכתבה שפת C בתחילה מקור השם של השפה הוא באותה שפת Bתוך התקדמות אות אחת באלפבית עם התפתחות שפת ,Cהקומפיילר בו מקמפלים את השפה נכתב אף הוא בשפת C © Keren Kalif עקרון מנחה של שפת - Cיעילות כל פעולה בשפה מתבטאת בפעולה בודדת או במספר מועט של פעולות בשפת סף לכן אין בשפה: מסיבה זו היא גם כוללת לא מעט אפשרויות לשגיאות בזמן ריצה, אשר אינן מאותרות ע"י הקומפיילר בזמן קומפילציה ,למשל: 4 סוגי מבנים מורכבים כגון מחרוזות בדיקות סמויות בגישה למערכים מערכת הטיפוסים שלה אינה בטוחה פנייה למשתנה שלא הוקצה פנייה לערך לא מאותחל בזיכרון (ה JAVA -מתקבלת שגיאת קומפילציה) הנחת העבודה של מתכנני השפה (והתקנים המאוחרים יותר) היא כי "המתכנת מבין מה הוא עושה" © Keren Kalif שפת Cכשפה פרוצדורלית שפה פרוצדורלית: 5 הרעיון שהקוד מחולק לפרוצדורות (פונקציות) שכל אחת אחראית על חלק מסויים בתוכנית בכל פרוצדורה יש את המשתנים המוגדרים בה ומוכרים אך ורק לה (נקראים גם משתנים לוקאליים או מקומיים) ישנם גם משתנים גלובליים אשר משותפים לכל הפרוצדורות בניגוד לשפה מונחת-עצמים ,ישנם רכיבים בשפה שאינם חלק מאובייקטים (למשל ה main -אינו נמצא בתוך מחלקה) האובייקטים רק מכילים מידע ואינם יודעים לבצע פעולות לצורך השוואה JAVA ,היא שפה מכוונת עצמים טיפוסים וקלט ופלט בשפת C הפקודות printfו ,scanf -פורמטי הדפסה שונים © Keren Kalif כתיבת תוכנית ראשונה בC - כל תוכנית ב C -תראה כך: ספריה המכילה פקודות קלט ופלט >#include <stdio.h נשים לב לסגנון :הסוגרים המסולסלים הפותחים בלוק נמצאים בשורה חדשה mainהיא פונקציה המורצת עם הרצת התוכנית 7 בכל פרוייקט תהייה פונקצית mainאחת בדיוק )(void main { … } © Keren Kalif !דגש כדי שאני לא אתעצבן http://qph.is.quoracdn.net/main-qimg-e0c9dafb319150b6c6d9816047ed9eae?convert_to_webp=true 8 © Keren Kalif C -טיפוסי נתונים ב Definition Size Range char 1 byte -27…27-1 4 bytes -231…231-1 )‘a’, ‘A’, ‘6’,’!’( תו int (-128..127) )-3,7,8234( מספר שלם float 4 bytes )5.2 ,-89 ,3.6( מספר ממשי double 8 bytes )5.2 ,-89 ,3.6( מספר ממשי boolean אין 9 © Keren Kalif )2( C -טיפוסי נתונים ב Definition Size Range short 2 bytes -215… 215-1 (-32,768…32,767) 8 bytes -263…263-1 4 bytes 0…232-1 2 bytes 0…216-1 (0….65535) 8 bytes 0…264-1 1 byte 0…28-1 מספר שלם long מספר שלם unsigned int מספר שלם חיובי unsigned short מספר שלם חיובי unsigned long מספר שלם חיובי unsigned char תו חיובי בלבד (0…255( 10 © Keren Kalif הגדרת משתנים בתוכנית charמורכב מbyte1 - intמורכב מbyte 4 - 1000 { 1004 5.2 1012 ’‘f char: ch 1013 7 short: n3 1015 77 short: n4 ;short n3 = 7, n4 = 77 1017 234234 uint: n5 ;unsigned int n5 = 234234 1021 -11 int: n6 int double: n2 ;n1 = 4 ;n2 = 5.2 int double ;’ch = ‘f ;n6 = -11 char } למה ההפרש בין הכתובות שונה? 11 4 int: n1 )(void main כי כל סוג משתנה תופס כמות שונה של בייטים בשפת Cערכו של משתנה שאינו מאותחל הוא זבל, וניתן לקרוא את ערכו ,אך יתכן ונעוף בזמן ריצה (בניגוד לשפת JAVAשתתריע על כך בז מן קומפילציה) © Keren Kalif הדפסה למסך : היא הדפסה למסךstdio.h אחת הפקודות הנמצאות בספריה #include <stdio.h> void main() { printf(“Hello World!”); } 12 © Keren Kalif הדפסה למסך ()2 >#include <stdio.h הרצה של תוכנית זו תדפיס למסך את המחרוזת 6 is a number )(void main { ;)”printf(“6 is a number } דרך נוספת לכתוב תוכנית זו: >#include <stdio.h מציין שבחלק זה במחרוזת יהיה מספר דצימלי ,במקרה זה 6 )(void main { ;)printf(“%d is a number”, 6 } 13 © Keren Kalif קבלת קלט מהמשתמש פקודה נוספת הנמצאת בספריה stdio.hהיא קליטת נתון מהמשתמש: פירוש הפקודה :scanf אחסן את הערך שיתקבל מהמשתמש במשתנה הנקרא x xהוא השם של המקום שבו נאכסן את הערך המתקבל מהמשתמש & מציין לנו איפה נמצא מקום זה מה זה ?%d 14 )(void main { ;int x ;)scanf(“%d”, &x } מה זה ?x >#include <stdio.h ציון שהנתון אותו אנו קוראים הוא מספר דצימלי © Keren Kalif פורמט קליטה והדפסה לטיפוסים השונים Data Type Format Explaination int %d Decimal short %d Decimal long %ld Long Decimal char %c Character float %f Float double %lf Long Float unsigned %u Unsigned 15 © Keren Kalif בעיה :התוכנית "מדלגת" על פקודת הקלט )(void main } ;int n1 ;char c1 הקלדתי 5ואז .ENTER לתוך n1נכנס ,5ולתוך ch1נכנס האנטר. ENTERהוא גם תו ,ומאחר והוא היה ב buffer -הוא נקלט לתוך ch1 ;)" printf("Please enter a number: ;)scanf("%d", &n1 ;)" printf("Please enter a char: ;)scanf("%c", &c1 ;)printf("n1= |%d| c1=|%c|\n", n1, c1 כאשר אנחנו קולטים נתונים בפקודות נפרדות, נרצה לנקות את ה buffer -בין הפעולות השונות. 16 { © Keren Kalif flushall הפקודה:הפתרון void main() } int n1; char c1; printf("Please enter a number: "); scanf("%d", &n1); printf("Please enter a char: "); flushall(); scanf("%c", &c1); printf("n1= |%d| c1=|%c|\n", n1, c1); { char כאשר קוראים לתוך משתנה מטיפוס נשים את,לאחר שכבר בוצעה קליטה כלשהי . בין הקריאות השונותflushall הפקודה .f - וENTER ,7 הקלדתי ואז ניקיתי את,7 נכנסn1 לתוך buffer - הENTER -ה .flushall באמצעות הפקודה .ch1 נכנס לתוךf התו 17 © Keren Kalif הדפסת וקליטת משתנים מטיפוסים שונים #include <stdio.h> void main() { int n1; double n2; char ch; } printf(“Please enter an int, double and char: “); scanf(“%d %lf %c”, &n1, &n2, &ch); printf(“values are: %d %lf %c \n”, n1, n2, ch); 18 © Keren Kalif פורמט הדפסה :ישור הטקסט כאשר משתמשם בפקודה printfלצורך הדפסה ניתן לשלוט על כמות התווים שכל חלק יתפוס ,וכן על כיוון היישור שלו: לאחר ה % -נכתוב מספר המציין כמה מקומות ערך המשתנה יתפוס בתצוגה ברירת המחדל היא הצמדת הטקסט לימין כדי להצמיד לשמאל נוסיף מינוס יישור הטקסט לימין יישור הטקסט לשמאל 19 )(void main { ;int n ;)" printf("Please enter a number: ;)scanf("%d", &n ;)printf("The number is: |%d|\n", n ;)printf("The number is: |%5d|\n", n ;)printf("The number is: |%-5d|\n", n { © Keren Kalif 1. 2. 3. void main() } int rows, cols, i, j, res; לוח הכפל:דוגמא 4. 5. 6. 7. 8. 9. 10. 11. printf("Enter rows and cols for multiplication-board: "); scanf("%d %d", &rows, &cols); 12. 13. 14. for (i=1 ; i <= (cols+1)*5 ; i++) printf("-"); printf("\n"); 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. for (i=1 ; i <= rows ; i++) } // print the left column printf("%4d|", i); for (j=1 ; j <= cols ; j++) printf("%4d|", i*j); printf("\n"); { printf("\n"); // print first line printf("\n%4c|", ' '); for (i=1 ; i <= cols ; i++) printf("%4d|", i); printf("\n"); { 20 © Keren Kalif יישור בגודל משתנה ניתן גם שכמות התווים שכל חלק יתפוס בהדפסה תלקח ממשתנה וברשימת הפרמטרים נשים עבורה את הערך,* נשים% -לאחר ה המבוקש void main() } int n, space; } printf("Please enter a number and number of spaces: "); scanf("%d %d", &n, &space); printf("The number is: |%d|\n", n); printf("The number is: |%*d|\n", space, n); printf("The number is: |%-*d|\n", space, n); 21 © Keren Kalif מוביל0 הדפסת מוביל0 ניתן להדפיס נתונים עם הדפסת % - לאחר ה0 נוסיף ספרות9 תמיד עם.ז.למשל לצורך הדפסת ת void main() } int id; printf("Please enter your id: "); scanf("%d", &id); printf("Your id is %09d\n", id); { 22 © Keren Kalif הגבלת כמות התווים הנכנסים לתוך משתנה ניתן להגביל את כמות התווים הנקלטים לתוך משתנה בפקודת ה scanf -לאחר ה % -נגביל את כמות התווים למשל :קליטת מספר קטן מ1000 - )(void main { ;int num ;)" printf("Please enter a number less than 1000: ;)scanf("%3d", &num ;)printf("The number is %d\n", num { 23 © Keren Kalif קליטת נתונים תוך הפרדתם ניתן לקלוט מהמשתמש ערך (בד"כ מספר או מחרוזת) ובפועל לקלוט את הערך ליותר ממשתנה אחד void main() } int departmentId, innerCourseId; } printf("Please enter a 5 digits course code: "); scanf("%2d%3d", &departmentId, &innerCourseId); printf("Department: %d Course: %d\n", departmentId, innerCourseId); 24 © Keren Kalif הגדרת משתנים בתוכנית שגיאת קומפילציה :המשתנים חייבים להיות מוגדרים בתחילת בלוק! 25 © Keren Kalif #define אפשר להגדיר קבוע באופן הבא: שורה זו אינה מסתיימת ב; - 26 #define TAXES 0.17 את הפקודה #defineרצוי לכתוב בתחילת התוכנית ,לפני הפונקציה mainובכך לקבוע לערך תחום הכרה ציבורי אפשר לכתוב את הפקודה בכל מקום בתוכנית .הקבוע יהיה מוכר מאותו מקום ועד לסוף התוכנית בכל פעם שהקומפיילר יפגוש את השם ) TAXESבדוגמא) הוא יחליפו בערך )(0.17 © Keren Kalif define הגדרת קבועים ע"י #include <stdio.h> #define TAXES 0.17 אינו משתנהdefine קבוע שהוגדר ע"י .ולכן אינו תופס מקום בזכרון .הסבר מפורט בהמשך float: price void main() { float price; float totalPrice; 1000 float: totalPrice 1004 1008 printf("Please enter the product’s price: "); scanf("%f", &price); % כדי להציג את התו%% totalPrice = price + (price*TAXES); printf("Total price including %.2f%% taxes is %.2f\n", TAXES*100, totalPrice); ספרות2 ציון להצגת רק } אחרי הנקודה העשרונית 27 פונקציות הפרדה בין הצהרה למימוש ,יצירת קובץ ספריה ,תהליך הקומפילציה ,שגיאות קומפילציה לעומת שגיאות לינקר ,יצירת מספרים אקראיים © Keren Kalif C תוכנית שלמה עם פונקציה – איך זה בשפת #include <stdio.h> int power(int a, int b) { int i, result=1; for (i=0 ; i < b ; i++) result *= a; return result; } void main() { int base, exponent, result; printf("Please enter base and exponent: "); scanf("%d %d", &base, &exponent); int:base ??? 2 1000 int: exponent ??? 3 1004 int: result ??? 8 1008 main -הזיכרון של ה int: a 2 2000 int: b 3 2004 int: i ??? … 2008 int: result ??? 1 8 2012 result = power(base, exponent); printf("%d^%d=%d\n", base, exponent, result); } power הזיכרון של power main ( line 4) מחסנית הקריאות 29 © Keren Kalif הפרדה בין הצהרות למימוש #include <stdio.h> // prototypes power(int,base, int); int exponent); int power(int void main() { int base, exponent, result; בשורת ההצהרה לא חייבים לציין את שמות המשתנים printf("Please enter base and exponent: "); scanf("%d %d", &base, &exponent); } result = power(base, exponent); printf("%d^%d=%d\n", base, exponent, result); ; ובסיום כל הצהרה,main -• את ההצהרות נרשום לפני ה int power(int base, int exponent) )טיפוס- (אבprototype • חלק זה נקרא { int i, result=1; main -את המימושים נכתוב מתחת ל for (i=0 ; i < exponent ; i++) ההפרדה היא מאחר ומעניין אותנו איזה פונקציות יש result *= base; ופחות איך הן מבצעות את העבודה,בתוכנית return result; } חתימת הפונקציה בהגדרה בראש הקובץ ובמימוש חייבות • • • להיות זהות 30 © Keren Kalif פונקציות ספריה כל תוכנית בשפת תכנות עילית ,ובפרט שפת ,Cמורכבת מאוסף של פונקציות קיימות פונקציות ספריה בשפה אותן ניתן להכליל בתוכנית ולקרוא להן בכל מקום בו נרצה ולהשתמש בהן כמה פעמים שנרצה למשל כאשר עושים: >#include <stdio.h ניתן להשתמש בפונקציות המוגדרות בספריה זו בכל מיני מקומות בקוד (למשל )printf, scanf 31 גם אנו יכולים לכתוב פונקציות או ספריות של פונקציות ,שאותן ניתן להכליל ולהשתמש בהן כרצוננו © Keren Kalif יצירת קובץ ספריה עד כה השתמשנו בפונקציות שקיבלנו משפת C כל פונקציה כזו נכתבה בספריה שאליה ביצענו include כדי לייצר ספריה משלנו נייצר 2קבצים חדשים: 32 למשל כדי להשתמש בפונקציות של מחרוזות נכלול את >#include <string.h <file_name>.hהוא קובץ אשר יכלול את ה prototypes -של הפונקציות ,ואליו אח"כ נעשה includeמהקובץ שירצה להשתמש בפונקציות המוגדרות בו <file_name>.cהוא קובץ שיכיל includeלקובץ הheader - התואם ויממש את הפונקציות המוגדרות בו includeלספריה שכתבנו יהיו בתוך "" (גרשיים) ולא בתוך <> © Keren Kalif דוגמא – ספריה המטפלת בתווים 33 © Keren Kalif דוגמא – ספריה המטפלת בתווים ()2 34 © Keren Kalif ספריות שלנו -סיכום 35 ניתן לכתוב את כל הקוד בקובץ אחד ,אבל אם אנחנו כותבים קוד כללי ,שיתכן ויהיה שימושי גם במקומות אחרים ,נעדיף לחלק את הקוד לקובץ ספריה נפרד מי שירצה להשתמש בספריה שלנו ,יתעניין מה יש לה להציע, ולא איך היא מבצעת את הפעולות ,וכך הוא יוכל להסתכל בקובץ headerבלבד בו יש ריכוז של כל הפונקציות מכירת הספריה ללקוח תסתכם בקובץ ה h -ובקובץ בינארי שהוא תוצר של הקומפילציה ,וכך לא נחשוף את ה"איך" © Keren Kalif תהליך הפיכת תוכנית Cלשפת מכונה Disk Editor Disk Preprocessor Disk Compiler Disk Linker התוכנית נכתבת בעורך טקסטואלי ונכתבת לדיסק קדם המעבד עובר על הקוד ומבצע החלפות נחוצות (כל הפקודות המתחילות ב ,# -כמו defineוinclude - עבור כל קובץ ,cהקומפילר יוצר קובץ objבשפת מכונה ושומר אותו על הדיסק .בשלב זה רק נבדקת תקינות הסינטקס בפונקציות. ה linker -קושר את קובץ ה obj -עם הספריות ,ויוצר קובץ exeהמוכן להרצה ונשמר בדיסק .כלומר ,מקשר בין קריאה לפונקציה ,למימוש שלה. Primary Memory Loader .. .. .. Disk Primary Memory CPU .. .. .. 36 בעת ההרצה ,טוענים את התוכנית מהדיסק לזיכרון הראשי עם הרצת התוכנית ,ה cpu -עובר על כל פקודה ומריץ אותה © Keren Kalif דוגמא לשגיאת קומפילציה #include <stdio.h> void foo(int x) } printf("the number is %d\n"); { void main() { for (i=0 ; i < 5 ; i++) } printf("%d ", i); goo(i); { } :נקבל את שגיאת הקומפילציה הבאה error C2065: 'i' : undeclared identifier :goo הקומפיילר רק נותן אזהרה שהוא לא מוצא את warning C4013: 'goo' undefined; assuming extern returning int הקומפיילר אינו ממשיך לתהליך לינקר,במידה ויש שגיאות קומפילציה 37 © Keren Kalif דוגמא לשגיאת לינקר #include <stdio.h> void foo(int x) } printf("the number is %d\n"); { void main() { int i; } for (i=0 ; i < 5 ; i++) } printf("%d ", i); goo(i); { תוכנית זו מתקמפלת (אין שגיאות סינטקטיות בתוך ,הפונקציות) ולכן הקומפיילר עובר לתהליך הלינקר :ומוציא את השגיאה הבאה error LNK2019: unresolved external symbol _goo referenced in function _main 38 © Keren Kalif יצירת מספרים אקראיים אשר מגרילה מספרrand() קיימת הפונקציהC בשפת MAX_RAND לקבוע0 טווח הערכים שיוגרלו הינו בין stdlib.h קבוע זה מוגדר בספריה #include <stdio.h> #include <stdlib.h> // for ‘RAND_MAX’ void main() } int i; printf("rand gives value between 0-%d\n", RAND_MAX); for (i=0 ; i < 5 ; i++) printf("%d ", rand()); } printf("\n"); 39 © Keren Kalif יצירת מספרים אקראיים ()2 40 randפועלת עפ"י אלגוריתם קבוע המתבסס על מספר התחלתי כלשהו (נקרא ,)seed numberולכן תמיד בכל הרצה תחזיר ערכים זהים כדי ש rand -באמת תחזיר מספרים אקראיים ,כלומר תתבסס על מספר התחלתי שונה בכל פעם ,יש להפעיל את הפונקציה srandעם ערך התחלתי המייצג את הזמן הנוכחי (והוא אכן יחודי בכל הרצה) © Keren Kalif )3( יצירת מספרים אקראיים #include <stdio.h> #include <stdlib.h> // for 'RAND_MAX' #include <time.h> // for 'time' void main() } int i; מחזירה מספר המייצג את הזמן הנוכחיtime(NULL) )1.1.1970 -(מספר השניות שעברו מאז ה srand ( time(NULL) ); // initialize random seed printf("rand gives value between 0-%d\n", RAND_MAX); for (i=0 ; i < 5 ; i++) printf("%d ", rand()); printf("\n"); { 41 © Keren Kalif יצירת מספרים אקראיים בטווח מסוים:תזכורת #include <stdio.h>' #include <time.h> // for 'time' void main() } int i; srand ( time(NULL) ); // initialize random seed printf("rand gives value between 0-6\n"); for (i=0 ; i < 5 ; i++) printf("%d ", rand()%6+1); printf("\n"); { ומאחר,0-5 תחזיר לנו ערכים בין%6 פעולת ..1 הוספנו1-6 ואנחנו רוצים בטווח בין 42 מערכים הגדרת מערכים ,מערכים בזכרון ,גודל של מערך ,אתחול מערך ,השמת מערכים ,מערך רב-מימדי ,העברת מערך לפונקציה © Keren Kalif מערכים בשפת Cמערך הוא אוסף של משתנים מאותו טיפוס המוגדרים על הstack - בניגוד לשפת JAVAבה מערך הוא אובייקט המוקצה ונמצא על ה- heap לכן יש להגדיר את גודל המערך עם יצירתו הגודל צריך להיות ידוע בזמן קומפילציה (לכן אינו יכול להיות משתנה) הסוגריים [ ] יהיו צמודים לשם המשתנה 44 )(void main { ;]int arr1[3 } © Keren Kalif דוגמא למערך בזיכרון int: arr1[] #define SIZE 3 void main() { int arr1[SIZE], x=4, arr2[SIZE]; } int: x int: arr2[] ??? 1000 ??? 1004 ??? 1008 4 1012 ??? 1016 ??? 1020 ??? 1024 כמו כל משתנה שלא אותחל,ערכם של איברי המערך הוא זבל SIZE*sizeof(type) :גודל המערך בזיכרון 3*sizeof(int) = 3*4 = 12 :ובדוגמא זו 45 © Keren Kalif גודל של מערך גודל המערך יוחזק במשתנה נפרד בה המערך "יודע" את גודלוJAVA בניגוד לשפת #define SIZE 5 void main() { int arr[SIZE], i; printf("Please enter %d numbers: ", SIZE); for (i=0 ; i < SIZE ; i++) scanf("%d", &arr[i]); printf("The numbers are: "); for (i=0 ; i < SIZE ; i++) printf("%d ", arr[i]); printf("\n"); } 46 © Keren Kalif אתחול מערך כאשר מגדירים מערך ערכי איבריו הוא זבל ניתן לאתחל את איברי המערך באחת מהדרכים הבאות: //arr1[0]=5, arr1[1]=3, arr1[2]=1 47 ;}int arr1[3] = {5, 3, 1 //arr2[0]=5, arr2[1]=3, arr2[2]=1 !and the size of the array is 3 ;}int arr2[] = {5, 3, 1 //arr3[0]=5, arr3[1]=0, arr3[2]=0 ;}int arr3[3] = {5 //arr4[0]=0, arr4[1]=0, arr4[2]=0 ;}int arr4[3] = {0 נשים לב כי רק בעת האיתחול ניתן לתת ערך לכמה איברים יחד! כל נתינת ערך בהמשך הינה השמה ,ולא איתחול ,ולכן יבוצע על כל איבר בנפרד © Keren Kalif הגדרת הגודל והערכים:אתחול מערך : עבור המערכים הבאים int numbers[3] = {5, 3, 1}; char letters[3] = {‘m’, ‘A’, ‘k’}; int[]: numbers 5 3 1 char[]: letters ‘m’ ‘A’ ‘k’ : הזכרון יראה כך 1000 1004 1008 1012 1013 1014 48 © Keren Kalif אתחול מערך :הגדרת הערכים בלבד עבור המערך הבא: הזכרון יראה כך: נשים לב שאין צורך בהגדרת גודל המערך ,הקומפיילר יודע זאת לבד לפי כמות הערכים שאותחלו ;}double numbers[] = {5, 3.2, 1.1 1000 1008 1016 49 5.0 double[]: numbers 3.2 1.1 © Keren Kalif אתחול מערך :הגדרת גודל וחלק מהערכים כאשר נגדיר מערך באופן הבא: ;}int numbers[3] = {5 הזכרון יראה כך: כאשר מאתחלים את איברי המערך באופן חלקי ,שאר האיברים מקבלים ערך ( 0בניגוד לזבל שהיה אם לא היינו מאתחלים כלל) 1000 1004 1008 50 5 int[]: numbers 0 0 © Keren Kalif אתחול מערך :איפוס כל איברי המערך כאשר נגדיר מערך באופן הבא: ;}int numbers[3] = {0 הזכרון יראה כך: 1000 1004 1008 0 int[]:numbers 0 0 זהו מקרה פרטי של צורת האתחול הקודמת 51 © Keren Kalif sizeof הפונקציה היא פונקציה המקבלת משתנה או טיפוס ומחזירה אתsizeof מספר הבתים שהוא תופס בזיכרון void main() { int num; double d; char ch; printf("sizeof(int)=%d,\t sizeof(num)=%d\n", sizeof(int), sizeof(num)); printf("sizeof(double)=%d,\t sizeof(d)=%d\n", sizeof(double), sizeof(d)); printf("sizeof(char)=%d,\t sizeof(ch)=%d\n", sizeof(char), sizeof(ch)); } 52 © Keren Kalif חישוב גודל המערך SIZE*sizeof(type) גודל המערך בזיכרון הוא יהיו מקרים בהם נרצה לדעת בזמן ריצה כמה איברים יש : למשל, ולא תמיד הגודל מוגדר לנו,במערך void main() { int i, arr[] = {4, 3, 2, 7}; int size = sizeof(arr) / sizeof(int); printf("There are %d numbers in the array: ", size); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); } 53 © Keren Kalif חישוב גודל המערך ()2 ניתן לחשב את כמות האיברים במערך גם באופן הבא: )(void main { ;}int arr[] = {4, 3, 2, 7 )]sizeof(arr[0 ;)int size = sizeof(arr) / sizeof(int … } 54 דרך זו עדיפה ,שכן אם נשנה את טיפוס איברי המערך לא נצטרך לתקן את השורה המחשבת את הsize - © Keren Kalif השמת מערכים כדי לבצע השמה בין משתנים מאותו הסוג אנו משתמשים באופרטור = ;int x, y=5 ;x = y עבור מערכים לא ניתן לבצע זאת: השמה בין מערכים תבוצע בעזרת לולאה ,בה נעתיק איבר- איבר ;]int arr1[]={1,2,3}, arr2[3 ;arr2 = arr1 55 וזה בניגוד לשפת JAVAבה השמת מערכים משנה את ההפניה (מאחר ומערך ב JAVA -הוא אובייקט) © Keren Kalif דוגמא- השמת מערכים void main() { int arr1[] = {1,2,3}, arr2[3], i; // arr2 = arr1; // DOESN'T COMPILE!! printf("Elements in arr2 before: "); for (i=0 ; i < 3 ; i++) printf("%d ", arr2[i]); for (i=0 ; i < 3 ; i++) arr2[i] = arr1[i]; printf("\nElements in arr2 after: "); for (i=0 ; i < 3 ; i++) printf("%d ", arr2[i]); printf("\n"); } 56 © Keren Kalif מערך דו-מימדי בהגדרת מערך חד-מימדי מגדירים את כמות התאים בו (מספר העמודות): ]arr[3 ]arr[2 ]arr[1 בהגדרת מערך דו-מימדי נגדיר את כמות התאים בו ע"י ציון מספר השורות ומספר העמודות: arr arr arr arr arr arr arr arr ][0][3 ][1][3 57 ]arr[0 ;]int arr[4 ][0][2 ][1][2 ][0][1 ][1][1 ][0][0 ;]int arr[2][4 ][1][0 מערך דו-מימדי הוא למעשה מטריצה ,או ניתן להסתכל עליו כמערך של מערכים מערך דו-מימדי – דוגמא :קליטת ציונים לכמה כיתות והדפסתם -פלט © Keren Kalif 58 קליטת ציונים לכמה:מימדי – דוגמא-מערך דו כיתות והדפסתם © Keren Kalif #define NUM_CLASSES 3 #define STUDENTS_IN_CLASS 5 void main() { int grades[NUM_CLASSES][STUDENTS_IN_CLASS]; int i, j; printf("Please enter grades for students in %d classes:\n", NUM_CLASSES); for (i=0 ; i < NUM_CLASSES ; i++) { printf("Please enter grades for %d students in class #%d:", STUDENTS_IN_CLASS, i+1); for (j=0 ; j < STUDENTS_IN_CLASS ; j++) scanf("%d", &grades[i][j]); } printf("The grades in all classes:\n"); for (i=0 ; i < NUM_CLASSES ; i++) { printf("Class #%d: ", i+1); for (j=0 ; j < STUDENTS_IN_CLASS ; j++) printf("%d ", grades[i][j]); printf("\n"); } 59 © Keren Kalif מערך דו-מימדי – ייצוגו בזיכרון כמו מערך חד-מימדי ,גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם ,ומיד אח"כ איברי השורה השניה וכו' ;]int arr[2][4 arr arr arr arr ][0][3 ][0][2 ][0][1 ][0][0 arr arr arr arr ][1][3 60 1000 ??? arr[0][0] 1004 ??? arr[0][1] 1008 ??? arr[0][2] 1012 ??? arr[0][3] 1016 ??? arr[1][0] 1020 ??? arr[1][1] 1024 ??? arr[1][2] 1028 ??? arr[1][3] ][1][2 ][1][1 ][1][0 int[2][4]: ההתאמה בין המקום במערך הדו -מימדי למערך החד- מימדי היא COLUMNS * i + j © Keren Kalif מערך דו-מימדי – ייצוגו בזיכרון כמו מערך חד-מימדי ,גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם ,ומיד אח"כ איברי השורה השניה וכו' ;]int arr[2][4 arr arr arr arr ][0][3 ][0][2 ][0][1 ][0][0 arr arr arr arr ][1][3 61 1000 ??? arr[0][0] 1004 ??? arr[0][1] 1008 ??? arr[0][2] 1012 ??? arr[0][3] 1016 ??? arr[1][0] 1020 ??? arr[1][1] 1024 ??? arr[1][2] 1028 ??? arr[1][3] ][1][2 ][1][0 ][1][1 int[2][4]: ההתאמה בין המקום במערך הדו -מימדי למערך החד- מימדי היא COLUMNS * i + j למשל arr[0][3] :נמצא במקום 4*0+3 = 3 © Keren Kalif מערך דו-מימדי – ייצוגו בזיכרון כמו מערך חד-מימדי ,גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם ,ומיד אח"כ איברי השורה השניה וכו' ;]int arr[2][4 arr arr arr arr ][0][3 ][0][2 ][0][1 ][0][0 arr arr arr arr ][1][3 62 1000 ??? arr[0][0] 1004 ??? arr[0][1] 1008 ??? arr[0][2] 1012 ??? arr[0][3] 1016 ??? arr[1][0] 1020 ??? arr[1][1] 1024 ??? arr[1][2] 1028 ??? arr[1][3] ][1][2 int[2][4]: ][1][1 ][1][0 ההתאמה בין המקום במערך הדו -מימדי למערך החד- מימדי היא COLUMNS * i + j למשל arr[0][3] :נמצא במקום 4*0+3 = 3 למשל arr[1][2] :נמצא במקום 4*1+2 = 6 © Keren Kalif מערך דו-מימדי -איתחול ניתן לאתחל מערך דו-מימדי באחת מהדרכים הבאות: ;} }int arr1[2][3] = { {1,2,3}, {4,5,6 ;} }int arr2[][3] = { {1,2,3}, {4,5,6 ניתן לאתחל בלי ציון מספר השורות ,אבל תמיד חייבים לציין את מספר העמודות ;} }int arr3[2][3] = { {5,5 ;}int arr4[2][3] = {0 63 © Keren Kalif מימדי – חישוב מספר השורות-מערך דו #include <stdio.h> #define NUM_OF_COLS 3 void main() { int arr[][NUM_OF_COLS ] = { {1,2,3}, {4,5,6} }; int i, j; int numOfRows = sizeof(arr)/sizeof(int)/NUM_OF_COLS; for (i=0 ; i < numOfRows ; i++) { for (j=0 ; j < NUM_OF_COLS ; j++) printf("%d ", arr[i][j]); printf("\n"); } } 64 © Keren Kalif מערך רב-מימדי עד כה ראינו מערכים חד-מימדיים ומערכים דו-מימדיים ניתן להרחיב את ההגדרה לכל מספר סופי של מימדים למשל :מערך תלת –מימדי ;]int matrix[LENGTH][HEIGHT][DEPTH דוגמא לשימוש :נרצה לשמור ממוצע ציונים עבור 5בתי-ספר ,כאשר בכל בית-ספר יש 10כיתות ,ובכל כיתה 30סטודנטים: ;]double average[5][10][30 65 במקרה זה נשתמש בלולאה ,בתוך לולאה ,בתוך לולאה.. © Keren Kalif – מימדי-מערך רב #define SCHOOLS 3 #define CLASSES 2 הספר-דוגמאת נתוני בתי #define STUDENTS 4 void main() { float grades[SCHOOLS][CLASSES][STUDENTS] = { { {90, 100, 95, 88}, {87, 70, 90, 98} }, { {88, 75, 80, 60}, {55, 87, 90, 82} }, }; { {60, 91, 40, 95}, {77, 66, 88, 99} } int i, j, k; for (i=0 ; i < SCHOOLS ; i++) { printf("Classes in school #%d:\n", i+1); for (j=0 ; j < CLASSES ; j++) { printf(" Grades in class #%d: ", j+1); for (k=0 ; k < STUDENTS ; k++) printf("%.2f ", grades[i][j][k]); printf("\n"); } printf("\n"); } } 66 © Keren Kalif by value תזכורת למשמעות של העברה void incNumber(int x) { printf("In function: number before: %d\n", x); x++; printf("In function: number after: %d\n", x); { void main() } int num = 3; 4 3 2000 incNumber הזיכרון של int: x int: num ?? 3 1000 main הזיכרון של printf("In main: number before function: %d\n", num); incNumber(num); printf("In main: number after function: %d\n", num); { 67 © Keren Kalif דוגמא- מערכים כפרמטר לפונקציה void incArray(int arr[], int size) { int i; for (i=0 ; i < size ; i++) arr[i]++; } void printArray(int arr[], int size) { int i; for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); } void main() { int arr[] = {4,3,8}; int size = sizeof(arr)/sizeof(arr[0]); printf("Orig array: "); printArray(arr, size); incArray(arr, size); } printf("Array after increment: "); printArray(arr, size); 68 © Keren Kalif מערכים כפרמטר לפונקציה 69 ראינו כי מערך מתנהג באופן שונה מאשר משתנה מטיפוס בסיסי כאשר מעבירים אותו לפונקציה כאשר מעבירים מערך לפונקציה ,לא מועבר עותק של המערך ) ,(by valueאלא מועברת רק כתובת ההתחלה של המערך לכן כאשר מעבירים מערך לפונקציה ומשנים אותו ,השינוי משפיע על המערך המקורי (ולא על ההעתק) נסביר לעומק כאשר נלמד את השיעור על מצביעים © Keren Kalif העברת מספר האיברים במערך כפרמטר לפונקציה 70 ראינו כי כאשר שלחנו מערך לפונקציה העברנו גם את מספר האיברים שבו ,ולא התבססנו על ערך קבוע זאת כדי שהפונקציה תהיה מספיק כללית על-מנת שתבצע את העבודה על מערכים בגדלים שונים © Keren Kalif דוגמא איך פונקציה המקבלת מערך לא צריכה להיות שימו לב :הקבוע מוגדר באותיות גדולות .דגש בהמשך... #define SIZE 3 )][void printArray(int arr הפונקציה יודעת לטפל אך ורק במערכים בגודל הקבוע { ,SIZEולא תעשה את המתבקש עבור מערכים בגודל שונה ;int i )for (i=0 ; i < SIZE ; i++ ;)]printf("%d ", arr[i ;)"printf("\n { )(void main } ;}int arr1[SIZE] = {1,2,3}, arr2[5]={10,20,30,40,50 ;)" printf("arr1: ;)printArray(arr1 71 ;)" printf("arr2: ;)printArray(arr2 { © Keren Kalif דוגמא איך פונקציה המקבלת מערך כן צריכה להיות #define SIZE 3 SIZE והקבועsize הפרמטר:שימו לב .)case sensitive שונים (הקומפיילר הוא ...דגש בהמשך void printArray(int arr[], int size) { int i; הפונקציה מקבלת כפרמטר נוסף את כמות האיברים for (i=0 ; i < size; i++) ולכן יודעת לטפל במערך בכל גודל,שעליה להדפיס printf("%d ", arr[i]); printf("\n"); { void main() } int arr1[SIZE] = {1,2,3}, arr2[5]={10,20,30,40,50}; printf("arr1: "); printArray(arr1, SIZE); printf("arr2: "); printArray(arr2, 5); { 72 © Keren Kalif שימו לב :שגיאה נפוצה! מי שלא מקפיד על הגדרת קבועים באותיות גדולות עלול להיתקל בשגיאת הקומפילציה הבאה: בעקבות פקודת ה :define -בכל מקום שהקומפיילר רואה sizeהוא מחליף אותו בערך 3 )void printArray(int arr[], int size ולכן מה שהקומפיילר רואה בתור } הפרמטר השניint 3 : ;int i ו 3 -אינו שם תקף למשתנה )for (i=0 ; i < size ; i++ ;)]printf("%d ", arr[i ;)"printf("\n { {…} )(void main #define size 3 73 © Keren Kalif – העברת מטריצה לפונקציה דוגמא הפונקציה מספיק כללית כדי לבצע את העבודה עבור מטריצה עם כל מספר שורות #define COLS 5 void setMatrix(int mat[][COLS], int rows) } int i, j; for (i=0 ; i < rows ; i++) } for (j=0 ; j < COLS ; j++) mat[i][j] = i*10+j; { { void printMatrix(int mat[][COLS], int rows) } int i, j; for (i=0 ; i < rows ; i++) } for (j=0 ; j < COLS ; j++) printf("%4d", mat[i][j]); printf("\n"); { { void main() } int mat1[3][COLS]; int mat2[4][COLS]; setMatrix(mat1, 3); setMatrix(mat2, 4); printf("Matrix 1:\n"); printMatrix(mat1, 3); printf("Matrix 2:\n"); printMatrix(mat2, 4); { 74 © Keren Kalif העברת מטריצה לפונקציה – דוגמא (פלט) 75 © Keren Kalif העברת מטריצה לפונקציה גם כאשר מעבירים מטריצה לפונקציה ,עוברת כתובת ההתחלה בלבד ,ולכן שינוי המטריצה בפונקציה משנה את המטריצה המקורית כאשר מעבירים מטריצה לפונקציה ,ניתן לציין רק את כמות העמודות ולהשאיר את הסוגריים של השורות ריקים (ולכן במקום יש להעביר כפרמטר את מספר השורות) 76 נרצה להעביר את כמות השורות כדי שהפונקציה תהיה מספיק כללית לכל מטריצה עם אותו מספר עמודות בהמשך נראה שאפשר גם להעביר מטריצות שמספר העמודות בהן שונה סוגי משתנים לוקאלים ,גלובלים ,סטטים © Keren Kalif משתנים מקומיים 78 עד כה ראינו שכל הקוד שלנו מורכב מפונקציות המשתנים שבתוך כל פונקציה (גם אלו שהועברו כפרמטרים) נקראים משתנים מקומיים (לוקאליים) וטווח ההכרה שלהם הוא רק בפונקציה בה הם מוגדרים ערכו של משתנה לוקאלי נשמר כל עוד אנחנו בפונקציה ,והוא נמחק ביציאה ממנה משתנה לוקאלי מאוחסן במחסנית של הפונקציה בכל קריאה חדשה לפונקציה המשתנים מאותחלים מחדש ונמחקים בסיום ביצוע הפונקציה ערכו זבל במידה ולא אותחל © Keren Kalif משתנה סטטי הוא משתנה שערכו נשמר בין הקריאות השונות לפונקציה #include <stdio.h> כדי להגדיר משתנה סטטי int counter() נשתמש במילת המפתח { לפני טיפוס המשתנהstatic static int count = 0; משתנה סטטי מאותחל רק count++; בקריאה הראשונה לפונקציה return count; } משתנים סטטיים counter הזיכרון של main -הזיכרון של ה 3 2 1 0 counter::count = ? void main() data segment -זיכרון ה { printf("'counter' was called %d times\n", printf("'counter' was called %d times\n", printf("'counter' was called %d times\n", } counter() ); counter() ); counter() ); counter main ( line 3) 1) 2) מחסנית הקריאות 79 © Keren Kalif משתנים סטטיים 80 בדומה למשתנים מקומיים ,המשתנים הסטטיים מוכרים אך ורק בתוך הפונקציה אשר בה הם הוגדרו משך חייהם מרגע תחילת הריצה של התוכנית ועד סיום ריצת התוכנית מאחר ושטח הזיכרון של כל פונקציה נמחק עם היציאה ממנה, משתנים סטטיים נשמרים באזור זיכרון אחר הנקרא data- ,segmentהקיים לאורך כל חיי התוכנית ,והם משתחררים רק עם היציאה מהתוכנית © Keren Kalif משתנה גלובלי מוגדר בראש התוכנית )(אינו משויך לאף פונקציה משתנים גלובליים כל הפונקציות שכתובות בקובץ בו הוגדר יכולות לגשת אליו ולשנות את ערכו int global = 3; void incGlobal() 11 3 4 global = 10 { data segment -זיכרון ה global++; printf("In function: global=%d\n", global); } incGlobal הזיכרון של void main() { printf("At first, global=%d\n", global); main -הזיכרון של ה incGlobal(); printf("After function (1), global=%d\n", global); incGlobal global = 10; printf("In main after change global, global=%d\n", global); main incGlobal(); printf("After function (2), global=%d\n", global); מחסנית הקריאות } 81 © Keren Kalif משתנים גלובליים במידה ולא אותחל ערכו ,0ולא זבל משתנה גלובלי קיים לאורך כל חיי התוכנית השימוש בו הוא בעייתי ולכן נשמר למצבים מיוחדים בלבד 82 כל אחד יכול לשנות אותו ,מה שפוגע ברעיון של הסתרת המידע ושכל פונקציה מסתמכת רק על נתונים שהם פנימיים לה מאחר ואינו משויך לאף פונקציה ,הוא אינו נמצא על המחסנית, אלא על חלקת הזיכרון המשותפת לכל הפונקציות הdata- - segment © Keren Kalif השוואה בין סוגי המשתנים השונים משתנה מקומי (רגיל) משתנה גלובלי 83 משתנה סטטי היכן מוגדר בתוך הפונקציה מחוץ לפונקציות בתוך הפונקציה טווח ההכרה בפונקציה בה הוא מוגדר מנקודת הגדרתו ומטה בפונקציה בה הוא מוגדר מי יכול לגשת אליו רק הפונקציה בה הוא מוגדר כל מקום בקוד הנמצא מתחת רק הפונקציה בה הוא מוגדר להגדרתו מתי מאותחל בכל פעם כשנכנסים לפונקציה בתחילת ריצת התוכנית רק בפעם הראשונה שמגיעים אליו © Keren Kalif השוואה בין סוגי המשתנים השונים משתנה מקומי (רגיל) משתנה גלובלי 84 משתנה סטטי היכן מוגדר בתוך הפונקציה מחוץ לפונקציות בתוך הפונקציה טווח ההכרה בפונקציה בה הוא מוגדר מנקודת הגדרתו ומטה בפונקציה בה הוא מוגדר מי יכול לגשת אליו רק הפונקציה בה הוא מוגדר כל מקום בקוד הנמצא מתחת רק הפונקציה בה הוא מוגדר להגדרתו מתי מאותחל בכל פעם כשנכנסים לפונקציה בתחילת ריצת התוכנית רק בפעם הראשונה שמגיעים אליו © Keren Kalif ביחידה זו למדנו: 85 רקע לשפת C תוכנית ראשונה בשפת C הגדרת משתנים בשפת C קלט ופלט בשפת C פונקציות מערכים סוגי משתנים © Keren Kalif תרגול 86
© Copyright 2024