יום ראשון, 26 בפברואר 2017

שפת C - מצביע לפונקציה, qsort , bsearch ומשתנה const

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

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

מבנה כללי:

     ; (הארגומנטים שהפונקציה מקבלת)   (שם_המצביע *)   סוג הערך שהפונקציה מחזירה


לדוגמה אם יש פונקציה בשם "F":
double F(int a, int b, char c) { ...... }

אז כדי שמצביע "p" יוכל להצביע עליה, צריך להגדיר אותו כך:
double (*p) (int a, int b, char c);

ואז נוכל להכניס אליו את הכתובת של הפונקציה:
p = F ;
-שימו לב שבניגוד למשתנים, כאן לא צריך את התו &. משום ששם פונקציה הוא מצביע בעצמו.




דוגמאות:
#include <stdio.h>
int F1(int a){
 return(a*a);
}
 
int F2(int b){
 return (b+b);
}
 
void main(){
 int (*pf) (int a);
 int x;
 pf = F1;
 x = pf(5);
 printf("%d\n",x);  // ידפיס 25
 pf = F2;
 x = pf(5);
 printf("%d\n",x);  // ידפיס 10
}

....................................................................................................................................................................



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




qsort

* ישנם מספר אלגוריתמים למיון איברי מערך, quik sort הוא המהיר מכולם. כלול בספרייה stdlib.h.

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

מבנה כללי:

; (מצביע_לפונקציית_ההשוואה , גודל כל איבר בבתים , מספר האיברים במערך , מצביע_לתחילת_המערך) qsort

- "פונקציית ההשוואה" צריכה להיות בתחביר מסוים בו qsort עושה שימוש. כפי שנראה בדוגמה.

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

#include <stdio.h>
#include <stdlib.h>
 
int compare(const void *p1const void *p2) {  // צריך qsort זה המבנה של פונקציית ההשוואה ש
 int a = *(int*)p1// זה התחביר הנחוץ משום שקיבלנו מצביעים בלי סוג מסוים
 int b = *(int*)p2;
 if(a > b) return 1;
 if(a < b) return -1;
 return 0;
}
 
void main(){
 int A[5] = {1,2,6,5,4} , i;
 qsort(A,5,sizeof(int),compare);
 
 for(i=0; i<5; i++)  printf("%d ",A[i]);
}




ע"י שינוי קטן בפונקציית ההשוואה נוכל לשנות את כל פעולת המיון, כך שימוין מהגדול לקטן:
int compare(const void *p1const void *p2) {
 int a = *(int*)p1;
 int b = *(int*)p2;
 if(a > b) return -1;
 if(a < b) return 1;
 return 0;
}



נראה שqsort בודקת רק האם פונקציית ההשוואה החזירה ערך גדול/קטן/שווה ל 0.
לכן נוכל לכתוב את פונקציית ההשוואה בקיצור:
#include <stdio.h>
#include <stdlib.h>
 
int compare(const void *p1const void *p2) {
 int a = *(int*)p1;
 int b = *(int*)p2;
 return a-b;
}
 
void main(){
 int A[5] = {1,-1,0,1,4} , i;
 qsort(A,5,sizeof(int),compare);
 
 for(i=0; i<5; i++)  printf("%d ",A[i]);
}




נראה ש qsort יכולה למיין גם ע"פ שדות של struct. באמצעות פונקציית ההשוואה שאנו מספקים:
#include <stdio.h>
#include <stdlib.h>
 
struct Item {
 int sum1;
 int sum2;
};
 
int compareS1(const void *p1const void *p2) {
 int a = ((struct Item*)p1)->sum1;
 int b = ((struct Item*)p2)->sum1;
 return a-b;
}
 
int compareS2(const void *p1const void *p2) {
 int a = ((struct Item*)p1)->sum2;
 int b = ((struct Item*)p2)->sum2;
 return a-b;
}
 
void main(){
 struct Item A[5];
 int i;
 for (i=0;i<5;i++){ // נקלוט שדה ראשון של המשתנים בכל המערך
  printf("%d. Enter sum1: ",i+1);
  scanf("%d",&A[i].sum1);
 }
 for (i=0;i<5;i++){ // נקלוט שדה שני של המשתנים בכל המערך
  printf("%d. Enter sum2: ",i+1);
  scanf("%d",&A[i].sum2);
 }
 
 qsort(A,5,sizeof(struct Item),compareS1);
 for(i=0; i<5; i++)  printf("%d ",A[i].sum1); // נדפיס את השדה הראשון של המשתנים במערך ממוין עפ השדה הראשון
 
 qsort(A,5,sizeof(struct Item),compareS2);
 for(i=0; i<5; i++)  printf("%d ",A[i].sum2); // נדפיס את השדה השני של המשתנים במערך ממוין עפ השדה השני
}





אפילו אפשר למיין ע"פ סדר אלפביתי:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int compareSTR(const void *p1const void *p2) {
 char* a = *(char**)p1;
 char* b = *(char**)p2;
 return strcmp(a,b);
}
 
void main(){
 char* SS[5]={"dog","cat","fish","mouse","bird"};
 int i;
 qsort(SS,5,sizeof(char*),compareSTR);
 for(i=0; i<5; i++)  printf("%s  ",SS[i]);
}


לסיכום: עד היום עשינו שימוש בפונקציות הכלולות בספריות. היום הן עושות שימוש בפונקציות שלנו.






bsearch

הפונקציה bsearch מחפשת ומחזירה כתובת של איבר במערך שהתוכן שלו זהה לארגומנט שהיא קיבלה.
שימו לב: הפונקציה מקבלת מצביע לתחילת מערך שכבר ממוין!

מבנה כללי:

; (מצביע_לפונקציית_ההשוואה , גודל כל איבר בבתים , מספר האיברים במערך , מצביע_לתחילת_המערך , כתובת_של_הערך_שצריך_לחפש) bsearch

דוגמה: (במערך ממוין)
#include <stdio.h>
#include <stdlib.h>
 
int comp(const void *p1const void *p2) {
 int a = *(int*)p1;
 int b = *(int*)p2;
 return a-b;
}
 
void main(){
 int B[6]={1,2,5,8,66,100};
 int num = 66,*p;
 p = (int*)bsearch(&num,B,6,sizeof(int),comp);
 printf("%d  %d\n",*(p-1),*p); // נדפיס את האיבר שחיפשנו והאיבר שלפניו
}





דוגמה 2: (במערך לא ממוין)
#include <stdio.h>
#include <stdlib.h>
 
int comp(const void *p1const void *p2) {
 int a = *(int*)p1;
 int b = *(int*)p2;
 return a-b;
}
 
void main(){
 int B[6]={100,66,1,8,2,5};
 int num = 66, *p;
 qsort(B,6,sizeof(int),comp);
 p = (int*)bsearch(&num,B,6,sizeof(int),comp);
 printf("%d  %d\n",*(p-1),*p); // נדפיס את האיבר שחיפשנו והאיבר שלפניו
}







const

const זו דרך ליצור משתנה שאסור לשנות את ערכו. (מצחיק לקרוא לו "משתנה קבוע", והאמת שהוא פשוט "קבוע".)
לדוגמה:
const int a = 5;            
a יהיה 5 לאורך כל התוכנית.

const נועד להגן על המתכנת משגיאות בקוד, והוא שימושי בעיקר במצביעים.



ראו עוד הרחבה בנושא: ויקיספר , C מההתחלה







-עוד מעט סיימנו!-

אין תגובות:

הוסף רשומת תגובה