Download presentation
Presentation is loading. Please wait.
Published byΣπύρος Σπυρόπουλος Modified over 6 years ago
1
כתיבת מאקרו הפקודה assert מצביעים לפונקציות
מכללת אורט כפר-סבא תכנות מערכות בשפת C כתיבת מאקרו הפקודה assert מצביעים לפונקציות אורי וולטמן
2
חידה לחימום ברשותך עשר שקיות שבכל אחת מהן עשרה מטבעות. משקלו של כל מטבע עשרה גרם, אך אחת השקיות מכילה מטבעות מזויפים, שמשקל כל אחד מהם תשעה גרם בלבד. ברשותך מאזניים שמראים את המשקל המדויק (בגרמים) של המטבעות המונחים עליהם. איך תוכל לדעת, באמצעות שקילה אחת בלבד, באיזו שקית נמצאים המטבעות המזויפים?
3
חידה לחימום במדינה זרה, יש שלושה סוגי מטבעות:
מטבע נחושת שערכו 6 שקלים מטבע כסף שערכו 10 שקלים מטבע זהב שערכו 15 שקלים על השולחן נמצאים מספר מטבעות, אשר ערכם הכולל הוא 47 שקלים. כמה מטבעות יש מכל סוג על השולחן? (יש למצוא את כל התשובות האפשריות)
4
מאקרו ניתן להשתמש בקדם-מעבד על מנת להגדיר מאקרו (Macro), שזו התנהגות של הקדם-מעבד שפועלת על הקוד, ומחקה התנהגות של פונקציה. לדוגמא, נכתוב מאקרו בשם F2C המקבל טמפרטורה במעלות פרנהייט, וממיר אותה לטמפרטורה במעלות צלזיוס: #define F2C(F) (((F)-32)*(5/9.0)) ואז אם נכתוב: printf (“%f”, F2C(100)); אחרי פעולת הקדם-מעבד, שורה זו תקומפל כך: printf (“%f”, (((100)-32)*(5/9.0))); דוגמא נוספת: מאקרו המקבל זווית ברדיאנים, וממיר אותה לזווית במעלות: #define RAD_TO_DEG(x) ((x)* ) כשמגדירים מאקרו, פתיחת הסוגריים (עבור הפרמטר) חייבת להיות צמודה לשם המאקרו.
5
מאקרו דוגמא למאקרו המקבל פרמטר ומעלה אותו בריבוע:
#define SQR(x) ((x)*(x)) מדוע זקוקים לכל זוגות הסוגריים הללו? נניח והיינו רושמים את המאקרו כך (עם זוג הסוגריים החיצוניים, אך ללא שתי זוגות הסוגריים הפנימיים): #define SQR(x) (x*x) מה היה קורה אם מישהו היה כותב איפשהו בקוד את הביטוי SQR(2+3)? זה היה מתורגם ל: (2+3*2+3), וזה, כמובן, לא מה שהיינו רוצים לקבל. ונניח והיינו רושמים את המאקרו כך (עם שתי זוגות הסוגריים הפנימיים, אך ללא זוג הסוגריים החיצוניים): #define SQR(x) (x)*(x) הייתה נוצרת בעיה עבור ביטוי כגון זה: 1.0 / SQR(5) . זה היה מתורגם ל: 1.0 / (5) * (5) , כלומר: זה היה קודם מבצע את החלוקה בחמש, ורק לאחר מכן את הכפל (בניגוד לכפי שהיינו מעוניינים שיקרה).
6
מאקרו ניתן להגדיר מאקרו על פני יותר משורה אחת, על-ידי שימוש בתו \ :
ניתן להגדיר מאקרו על פני יותר משורה אחת, על-ידי שימוש בתו \ : #define PRINTME(x) printf(“the number \ received is %d”, (x)) מדוע לדעתכם לא כתבנו ; (נקודה פסיק) בסוף השורה השנייה בהגדרת המאקרו? ניתן להגדיר גם מאקרו המקבל יותר מפרמטר אחד: #define MAX(a,b) ((a) > (b) ? (a) : (b)) נשים לב שבמאקרו הזה ישנם פרמטרים המופיעים בגוף המאקרו יותר מפעם אחת. זה עשוי ליצור בעיות, למשל במקרה של זימון כגון: MAX(++num1,num2) שיתורגם לביטוי: ((++num1) > (num2) ? (++num1) : (num2)) עלולה להתרחש כאן הגדלה של המשתנה num1 ב-2, וברור שלא לזה התכוונו. כאשר כותבים מאקרו, חשוב להיזהר מפני סכנות (hazards) כגון אלו, ובמידת הצורך לתעד אותן בהערות, על מנת שמתכנתים שעושים שימוש במאקרו זה ידעו להימנע מהן.
7
assert אחת מיחידות הספרייה הסטנדרטיות של שפת C היא יחידת הספרייה assert.h, המכילה מאקרו בשם assert. זהו מאקרו דמוי-פונקציה, המקבל כפרמטר ביטוי, ובודק האם הביטוי הוא שקרי (כלומר – שווה לאפס). במידה והביטוי אינו שקרי – דבר אינו קורה. במידה והביטוי שקרי – התכנית עוצרת את ריצתה באמצע, ומודפסת הודעת שגיאה (הנשלחת ל-stderr) המפרטת מהו הביטוי שכשל, באיזה קובץ קוד מקור זה אירע, ובאיזה שורה בקובץ. ניתן להשתמש במאקרו זה בעת ניפוי שגיאות מתכנית, כדי לוודא האם ערכי משתנים אינם כפי שהיינו רוצים שיהיו. לדוגמא: נניח שאנו כותבים תוכנה לניהול חשבונות, ואחת לכמה זמן אנחנו מבחינים כי התוכנה מציגה ערכים שגויים. לאחר בדיקה, ההשערה שלנו היא שהמשתנה interest_rate, שאמור תמיד להיות אי-שלילי, מקבל איכשהו ערך שלילי. כדי לבדוק זאת, נכניס את ההוראה הבאה למקומות בתוכנה, לפני שעושים שימוש במשתנה: assert (interest_rate >= 0); אם אי פעם נגיע לנקודה זו, וערכו של המשתנה יהיה שלילי, אז המאקרו יעצור את ריצת התכנית, והודעה מתאימה תוצג. זה יאפשר לנו לדבג את התכנית, ולחפש את הסיבה לכך שהמשתנה קיבל ערך שלילי, דבר שלא היה אמור לקרות.
8
assert דוגמא לשימוש ב-assert:
#define NDEBUG #include <stdio.h> #include <assert.h> int main() { int x; scanf ("%d", &x); assert(x >= 0); printf ("\nYou've entered %d", x); return 0; } כדי "לכבות" את המאקרו assert, ניתן להכליל בראש התכנית, לפני ההוראה #include <assert.h>, את ההגדרה של הקבוע NDEBUG. הדבר מאותת ל-assert.h כי איננו מעוניינים כרגע לנפות שגיאות, ולכן המאקרו assert יהיה נטול השפעה. הדבר שימושי כדי להפסיק בבת אחת את פעולת כל הזימונים של המאקרו assert ברחבי הקוד, מבלי שנצטרך לסרוק את הקוד ולמחוק.
9
מצביעים לפונקציות נעיין בתכנית הבאה: #include <stdio.h>
float add (float a, float b) { return a+b; } float subtract (float a, float b) { return a-b; } float multiply (float a, float b) { return a*b; } float divide (float a, float b) { return (b != 0) ? a/b : 0; } void calculate (float operand1, char operator, float operand2) { float result; switch (operator) { case '+': result = add(operand1,operand2); break; case '-': result = subtract(operand1,operand2); case '*': result = multiply(operand1,operand2); case '/': result = divide(operand1,operand2); default: printf("Illegal operation!"); return; } printf ("%.2f\n", result);
10
מצביעים לפונקציות המשך התכנית: int main() { float first, second;
printf ("Enter the first number: "); scanf ("%f", &first); printf ("Enter the second number: "); scanf ("%f", &second); calculate(first,'+',second); return 0; }
11
מצביעים לפונקציות כעת, נוסיף את הפונקציה calculate2 ונשכתב את ה-main:
void calculate2 (float operand1, float (*op)(float,float), float operand2) { float result = op(operand1,operand2); printf ("%.2f\n", result); } int main() float first, second; printf ("Enter the first number: "); scanf ("%f", &first); printf ("Enter the second number: "); scanf ("%f", &second); calculate(first,'+',second); calculate2(first,add,second); return 0; שתי השורות המופיעות בפלט זהות זו לזו. הפרמטר השני ל-calculate2 הוא מצביע לפונקציה (Pointer to a function). מצביע לפונקציה הוא מצביע, אשר במקום להכיל את כתובתו של משתנה הנמצא בזיכרון, הוא מכיל כתובת של פונקציה (שאף היא נמצאת בזיכרון).
12
מצביעים לפונקציות כשמגדירים מצביע לפונקציה חובה לציין את מספר הפרמטרים שהפונקציה מקבלת, את טיפוסיהם, ואת טיפוס הערך המוחזר. למשל, נגדיר משתנה מטיפוס מצביע לפונקציה המקבלת מס' ממשי ושני תווים, ומחזירה שלם. לאחר שנגדיר את המשתנה, נציב בו NULL: int (*ptr2func)(float,char,char); ptr2func = NULL; בדוגמאות הבאות נשתמש בפונקצית ספריה סטנדרטית בשם memcpy, אותה כבר פגשנו. כזכור, הפונקציה מוגדרת ב-string.h, וכותרתה: void *memcpy (void *destination, void *source, int num); הפונקציה מעתיקה בלוק שגודלו num בתים, מהכתובת source אל הכתובת destination. היא אינה מתחשבת כלל בסוג המידע המאוחסן בתאי זיכרון אלו, אלא פשוט מעתיקה גוש של נתונים ממקום למקום. הערך המוחזר של הפונקציה היא הכתובת destination, אך אין חובה לעשות בערך זה שימוש.
13
מצביעים לפונקציות נניח שברצוננו לכתוב תכנית הממיינת מערך של מספרים שלמים בעזרת האלגוריתם למיון בועות (Bubble Sort). להלן קוד הפותר בעיה זו: #include <stdio.h> /* VERSION 1 */ int arr[10] = {3,6,1,2,3,8,4,1,7,2}; void bubble_sort (int a[], int n); int main() } int i; for (i = 0; i < 10; i++) printf("%d ", arr[i]); bubble_sort(arr,10); printf("\n"); return 0; { void bubble_sort (int a[], int n) { int i, j, temp; for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (a[j-1] > a[j]) { temp = a[j-1]; a[j-1] = a[j]; a[j] = temp; }
14
מצביעים לפונקציות תכנית זו ממיינת מערך של שלמים, אך כיצד ניתן לשנותה אם נרצה שתמיין מערך של נתונים מסוג כלשהו, לאו דווקא משתנים מטיפוס int? פתרון אחד יהיה לכתוב מספר גרסאות של הפונקציה bubble_sort: אחת לצורך מיון int-ים, אחת לצורך מיון float-ים, אחת לצורך מיון מחרוזות, וכו'. פתרון אחר, יעשה שימוש במצביעים לפונקציות. נפתח פתרון זה בשלבים. ראשית, נכניס לשימוש פונקציית השוואה בשם compare: int compare (int a, int b) { return ((a > b) ? 1 : ((a < b) ? -1 : 0)); }
15
מצביעים לפונקציות כדי לקדם את האי-תלות בטיפוס הנתונים, נשתמש במצביעים לשלם, במקום במשתנים מטיפוס שלם, שכן כאשר עובדים עם מצביעים, קל יותר להמיר מטיפוס לטיפוס. קל יותר להמיר מצביע לממשי אל מצביע לשלם, מאשר להמיר ממשי לשלם. הסיבה לכך היא שכל המצביעים, לא משנה אל מה הם מצביעים, מכילים את אותו סוג הנתונים )כתובת בזיכרון): #include <stdio.h> /* VERSION 2 */ int arr[10] = {3,6,1,2,3,8,4,1,7,2}; void bubble_sort (int *a, int n); int compare (int *a, int *b); int main() } int i; for (i = 0; i < 10; i++) printf("%d ", arr[i]); bubble_sort(arr,10); printf("\n"); return 0; { void bubble_sort (int *a, int n) { int i, j, temp; for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (compare(&a[j-1],&a[j]) > 0) { temp = a[j-1]; a[j-1] = a[j]; a[j] = temp; } int compare (int *a, int *b) { return ((*a > *b) ? 1 : ((*a < *b) ? -1 : 0)); }
16
מצביעים לפונקציות הצעד הבא יהיה להגדיר את המצביעים שבפונקצית-ההשוואה compare להיות מצביע ל-void, במקום מצביע ל-int, על מנת שהפונקציה תהיה כללית יותר, ופחות 'רגישה' לסוג המסוים של הנתונים בו אנו מטפלים: #include <stdio.h> /* VERSION 3 */ int arr[10] = {3,6,1,2,3,8,4,1,7,2}; void bubble_sort (int *a, int n); int compare (void *a, void *b); int main() } int i; for (i = 0; i < 10; i++) printf("%d ", arr[i]); bubble_sort(arr,10); printf("\n"); return 0; { void bubble_sort (int *a, int n) { int i, j, temp; for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (compare((void *)&a[j-1], (void *)&a[j]) > 0) { temp = a[j-1]; a[j-1] = a[j]; a[j] = temp; }
17
מצביעים לפונקציות איך תיראה פונקציית ההשוואה compare בגירסה השלישית של התכנית? int compare (void *a, void * b) { return ((*(int *)a > *(int *)b) ? 1 : ((*(int *)a < *(int *)b) ? -1 : 0)); } נשים לב שהיה צורך לבצע הסבה (casting) באופן ידני ומפורש, הן בתוך compare והן כשהעברנו פרמטרים ל-compare. החשובה מבין שתי הסבות אלו היא ההסבה בתוך compare, שכן מבלי לדעת את הטיפוס, לא ניתן היה להשוות בין הערכים המוצבעים על-ידי a ו-b.
18
מצביעים לפונקציות נשים לב שהפרמטר הראשון ל-bubble_sort הוא מצביע לשלם. אילו שינויים נצטרך לעשות בקוד אם נרצה להפוך פרמטר זה למצביע ל-void? ראשית, נצטרך לעשות משהו עם המשתנה temp. כרגע הטיפוס שלו int, אבל מרגע שהפרמטר הראשון כבר לא יהיה מערך של int-ים, אז גם ה-temp אמור להשתנות בהתאם. הוא אמור לתפוס מספיק בתים בזיכרון על מנת שיוכל לאחסן איבר מהמערך. שנית, לא נוכל לבצע העתקה פשוטה מהצורה temp = a[j-1], שכן על מנת לבצע העתקה זו, צריך לדעת מהו הטיפוס של a[j-1] (אחרת לא נדע כמה בתים בזיכרון יש להעתיק). שלישית, לא ניתן לגשת לאיברי המערך בצורה a[i] שכן לצורך גישה כזו, צריך לדעת מהו הגודל בבתים של כל תא. את שלושת הבעיות האחרונות נפתור על-ידי כך שנעביר לפונקציה פרמטר בשם size, המכיל את הגודל בבתים של איבר במערך.
19
מצביעים לפונקציות #include <stdio.h> /* VERSION 4 */
#include <stdlib.h> #include <string.h> int arr[10] = {3,6,1,2,3,8,4,1,7,2}; void bubble_sort (void *a, int n, int size); int compare (void *a, void *b); int main() } int i; for (i = 0; i < 10; i++) printf("%d ", arr[i]); bubble_sort(arr,10,sizeof(int)); printf("\n"); return 0; { void bubble_sort (void *a, int n, int size) { int i, j; void *temp = malloc(size); for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (compare(a+size*(j-1),a+size*j)>0){ memcpy(temp,a+size*(j-1),size); memcpy(a+size*(j-1),a+size*j,size); memcpy(a+size*j,temp,size); } free(temp);
20
מצביעים לפונקציות איך תיראה פונקציית ההשוואה compare בגירסה הרביעית של התכנית? לא תשתנה כלל: int compare (void *a, void *b) { return ((*(int *)a > *(int *)b) ? 1 : ((*(int *)a < *(int *)b) ? -1 : 0)); }
21
מצביעים לפונקציות איך תשתנה התכנית כאשר נרצה למיין מערך מחרוזות, ולא מערך שלמים? #include <stdio.h> /* VERSION 4 - STRINGS INSTEAD OF INTEGERS*/ #include <stdlib.h> #include <string.h> char arr2[5][4] = {"Sam","Bob","Jim","Tim","Dan"}; void bubble_sort (void *a, int n, int size); int compare (void *a, void *b); int main() } int i; for (i = 0; i < 5; i++) printf("%s ", arr2[i]); bubble_sort(arr2,5,4); printf("\n"); return 0; {
22
מצביעים לפונקציות הפונקציה BubbleSort לא תשתנה כלל, מה שישתנה זו רק הפונקציה compare: void bubble_sort (void *a, int n, int size) { int i, j; void *temp = malloc(size); for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (compare(a+size*(j-1),a+size*j) > 0) memcpy(temp,a+size*(j-1),size); memcpy(a+size*(j-1),a+size*j,size); memcpy(a+size*j,temp,size); } free(temp); int compare (void *a, void *b) { return (strcmp((char *)a, (char *)b)); }
23
מצביעים לפונקציות כאשר עברנו ממערך של שלמים למערך של מחרוזות, שינינו את הפונקציה compare ואת ה-main, אבל הפונקציה bubble_sort לא השתנתה כלל. זה מלמד אותנו שהפונקציה bubble_sort בהחלט מסוגלת למיין נתונים מטיפוסים שונים, וצריך רק להעביר לה כפרמטר את פונקצית ההשוואה שאנחנו מבקשים לעשות בה שימוש. זה ייעשה על-ידי מצביע לפונקציה, אשר יוגדר באופן הבא: int (*compare)(const void *, const void *) כלומר, compare הוא מצביע לפונקציה אשר מקבלת שני פרמטרים מטיפוס מצביע ל-void שהוא קבוע, ומחזירה שלם. הכותרת של bubble_sort תראה כעת כך: void bubble_sort (void *a, int n, int size, int (*compare)(const void *, const void *));
24
מצביעים לפונקציות מדוע הגדרנו את המצביע לפונקציה באופן הבא –
int (*compare)(const void *, const void *) במקום, למשל, באופן הבא? int *compare(const void *, const void *) צורת כתיבה זו פירושה, ש-compare היא פונקציה המחזירה מצביע ל-int. בשפת C לסוגריים יש קדימות גבוהה יותר מאשר ל-*, ועל כן, בכך ששמנו סוגריים סביב *compare הודענו למהדר שאנחנו מצהירים על מצביע לפונקציה. התכנית הבאה מדגימה כיצד ניתן להשתמש בפונקציית bubble_sort אחת למיון נתונים משני סוגים: #include <stdio.h> /* VERSION 5 - POINTERS TO FUNCTIONS */ #include <stdlib.h> #include <string.h> int arr1[10] = {3,6,1,2,3,8,4,1,7,2}; char arr2[5][4] = {"Sam","Bob","Jim","Tim","Dan"};
25
מצביעים לפונקציות void bubble_sort (void *a, int n, int size,
int (*compare)(const void *, const void *)); int compare_string (const void *a, const void *b); int compare_int (const void *a, const void *b); int main() { int i; printf("The two arrays before sorting: \n"); for (i = 0; i < 10; i++) printf("%d ", arr1[i]); printf("\n"); for (i = 0; i < 5; i++) printf("%s ", arr2[i]); bubble_sort(arr1,10,sizeof(int),compare_int); bubble_sort(arr2,5,4,compare_string); printf("\n\nThe two arrays after sorting: \n"); return 0; }
26
מצביעים לפונקציות void bubble_sort (void *a, int n, int size,
int (*compare)(const void *, const void *)) { int i, j; void *temp = malloc(size); for (i = n-1; i >= 0; i--) for (j = 1; j <= i; j++) if (compare((void *)(a+size*(j-1)),(void *)(a+size*(j))) > 0) memcpy(temp,a+size*(j-1),size); memcpy(a+size*(j-1),a+size*j,size); memcpy(a+size*j,temp,size); } free(temp);
27
מצביעים לפונקציות int compare_int (const void *a, const void *b) {
return ((*(int *)a > *(int *)b) ? 1 : ((*(int *)a < *(int *)b) ? -1 : 0)); } int compare_string (const void *a, const void *b) return strcmp((char *)a,(char *)b);
28
מצביעים לפונקציות ישנן שתי פונקציות ספרייה סטנדרטיות, המוגדרות שתיהן ב-stdlib.h, העושות שימוש במצביעים לפונקציות: הפונקציה qsort (הממיינת מערך בעזרת אלגוריתם מיון יעיל הנקרא 'מיון-מהיר' – Quick Sort) הפונקציה bsearch (המבצעת חיפוש בינארי במערך ממוין). הכותרת של qsort היא: void qsort (void *base, int num, int size, int (*compare) (const void *, const void *)); הפונקציה ממיינת את num איברי המערך שמתחיל מהכתובת base, ואשר גודל כל אחד מאיבריו הוא size בתים. הפונקציה משתמשת בפונקצית ההשוואה compare. הכותרת של bsearch היא: void *bsearch (const void *key, void *base, int num, int size, הפונקציה מקבלת מערך המכיל num איברים, המתחיל מהכתובת base. גודל כל אחד מאיברי המערך הוא size בתים. המערך ממוין בסדר-עולה לפי פונקצית ההשוואה compare. הפונקציה מבצעת חיפוש בינארי במערך אחר האיבר key, ומחזירה NULL אם הוא לא מופיע במערך, ואת כתובתו אם הוא מופיע.
29
מצביעים לפונקציות תכנית לדוגמא העושה שימוש בשתי פונקציות סטנדרטיות אלה: #include <stdio.h> #include <stdlib.h> int values[] = {40, 10, 100, 90, 20, 25}; int compare_int (const void *a, const void *b) { return (*(int *)a - *(int *)b); } int main() int *ptr, key = 25; qsort(values,6,sizeof(int),compare_int); ptr = bsearch(&key,values,6,sizeof(int),compare_int); printf ("%d is %sin the array" ,key, (ptr == NULL) ? "not " : ""); return 0;
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.