מצביעים לפונקציות בשפת C

בכל שפת תכנות יש פה ושם "מוזרויות", תחביר חסר היגיון שאין ברירה אלא ללמוד בעל-פה. כך, למשל, ההגדרה בשפת C של מצביע לפונקציה – טריק מתקדם יחסית שמאפשר לנו לבנות קוד גמיש מאוד, לשלוח פונקציות כפרמטרים (callback) ועוד. הנה הסבר והדגמה שיעזרו לנו להבין כיצד עושים זאת.

אינטרפרטציה גרפית של AI כלשהי לפרומפט "מצביע לפונקציה"
אינטרפרטציה גרפית של AI כלשהי לפרומפט "מצביע לפונקציה"

מצביע פשוט (תזכורת)

בתוכנית הבאה, i הוא משתנה רגיל ו-p הוא מצביע שמקבל (בשורה 9) את הכתובת של i. המספרים בפלט של התוכנית יהיו "2" ואז "3". אפרופו, כל התוכניות בפוסט זה הן למחשב, לא למיקרו-בקר, ומכאן השימוש החופשי ב-printf.

הגדרה ושימוש במצביע פשוט ב-C
הגדרה ושימוש במצביע פשוט ב-C

בכל הגדרה של מצביע יש שלושה חלקים: משמאל – הטיפוס שאליו מצביעים והמאפיינים שלו. באמצע – כוכבית שאומרת שזה מצביע, ומימין – השם של המצביע והמאפיינים שלו. הכוונה במאפיינים היא מה שנקרא רשמית "Type qualifiers" – כל המגבלות האופציונליות ששפת C מאפשרת לנו לקבוע עבור משתנים. למשל, בשורה הבאה:

הגדרת מצביע עם מאפיינים נוספים למצביע ולמוצבע
הגדרת מצביע עם מאפיינים נוספים למצביע ולמוצבע

המצביע, ששמו p, מצביע לטיפוס int קבוע (כי מילת המפתח const היא משמאל לכוכבית!), אבל הוא עצמו לא קבוע אלא volatile. אם תכתבו את השורה הזו במקום שורה 6 בקוד למעלה, הקומפיילר יודיע על שגיאה בשורה 10, כי מתבצע שם שינוי ערך של המוצבע, והרי אסור לשנות const.

מצביע לפונקציה

פונקציה היא יצור מורכב יותר מאשר משתנה רגיל: יש לה טיפוס ערך חוזר (או void), ורשימת פרמטרים (או void). איך כוללים את כל זה בתוך הגדרת מצביע? התשובה היא, כאמור, תחביר שרירותי שחייבים לשנן. בגדול הוא נראה כמו הגדרת פונקציה, אבל עם כוכבית לפני השם שלה, וסוגריים סביבם. כמו כן, לא חייבים לכתוב את שמות הפרמטרים, אלא רק את הטיפוס שלהם. הנה תוכנית לדוגמה, עם מצביע-לפונקציה שנקרא funcPtr (מוגדר בשורה 9). המצביע הזה מקבל (בשורה 11) את הכתובת של הפונקציה helloPrint וקורא לה עם הערך 123 (שורה 12). לכן, הפלט של התוכנית יהיה "Hello 123".

הגדרה ושימוש במצביע לפונקציה ב-C
הגדרה ושימוש במצביע לפונקציה ב-C

פרמטר שהוא מצביע לפונקציה

אפשר להשתמש בצורת ההגדרה המגושמת הזו גם ברשימת פרמטרים של פונקציה רגילה. בקוד הבא, הפונקציה callbackUser מקבלת פרמטר בשם callback, שהוא בעצמו מצביע לפונקציה. שימו לב שצורת ההגדרה שלו (בשורה 7) זהה לזו של funcPtr בקוד למעלה. גם כאן הפלט יהיה "Hello 123".

הגדרת פרמטר בפונקציה שהוא מצביע לפונקציה
הגדרת פרמטר בפונקציה שהוא מצביע לפונקציה

אבל זה מסורבל – דמיינו הגדרה של פרמטר, שהוא מצביע לפונקציה שמקבלת שלושה או ארבעה פרמטרים שונים! כדי ליצור קוד נקי יותר ונוח יותר לקריאה ולשינויים, עדיף ליצור טיפוס של מצביע לפונקציה הזו. יצירת טיפוסים נעשית באמצעות typedef. הנה תוכנית דומה, רק עם typedef למצביע-לפונקציה (מוגדר בשורה 3, ונעשה בו שימוש בשורה 9):

מערך של מצביעים לפונקציות

מרגע שהגדרתי טיפוס, לא משנה של מה, אני יכול כמובן ליצור מערך של משתנים מטיפוס זה. לדוגמה,

הגדרה זו יוצרת מערך בגודל 2, של מצביעים לפונקציות שמקבלות פרמטר const int ולא מחזירות כלום.

אבל אפשר גם להגדיר את המערך עצמו ב-typedef, וכך ליצור קבוצת מצביעים שלמה ולהעביר אותה, לפי הצורך, בתור פרמטר. איפה לדעתכם צריך לשים בהגדרה את הסוגריים המרובעים שיוצרים מערך? התשובה, שאפשר להתווכח לגבי ההיגיון שבה, היא מיד אחרי שם המערך. הנה תוכנית הדגמה שעושה את זה. שורה 3 מגדירה טיפוס של מערך של מצביעים לפונקציות, ובשורה 4 מוגדר משתנה אחד מטיפוס זה. בשורות 22 ו-23 המערך מאוכלס בהפניות לפונקציות שונות, ושורה 24 שולחת אותו כפרמטר לפונקציה "callbackUser", שעוברת על כולו ומריצה את הפונקציות שבו בזו אחר זו.

הגדרה ושימוש במערך של מצביעים לפונקציות ב-C
הגדרה ושימוש במערך של מצביעים לפונקציות ב-C
להרשמה
הודע לי על
8 תגובות
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

מצביע לפונקציה זה קונספט ממש מגניב של C.
סתם כדוגמא שימושית אולי – לאחרונה השתמשתי בזה כדי להעביר לספריה של מסך hd44780 את הפונקציות שעושות את הכתיבה, כדי לבחור דינאמית אם זה ממשק 4 או 8 ביט – לא משנה כרגע למה שאני ארצה להחליף ביניהם דינאמית.
זה גם לא מוסיף תקורה גדולה בדחיסות הקוד, למי שזה מפריע לו פה.

גיליתי את הבלוג שלך דרך איזה פוסט שלך בפייסבוק לגבי מיקרוסקופים והאמת די התרשמתי. התחלתי ללמוד לתכנת בשפת R בשנת 2019 ולאט לאט התקדמתי בסולם השפות ל ++c כדי לתכנת ארדואינו לפרוייקטים קטנים על מטריצה. בשלב מסויים הבנתי שלתכנת בקרי AVR בשפת c כמעט תמיד לא יאפשר לנצל את הפוטנציאל האמיתי שלהם אז למדתי לכתוב אסמבלי ואז באמת התחלתי לעשות פרוייקטים רציניים ודחוסים בצורה מטורפת. הבעיה עם שפת c זה השימוש המופרז ב stack ומלא boilerplate שמבזבז לך משאבים ב flash של הבקר (שבבקרים הכי זולים זה 1kb בערך) וזה נכפה עליך גם כשזה כלל לא נדרש (כן הגיוני אולי… לקרוא עוד »

לא צריך להוסיף & בקריאה?

הפלט של התוכנית בסעיף "מצביע לפונקציה" יהיה Hello 123,
ולא 123.