Download presentation
Presentation is loading. Please wait.
1
מ- JAVA ל- C קרן כליף
2
ביחידה זו נלמד: רקע לשפת C תוכנית ראשונה בשפת C הגדרת משתנים בשפת C
פונקציות מערכים סוגי משתנים
3
רקע כללי לשפת C שפת C תוכננה לראשונה בשנים על ידי דניס ריצ'י ממעבדות בל על מערכות UNIX הצורך בשפה עלה לשם כתיבת מערכת הפעלה רוב הגרעיןKernel) ) של מערכת ההפעלה לינוקס כתוב ב-C , כמו גם הרבה תוכנות אחרות המהוות את מערכת ההפעלה GNU/Linux באותה עת הייתה קיימת שפת B ובאמצעותה נכתבה שפת C בתחילה מקור השם של השפה הוא באותה שפת B תוך התקדמות אות אחת באלפבית עם התפתחות שפתC , הקומפיילר בו מקמפלים את השפה נכתב אף הוא בשפת C
4
עקרון מנחה של שפת C - יעילות
כל פעולה בשפה מתבטאת בפעולה בודדת או במספר מועט של פעולות בשפת סף לכן אין בשפה: סוגי מבנים מורכבים כגון מחרוזות בדיקות סמויות בגישה למערכים מערכת הטיפוסים שלה אינה בטוחה מסיבה זו היא גם כוללת לא מעט אפשרויות לשגיאות בזמן ריצה, אשר אינן מאותרות ע"י הקומפיילר בזמן קומפילציה, למשל: פנייה למשתנה שלא הוקצה פנייה לערך לא מאותחל בזיכרון (ה- JAVA מתקבלת שגיאת קומפילציה) הנחת העבודה של מתכנני השפה (והתקנים המאוחרים יותר) היא כי "המתכנת מבין מה הוא עושה"
5
שפת C כשפה פרוצדורלית שפה פרוצדורלית:
הרעיון שהקוד מחולק לפרוצדורות (פונקציות) שכל אחת אחראית על חלק מסויים בתוכנית בכל פרוצדורה יש את המשתנים המוגדרים בה ומוכרים אך ורק לה (נקראים גם משתנים לוקאליים או מקומיים) ישנם גם משתנים גלובליים אשר משותפים לכל הפרוצדורות בניגוד לשפה מונחת-עצמים, ישנם רכיבים בשפה שאינם חלק מאובייקטים (למשל ה- main אינו נמצא בתוך מחלקה) האובייקטים רק מכילים מידע ואינם יודעים לבצע פעולות לצורך השוואה,JAVA היא שפה מכוונת עצמים
6
טיפוסים וקלט ופלט בשפת C
הפקודות printf ו- scanf, פורמטי הדפסה שונים
7
כתיבת תוכנית ראשונה ב- C
main היא פונקציה המורצת עם הרצת התוכנית בכל פרוייקט תהייה פונקצית main אחת בדיוק ספריה המכילה פקודות קלט ופלט #include <stdio.h> void main() { … } נשים לב לסגנון: הסוגרים המסולסלים הפותחים בלוק נמצאים בשורה חדשה
8
דגש כדי שאני לא אתעצבן!
9
טיפוסי נתונים ב- C Definition Size Range char תו (‘a’, ‘A’, ‘6’,’!’)
1 byte -27…27-1 ( ) int מספר שלם (3,7,8234-) 4 bytes -231…231-1 float מספר ממשי (3.6, 89-, 5.2) double 8 bytes אין boolean
10
טיפוסי נתונים ב- C (2) Definition Size Range short 2 bytes long
מספר שלם 2 bytes -215… 215-1 (-32,768…32,767) long 8 bytes -263…263-1 unsigned int מספר שלם חיובי 4 bytes 0…232-1 unsigned short 0…216-1 (0….65535) unsigned long 0…264-1 unsigned char תו חיובי בלבד 1 byte 0…28-1 (0…255(
11
הגדרת משתנים בתוכנית למה ההפרש בין הכתובות שונה? 4 char מורכב מ- 1byte
void main() { int n1 = 4; double n2 = 5.2; char ch = ‘f’; short n3 = 7, n4 = 77; unsigned int n5 = ; int n6 = -11; } למה ההפרש בין הכתובות שונה? כי כל סוג משתנה תופס כמות שונה של בייטים int מורכב מ- 4 byte int: n1 4 1000 double: n2 5.2 1004 char: ch ‘f’ 1012 short: n3 7 1013 short: n4 77 1015 uint: n5 234234 1017 int: n6 -11 1021 בשפת C ערכו של משתנה שאינו מאותחל הוא זבל, וניתן לקרוא את ערכו, אך יתכן ונעוף בזמן ריצה (בניגוד לשפת JAVA שתתריע על כך בז מן קומפילציה)
12
הדפסה למסך אחת הפקודות הנמצאות בספריה stdio.h היא הדפסה למסך:
#include <stdio.h> void main() { printf(“Hello World!”); }
13
מציין שבחלק זה במחרוזת יהיה מספר דצימלי, במקרה זה 6
הדפסה למסך (2) דרך נוספת לכתוב תוכנית זו: #include <stdio.h> void main() { printf(“6 is a number”); } הרצה של תוכנית זו תדפיס למסך את המחרוזת 6 is a number #include <stdio.h> void main() { printf(“%d is a number”, 6); } מציין שבחלק זה במחרוזת יהיה מספר דצימלי, במקרה זה 6
14
קבלת קלט מהמשתמש פקודה נוספת הנמצאת בספריה stdio.h היא קליטת נתון מהמשתמש: מה זה x? x הוא השם של המקום שבו נאכסן את הערך המתקבל מהמשתמש & מציין לנו איפה נמצא מקום זה מה זה %d? ציון שהנתון אותו אנו קוראים הוא מספר דצימלי #include <stdio.h> void main() { int x; scanf(“%d”, &x); } פירוש הפקודה scanf: אחסן את הערך שיתקבל מהמשתמש במשתנה הנקרא x
15
פורמט קליטה והדפסה לטיפוסים השונים
Data Type Format Explaination int %d Decimal short long %ld Long Decimal char %c Character float %f Float double %lf Long Float unsigned %u Unsigned
16
בעיה: התוכנית "מדלגת" על פקודת הקלט
void main() } int n1; char c1; printf("Please enter a number: "); scanf("%d", &n1); printf("Please enter a char: "); scanf("%c", &c1); printf("n1= |%d| c1=|%c|\n", n1, c1); { הקלדתי 5 ואז ENTER. לתוך n1 נכנס 5, ולתוך ch1 נכנס האנטר. ENTER הוא גם תו, ומאחר והוא היה ב- buffer הוא נקלט לתוך ch1 כאשר אנחנו קולטים נתונים בפקודות נפרדות, נרצה לנקות את ה- buffer בין הפעולות השונות.
17
הפתרון: הפקודה 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 בין הקריאות השונות. הקלדתי 7, ENTER ו- f. לתוך n1 נכנס 7, ואז ניקיתי את ה- ENTER ה- buffer באמצעות הפקודה flushall. התו f נכנס לתוך ch1.
18
הדפסת וקליטת משתנים מטיפוסים שונים
#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); }
19
פורמט הדפסה: ישור הטקסט
כאשר משתמשם בפקודה printf לצורך הדפסה ניתן לשלוט על כמות התווים שכל חלק יתפוס, וכן על כיוון היישור שלו: לאחר ה- % נכתוב מספר המציין כמה מקומות ערך המשתנה יתפוס בתצוגה ברירת המחדל היא הצמדת הטקסט לימין כדי להצמיד לשמאל נוסיף מינוס 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); יישור הטקסט לימין יישור הטקסט לשמאל
20
דוגמא: לוח הכפל void main() } int rows, cols, i, j, res;
printf("Enter rows and cols for multiplication-board: "); scanf("%d %d", &rows, &cols); // print first line printf("\n%4c|", ' '); for (i=1 ; i <= cols ; i++) printf("%4d|", i); printf("\n"); for (i=1 ; i <= (cols+1)*5 ; i++) printf("-"); for (i=1 ; i <= rows ; i++) // print the left column for (j=1 ; j <= cols ; j++) printf("%4d|", i*j); {
21
יישור בגודל משתנה ניתן גם שכמות התווים שכל חלק יתפוס בהדפסה תלקח ממשתנה לאחר ה- % נשים *, וברשימת הפרמטרים נשים עבורה את הערך המבוקש 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);
22
הדפסת 0 מוביל ניתן להדפיס נתונים עם הדפסת 0 מוביל
נוסיף 0 לאחר ה- % למשל לצורך הדפסת ת.ז. תמיד עם 9 ספרות void main() } int id; printf("Please enter your id: "); scanf("%d", &id); printf("Your id is %09d\n", id); {
23
הגבלת כמות התווים הנכנסים לתוך משתנה
ניתן להגביל את כמות התווים הנקלטים לתוך משתנה בפקודת ה- scanf לאחר ה- % נגביל את כמות התווים למשל: קליטת מספר קטן מ- 1000 void main() { int num; printf("Please enter a number less than 1000: "); scanf("%3d", &num); printf("The number is %d\n", num);
24
קליטת נתונים תוך הפרדתם
ניתן לקלוט מהמשתמש ערך (בד"כ מספר או מחרוזת) ובפועל לקלוט את הערך ליותר ממשתנה אחד 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);
25
שגיאת קומפילציה: המשתנים חייבים להיות מוגדרים בתחילת בלוק!
הגדרת משתנים בתוכנית שגיאת קומפילציה: המשתנים חייבים להיות מוגדרים בתחילת בלוק!
26
שורה זו אינה מסתיימת ב- ;
#define אפשר להגדיר קבוע באופן הבא: #define TAXES 0.17 את הפקודה #define רצוי לכתוב בתחילת התוכנית, לפני הפונקציה main ובכך לקבוע לערך תחום הכרה ציבורי אפשר לכתוב את הפקודה בכל מקום בתוכנית. הקבוע יהיה מוכר מאותו מקום ועד לסוף התוכנית בכל פעם שהקומפיילר יפגוש את השם TAXES) בדוגמא) הוא יחליפו בערך (0.17) שורה זו אינה מסתיימת ב- ;
27
הגדרת קבועים ע"י define קבוע שהוגדר ע"י define אינו משתנה ולכן אינו תופס מקום בזכרון. הסבר מפורט בהמשך. #include <stdio.h> #define TAXES 0.17 void main() { float price; float totalPrice; 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); } float: price 1000 float: totalPrice 1004 1008 %% כדי להציג את התו % ציון להצגת רק 2 ספרות אחרי הנקודה העשרונית
28
פונקציות הפרדה בין הצהרה למימוש, יצירת קובץ ספריה, תהליך הקומפילציה, שגיאות קומפילציה לעומת שגיאות לינקר, יצירת מספרים אקראיים
29
תוכנית שלמה עם פונקציה – איך זה בשפת 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); = power(base, exponent); printf("%d^%d=%d\n", base, exponent, result); int:base ??? 1000 int: exponent 1004 int: result 1008 int:base 2 1000 int: exponent 3 1004 int: result 8 1008 int:base 2 1000 int: exponent 3 1004 int: result ??? 1008 הזיכרון של ה- main int: a 2 2000 int: b 3 2004 int: i … 2008 int: result 1 2012 int: a 2 2000 int: b 3 2004 int: i … 2008 int: result 8 2012 int: a 2 2000 int: b 3 2004 int: i ??? 2008 int: result 2012 הזיכרון של power power result main ( line 4) מחסנית הקריאות
30
הפרדה בין הצהרות למימוש
#include <stdio.h> // prototypes 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); } int power(int base, int exponent) int i, result=1; for (i=0 ; i < exponent ; i++) result *= base; return result; int power(int base, int exponent); int power(int, int); בשורת ההצהרה לא חייבים לציין את שמות המשתנים את ההצהרות נרשום לפני ה- main, ובסיום כל הצהרה ; חלק זה נקרא prototype (אב-טיפוס) את המימושים נכתוב מתחת ל- main ההפרדה היא מאחר ומעניין אותנו איזה פונקציות יש בתוכנית, ופחות איך הן מבצעות את העבודה חתימת הפונקציה בהגדרה בראש הקובץ ובמימוש חייבות להיות זהות
31
פונקציות ספריה כל תוכנית בשפת תכנות עילית, ובפרט שפת C, מורכבת מאוסף של פונקציות קיימות פונקציות ספריה בשפה אותן ניתן להכליל בתוכנית ולקרוא להן בכל מקום בו נרצה ולהשתמש בהן כמה פעמים שנרצה למשל כאשר עושים: #include <stdio.h> ניתן להשתמש בפונקציות המוגדרות בספריה זו בכל מיני מקומות בקוד (למשל printf, scanf) גם אנו יכולים לכתוב פונקציות או ספריות של פונקציות, שאותן ניתן להכליל ולהשתמש בהן כרצוננו
32
יצירת קובץ ספריה עד כה השתמשנו בפונקציות שקיבלנו משפת C
כל פונקציה כזו נכתבה בספריה שאליה ביצענו include למשל כדי להשתמש בפונקציות של מחרוזות נכלול את #include <string.h> כדי לייצר ספריה משלנו נייצר 2 קבצים חדשים: <file_name>.h הוא קובץ אשר יכלול את ה- prototypes של הפונקציות, ואליו אח"כ נעשה include מהקובץ שירצה להשתמש בפונקציות המוגדרות בו <file_name>.c הוא קובץ שיכיל include לקובץ ה- header התואם ויממש את הפונקציות המוגדרות בו include לספריה שכתבנו יהיו בתוך "" (גרשיים) ולא בתוך <>
33
דוגמא – ספריה המטפלת בתווים
34
דוגמא – ספריה המטפלת בתווים (2)
35
ספריות שלנו - סיכום ניתן לכתוב את כל הקוד בקובץ אחד, אבל אם אנחנו כותבים קוד כללי, שיתכן ויהיה שימושי גם במקומות אחרים, נעדיף לחלק את הקוד לקובץ ספריה נפרד מי שירצה להשתמש בספריה שלנו, יתעניין מה יש לה להציע, ולא איך היא מבצעת את הפעולות, וכך הוא יוכל להסתכל בקובץ header בלבד בו יש ריכוז של כל הפונקציות מכירת הספריה ללקוח תסתכם בקובץ ה- h ובקובץ בינארי שהוא תוצר של הקומפילציה, וכך לא נחשוף את ה"איך"
36
תהליך הפיכת תוכנית C לשפת מכונה
Editor התוכנית נכתבת בעורך טקסטואלי ונכתבת לדיסק Disk קדם המעבד עובר על הקוד ומבצע החלפות נחוצות (כל הפקודות המתחילות ב- #, כמו define ו- include Preprocessor Disk עבור כל קובץ c, הקומפילר יוצר קובץ obj בשפת מכונה ושומר אותו על הדיסק. בשלב זה רק נבדקת תקינות הסינטקס בפונקציות. Disk Compiler ה- linker קושר את קובץ ה- obj עם הספריות, ויוצר קובץ exe המוכן להרצה ונשמר בדיסק. כלומר, מקשר בין קריאה לפונקציה, למימוש שלה. Linker Disk Primary Memory בעת ההרצה, טוענים את התוכנית מהדיסק לזיכרון הראשי Loader . Disk Primary Memory עם הרצת התוכנית, ה- cpu עובר על כל פקודה ומריץ אותה CPU .
37
דוגמא לשגיאת קומפילציה
#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 במידה ויש שגיאות קומפילציה, הקומפיילר אינו ממשיך לתהליך לינקר
38
דוגמא לשגיאת לינקר #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
39
יצירת מספרים אקראיים בשפת C קיימת הפונקציה rand() אשר מגרילה מספר
טווח הערכים שיוגרלו הינו בין 0 לקבוע MAX_RAND קבוע זה מוגדר בספריה 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");
40
יצירת מספרים אקראיים (2)
rand פועלת עפ"י אלגוריתם קבוע המתבסס על מספר התחלתי כלשהו (נקרא seed number), ולכן תמיד בכל הרצה תחזיר ערכים זהים כדי ש- rand באמת תחזיר מספרים אקראיים, כלומר תתבסס על מספר התחלתי שונה בכל פעם, יש להפעיל את הפונקציה srand עם ערך התחלתי המייצג את הזמן הנוכחי (והוא אכן יחודי בכל הרצה)
41
יצירת מספרים אקראיים (3)
#include <stdio.h> #include <stdlib.h> // for 'RAND_MAX' #include <time.h> // for 'time' void main() } int i; 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"); { time(NULL) מחזירה מספר המייצג את הזמן הנוכחי (מספר השניות שעברו מאז ה )
42
תזכורת: יצירת מספרים אקראיים בטווח מסוים
#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"); { פעולת %6 תחזיר לנו ערכים בין 0-5, ומאחר ואנחנו רוצים בטווח בין 1-6 הוספנו 1..
43
המנון חשוב!
44
מערכים הגדרת מערכים, מערכים בזכרון, גודל של מערך, אתחול מערך, השמת מערכים, מערך רב-מימדי, העברת מערך לפונקציה
45
הסוגריים [ ] יהיו צמודים לשם המשתנה
מערכים בשפת C מערך הוא אוסף של משתנים מאותו טיפוס המוגדרים על ה- stack בניגוד לשפת JAVA בה מערך הוא אובייקט המוקצה ונמצא על ה- heap לכן יש להגדיר את גודל המערך עם יצירתו הגודל צריך להיות ידוע בזמן קומפילציה (לכן אינו יכול להיות משתנה) הסוגריים [ ] יהיו צמודים לשם המשתנה void main() { int arr1[3]; }
46
דוגמא למערך בזיכרון #define SIZE 3 void main() {
int: arr1[] ??? 1000 1004 1008 int: x 4 1012 int: arr2[] 1016 1020 1024 #define SIZE 3 void main() { int arr1[SIZE], x=4, arr2[SIZE]; } ערכם של איברי המערך הוא זבל, כמו כל משתנה שלא אותחל גודל המערך בזיכרון: SIZE*sizeof(type) ובדוגמא זו: 3*sizeof(int) = 3*4 = 12
47
גודל של מערך גודל המערך יוחזק במשתנה נפרד
בניגוד לשפת 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: "); printf("%d ", arr[i]); printf("\n"); }
48
אתחול מערך כאשר מגדירים מערך ערכי איבריו הוא זבל
ניתן לאתחל את איברי המערך באחת מהדרכים הבאות: int arr1[3] = {5, 3, 1}; int arr2[] = {5, 3, 1}; int arr3[3] = {5}; int arr4[3] = {0}; נשים לב כי רק בעת האיתחול ניתן לתת ערך לכמה איברים יחד! כל נתינת ערך בהמשך הינה השמה, ולא איתחול, ולכן יבוצע על כל איבר בנפרד //arr1[0]=5, arr1[1]=3, arr1[2]=1 //arr2[0]=5, arr2[1]=3, arr2[2]= and the size of the array is 3! //arr3[0]=5, arr3[1]=0, arr3[2]=0 //arr4[0]=0, arr4[1]=0, arr4[2]=0
49
אתחול מערך: הגדרת הגודל והערכים
עבור המערכים הבאים: int numbers[3] = {5, 3, 1}; char letters[3] = {‘m’, ‘A’, ‘k’}; הזכרון יראה כך: int[]: numbers 5 1000 3 1004 1 1008 char[]: letters ‘m’ 1012 ‘A’ 1013 ‘k’ 1014
50
אתחול מערך: הגדרת הערכים בלבד
עבור המערך הבא: double numbers[] = {5, 3.2, 1.1}; הזכרון יראה כך: נשים לב שאין צורך בהגדרת גודל המערך, הקומפיילר יודע זאת לבד לפי כמות הערכים שאותחלו double[]: numbers 5.0 1000 3.2 1008 1.1 1016
51
אתחול מערך: הגדרת גודל וחלק מהערכים
כאשר נגדיר מערך באופן הבא: int numbers[3] = {5}; הזכרון יראה כך: כאשר מאתחלים את איברי המערך באופן חלקי, שאר האיברים מקבלים ערך 0 (בניגוד לזבל שהיה אם לא היינו מאתחלים כלל) int[]: numbers 5 1000 1004 1008
52
אתחול מערך: איפוס כל איברי המערך
כאשר נגדיר מערך באופן הבא: int numbers[3] = {0}; הזכרון יראה כך: זהו מקרה פרטי של צורת האתחול הקודמת int[]:numbers 1000 1004 1008
53
הפונקציה 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)); }
54
חישוב גודל המערך גודל המערך בזיכרון הוא 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"); }
55
חישוב גודל המערך (2) ניתן לחשב את כמות האיברים במערך גם באופן הבא:
void main() { int arr[] = {4, 3, 2, 7}; int size = sizeof(arr) / sizeof(int); … } דרך זו עדיפה, שכן אם נשנה את טיפוס איברי המערך לא נצטרך לתקן את השורה המחשבת את ה- size sizeof(arr[0])
56
השמת מערכים כדי לבצע השמה בין משתנים מאותו הסוג אנו משתמשים באופרטור =
כדי לבצע השמה בין משתנים מאותו הסוג אנו משתמשים באופרטור = int x, y=5; x = y; עבור מערכים לא ניתן לבצע זאת: int arr1[]={1,2,3}, arr2[3]; arr2 = arr1; השמה בין מערכים תבוצע בעזרת לולאה, בה נעתיק איבר- איבר וזה בניגוד לשפת JAVA בה השמת מערכים משנה את ההפניה (מאחר ומערך ב- JAVA הוא אובייקט)
57
השמת מערכים - דוגמא 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]); arr2[i] = arr1[i]; printf("\nElements in arr2 after: "); printf("\n"); }
58
מערך דו-מימדי בהגדרת מערך חד-מימדי מגדירים את כמות התאים בו (מספר העמודות): int arr[4]; בהגדרת מערך דו-מימדי נגדיר את כמות התאים בו ע"י ציון מספר השורות ומספר העמודות: int arr[2][4]; מערך דו-מימדי הוא למעשה מטריצה, או ניתן להסתכל עליו כמערך של מערכים arr[0] arr[1] arr[2] arr[3] arr [0][0] arr [0][1] arr [0][2] arr [0][3] arr [1][0] arr [1][1] arr [1][2] arr [1][3]
59
מערך דו-מימדי – דוגמא: קליטת ציונים לכמה כיתות והדפסתם - פלט
60
מערך דו-מימדי – דוגמא: קליטת ציונים לכמה כיתות והדפסתם
#define NUM_CLASSES #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"); printf("Class #%d: ", i+1); printf("%d ", grades[i][j]); printf("\n");
61
מערך דו-מימדי – ייצוגו בזיכרון
כמו מערך חד-מימדי, גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם, ומיד אח"כ איברי השורה השניה וכו' int arr[2][4]; arr [0][0] arr [0][1] arr [0][2] arr [0][3] arr [1][0] arr [1][1] arr [1][2] arr [1][3] ההתאמה בין המקום במערך הדו -מימדי למערך החד-מימדי היא COLUMNS * i + j int[2][4]: arr[0][0] ??? 1000 arr[0][1] 1004 arr[0][2] 1008 arr[0][3] 1012 arr[1][0] 1016 arr[1][1] 1020 arr[1][2] 1024 arr[1][3] 1028
62
מערך דו-מימדי – ייצוגו בזיכרון
כמו מערך חד-מימדי, גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם, ומיד אח"כ איברי השורה השניה וכו' int arr[2][4]; arr [0][0] arr [0][1] arr [0][2] arr [0][3] arr [1][0] arr [1][1] arr [1][2] arr [1][3] ההתאמה בין המקום במערך הדו -מימדי למערך החד-מימדי היא COLUMNS * i + j למשל: arr[0][3] נמצא במקום 4*0+3 = 3 int[2][4]: arr[0][0] ??? 1000 arr[0][1] 1004 arr[0][2] 1008 arr[0][3] 1012 arr[1][0] 1016 arr[1][1] 1020 arr[1][2] 1024 arr[1][3] 1028
63
מערך דו-מימדי – ייצוגו בזיכרון
כמו מערך חד-מימדי, גם מערך דו-מימדי נשמר בזיכרון ברצף, כאשר איברי השורה הראשונה נשמרים קודם, ומיד אח"כ איברי השורה השניה וכו' int arr[2][4]; arr [0][0] arr [0][1] arr [0][2] arr [0][3] arr [1][0] arr [1][1] arr [1][2] arr [1][3] ההתאמה בין המקום במערך הדו -מימדי למערך החד-מימדי היא COLUMNS * i + j למשל: arr[0][3] נמצא במקום 4*0+3 = 3 למשל: arr[1][2] נמצא במקום 4*1+2 = 6 int[2][4]: arr[0][0] ??? 1000 arr[0][1] 1004 arr[0][2] 1008 arr[0][3] 1012 arr[1][0] 1016 arr[1][1] 1020 arr[1][2] 1024 arr[1][3] 1028
64
מערך דו-מימדי - איתחול ניתן לאתחל מערך דו-מימדי באחת מהדרכים הבאות:
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}; ניתן לאתחל בלי ציון מספר השורות, אבל תמיד חייבים לציין את מספר העמודות
65
מערך דו-מימדי – חישוב מספר השורות
#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"); }
66
מערך רב-מימדי עד כה ראינו מערכים חד-מימדיים ומערכים דו-מימדיים
ניתן להרחיב את ההגדרה לכל מספר סופי של מימדים למשל: מערך תלת –מימדי int matrix[LENGTH][HEIGHT][DEPTH]; דוגמא לשימוש: נרצה לשמור ממוצע ציונים עבור 5 בתי-ספר, כאשר בכל בית-ספר יש 10 כיתות, ובכל כיתה 30 סטודנטים: double average[5][10][30]; במקרה זה נשתמש בלולאה, בתוך לולאה, בתוך לולאה..
67
מערך רב-מימדי – דוגמאת נתוני בתי- הספר
מערך רב-מימדי – דוגמאת נתוני בתי- הספר #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"); }
68
תזכורת למשמעות של העברה 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; printf("In main: number before function: %d\n", num); incNumber(num); printf("In main: number after function: %d\n", num); int: x 3 2000 int: x 4 2000 הזיכרון של incNumber int: num ?? 1000 int: num 3 1000 הזיכרון של main
69
מערכים כפרמטר לפונקציה - דוגמא
void incArray(int arr[], int size) { int i; for (i=0 ; i < size ; i++) arr[i]++; } void printArray(int arr[], int size) 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: "); }
70
מערכים כפרמטר לפונקציה
ראינו כי מערך מתנהג באופן שונה מאשר משתנה מטיפוס בסיסי כאשר מעבירים אותו לפונקציה כאשר מעבירים מערך לפונקציה, לא מועבר עותק של המערך (by value), אלא מועברת רק כתובת ההתחלה של המערך לכן כאשר מעבירים מערך לפונקציה ומשנים אותו, השינוי משפיע על המערך המקורי (ולא על ההעתק) נסביר לעומק כאשר נלמד את השיעור על מצביעים
71
העברת מספר האיברים במערך כפרמטר לפונקציה
ראינו כי כאשר שלחנו מערך לפונקציה העברנו גם את מספר האיברים שבו, ולא התבססנו על ערך קבוע זאת כדי שהפונקציה תהיה מספיק כללית על-מנת שתבצע את העבודה על מערכים בגדלים שונים
72
דוגמא איך פונקציה המקבלת מערך לא צריכה להיות
#define SIZE 3 void printArray(int arr[]) { 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); printf("arr2: "); printArray(arr2); שימו לב: הקבוע מוגדר באותיות גדולות. דגש בהמשך... הפונקציה יודעת לטפל אך ורק במערכים בגודל הקבוע SIZE, ולא תעשה את המתבקש עבור מערכים בגודל שונה
73
דוגמא איך פונקציה המקבלת מערך כן צריכה להיות
דוגמא איך פונקציה המקבלת מערך כן צריכה להיות #define SIZE 3 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); שימו לב: הפרמטר size והקבוע SIZE שונים (הקומפיילר הוא case sensitive). דגש בהמשך... הפונקציה מקבלת כפרמטר נוסף את כמות האיברים שעליה להדפיס, ולכן יודעת לטפל במערך בכל גודל
74
ולכן מה שהקומפיילר רואה בתור הפרמטר השני:int 3
שימו לב: שגיאה נפוצה! מי שלא מקפיד על הגדרת קבועים באותיות גדולות עלול להיתקל בשגיאת הקומפילציה הבאה: בעקבות פקודת ה- define: בכל מקום שהקומפיילר רואה size הוא מחליף אותו בערך 3 #define size 3 void printArray(int arr[], int size) } int i; for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); { void main() }…{ ולכן מה שהקומפיילר רואה בתור הפרמטר השני:int 3 ו- 3 אינו שם תקף למשתנה
75
העברת מטריצה לפונקציה – דוגמא
העברת מטריצה לפונקציה – דוגמא הפונקציה מספיק כללית כדי לבצע את העבודה עבור מטריצה עם כל מספר שורות #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) 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); {
76
העברת מטריצה לפונקציה – דוגמא (פלט)
העברת מטריצה לפונקציה – דוגמא (פלט)
77
העברת מטריצה לפונקציה גם כאשר מעבירים מטריצה לפונקציה, עוברת כתובת ההתחלה בלבד, ולכן שינוי המטריצה בפונקציה משנה את המטריצה המקורית כאשר מעבירים מטריצה לפונקציה, ניתן לציין רק את כמות העמודות ולהשאיר את הסוגריים של השורות ריקים (ולכן במקום יש להעביר כפרמטר את מספר השורות) נרצה להעביר את כמות השורות כדי שהפונקציה תהיה מספיק כללית לכל מטריצה עם אותו מספר עמודות בהמשך נראה שאפשר גם להעביר מטריצות שמספר העמודות בהן שונה
78
סוגי משתנים לוקאלים, גלובלים, סטטים
79
משתנים מקומיים עד כה ראינו שכל הקוד שלנו מורכב מפונקציות
המשתנים שבתוך כל פונקציה (גם אלו שהועברו כפרמטרים) נקראים משתנים מקומיים (לוקאליים) וטווח ההכרה שלהם הוא רק בפונקציה בה הם מוגדרים ערכו של משתנה לוקאלי נשמר כל עוד אנחנו בפונקציה, והוא נמחק ביציאה ממנה משתנה לוקאלי מאוחסן במחסנית של הפונקציה בכל קריאה חדשה לפונקציה המשתנים מאותחלים מחדש ונמחקים בסיום ביצוע הפונקציה ערכו זבל במידה ולא אותחל
80
משתנים סטטיים משתנה סטטי הוא משתנה שערכו נשמר בין הקריאות השונות לפונקציה #include <stdio.h> int counter() { static int count = 0; count++; return count; } void main() printf("'counter' was called %d times\n", ); כדי להגדיר משתנה סטטי נשתמש במילת המפתח static לפני טיפוס המשתנה הזיכרון של counter משתנה סטטי מאותחל רק בקריאה הראשונה לפונקציה הזיכרון של ה- main counter::count = ? 2 3 1 זיכרון ה- data segment counter() counter() counter() counter main ( line 1) main ( line 2) main ( line 3) מחסנית הקריאות
81
משתנים סטטיים בדומה למשתנים מקומיים, המשתנים הסטטיים מוכרים אך ורק בתוך הפונקציה אשר בה הם הוגדרו משך חייהם מרגע תחילת הריצה של התוכנית ועד סיום ריצת התוכנית מאחר ושטח הזיכרון של כל פונקציה נמחק עם היציאה ממנה, משתנים סטטיים נשמרים באזור זיכרון אחר הנקרא data- segment, הקיים לאורך כל חיי התוכנית, והם משתחררים רק עם היציאה מהתוכנית
82
משתנים גלובליים משתנה גלובלי מוגדר בראש התוכנית (אינו משויך לאף פונקציה) כל הפונקציות שכתובות בקובץ בו הוגדר יכולות לגשת אליו ולשנות את ערכו int global = 3; void incGlobal() { global++; printf("In function: global=%d\n", global); } void main() printf("At first, global=%d\n", global); incGlobal(); printf("After function (1), global=%d\n", global); global = 10; printf("In main after change global, global=%d\n", global); printf("After function (2), global=%d\n", global); global = 11 10 4 3 זיכרון ה- data segment הזיכרון של incGlobal הזיכרון של ה- main incGlobal main מחסנית הקריאות
83
משתנים גלובליים במידה ולא אותחל ערכו 0, ולא זבל
משתנה גלובלי קיים לאורך כל חיי התוכנית השימוש בו הוא בעייתי ולכן נשמר למצבים מיוחדים בלבד כל אחד יכול לשנות אותו, מה שפוגע ברעיון של הסתרת המידע ושכל פונקציה מסתמכת רק על נתונים שהם פנימיים לה מאחר ואינו משויך לאף פונקציה, הוא אינו נמצא על המחסנית, אלא על חלקת הזיכרון המשותפת לכל הפונקציות ה- data- segment
84
השוואה בין סוגי המשתנים השונים
משתנה סטטי משתנה גלובלי משתנה מקומי (רגיל) בתוך הפונקציה מחוץ לפונקציות היכן מוגדר בפונקציה בה הוא מוגדר מנקודת הגדרתו ומטה טווח ההכרה רק הפונקציה בה הוא מוגדר כל מקום בקוד הנמצא מתחת להגדרתו מי יכול לגשת אליו רק בפעם הראשונה שמגיעים אליו בתחילת ריצת התוכנית בכל פעם כשנכנסים לפונקציה מתי מאותחל
85
השוואה בין סוגי המשתנים השונים
משתנה סטטי משתנה גלובלי משתנה מקומי (רגיל) בתוך הפונקציה מחוץ לפונקציות היכן מוגדר בפונקציה בה הוא מוגדר מנקודת הגדרתו ומטה טווח ההכרה רק הפונקציה בה הוא מוגדר כל מקום בקוד הנמצא מתחת להגדרתו מי יכול לגשת אליו רק בפעם הראשונה שמגיעים אליו בתחילת ריצת התוכנית בכל פעם כשנכנסים לפונקציה מתי מאותחל
86
ביחידה זו למדנו: רקע לשפת C תוכנית ראשונה בשפת C הגדרת משתנים בשפת C
פונקציות מערכים סוגי משתנים
87
תרגול
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.