10.5 מצביעי פונקציות פתרון
פתרון - מצביעי פונקציות ו-callbacks - function pointers¶
פתרון 1 - מחשבון עם dispatch table¶
#include <stdio.h>
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div_op(double a, double b) {
if (b == 0) {
printf("error: division by zero\n");
return 0;
}
return a / b;
}
typedef double (*math_op_t)(double, double);
int main(void) {
math_op_t ops[] = {add, sub, mul, div_op};
const char *op_names[] = {"add", "sub", "mul", "div"};
double a, b;
int op;
printf("enter two numbers: ");
scanf("%lf %lf", &a, &b);
printf("choose operation (0=add, 1=sub, 2=mul, 3=div): ");
scanf("%d", &op);
if (op < 0 || op > 3) {
printf("invalid operation\n");
return 1;
}
double result = ops[op](a, b);
printf("%s(%.2f, %.2f) = %.2f\n", op_names[op], a, b, result);
return 0;
}
שימו לב שהקריאה ops[op](a, b) ניגשת למערך ומפעילה את הפונקציה בשורה אחת. זה הרבה יותר נקי מ-switch עם 4 מקרים, ומתרחב בקלות - מוסיפים פונקציה ואיבר למערך.
פתרון 2 - apply ו-filter גנריים¶
#include <stdio.h>
void array_apply(int *arr, int n, int (*func)(int)) {
for (int i = 0; i < n; i++)
arr[i] = func(arr[i]);
}
int array_filter(int *arr, int n, int *out, int (*predicate)(int)) {
int count = 0;
for (int i = 0; i < n; i++) {
if (predicate(arr[i])) {
out[count] = arr[i];
count++;
}
}
return count;
}
int array_reduce(int *arr, int n, int initial, int (*combine)(int, int)) {
int result = initial;
for (int i = 0; i < n; i++)
result = combine(result, arr[i]);
return result;
}
// פונקציות עזר
int triple(int x) { return x * 3; }
int is_even(int x) { return x % 2 == 0; }
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
void print_array(const char *label, int *arr, int n) {
printf("%s: [", label);
for (int i = 0; i < n; i++) {
printf("%d", arr[i]);
if (i < n - 1) printf(", ");
}
printf("]\n");
}
int main(void) {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = 10;
// הכפלה ב-3
int tripled[10];
for (int i = 0; i < n; i++) tripled[i] = arr[i];
array_apply(tripled, n, triple);
print_array("tripled", tripled, n);
// סינון זוגיים
int evens[10];
int even_count = array_filter(arr, n, evens, is_even);
print_array("evens", evens, even_count);
// סכום
int sum = array_reduce(arr, n, 0, add);
printf("sum: %d\n", sum);
// מכפלה
int product = array_reduce(arr, n, 1, multiply);
printf("product: %d\n", product);
return 0;
}
פלט:
הפונקציות array_apply, array_filter, ו-array_reduce הן גנריות - הן לא יודעות מה הcallback עושה, אבל הן יודעות איך להפעיל אותו על המערך. זה בדיוק הרעיון של תכנות פונקציונלי, ממומש בC עם מצביעי פונקציות.
פתרון 3 - מערכת אירועים - event system¶
#include <stdio.h>
#include <string.h>
#define MAX_HANDLERS 10
typedef void (*event_handler_t)(const char *event_name, void *data);
typedef struct {
event_handler_t handlers[MAX_HANDLERS];
int count;
} event_system;
void event_system_init(event_system *sys) {
sys->count = 0;
}
void register_handler(event_system *sys, event_handler_t handler) {
if (sys->count >= MAX_HANDLERS) {
printf("error: max handlers reached\n");
return;
}
sys->handlers[sys->count] = handler;
sys->count++;
}
void fire_event(event_system *sys, const char *name, void *data) {
for (int i = 0; i < sys->count; i++) {
sys->handlers[i](name, data);
}
}
// שלושה handlers שונים
void logger_handler(const char *name, void *data) {
printf("[LOG] event '%s' fired", name);
if (data)
printf(" with data: %s", (char *)data);
printf("\n");
}
void counter_handler(const char *name, void *data) {
static int count = 0;
count++;
printf("[COUNTER] total events: %d\n", count);
}
void alert_handler(const char *name, void *data) {
if (strcmp(name, "error") == 0) {
printf("[ALERT] ERROR detected!\n");
}
}
int main(void) {
event_system sys;
event_system_init(&sys);
register_handler(&sys, logger_handler);
register_handler(&sys, counter_handler);
register_handler(&sys, alert_handler);
printf("--- firing 'startup' ---\n");
fire_event(&sys, "startup", "system ready");
printf("\n--- firing 'error' ---\n");
fire_event(&sys, "error", "disk full");
printf("\n--- firing 'shutdown' ---\n");
fire_event(&sys, "shutdown", NULL);
return 0;
}
פלט:
--- firing 'startup' ---
[LOG] event 'startup' fired with data: system ready
[COUNTER] total events: 1
--- firing 'error' ---
[LOG] event 'error' fired with data: disk full
[COUNTER] total events: 2
[ALERT] ERROR detected!
--- firing 'shutdown' ---
[LOG] event 'shutdown' fired
[COUNTER] total events: 3
זה דפוס Observer/Pub-Sub קלאסי. מערכת האירועים לא יודעת מה הhandlers עושים, והhandlers לא יודעים על handlers אחרים. fire_event פשוט קוראת לכולם.
פתרון 4 - פולימורפיזם - צורות גיאומטריות¶
#include <stdio.h>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
typedef struct shape shape;
struct shape {
const char *type;
double (*area)(shape *self);
double (*perimeter)(shape *self);
void (*print)(shape *self);
double params[4];
};
// עיגול
double circle_area(shape *self) {
double r = self->params[0];
return M_PI * r * r;
}
double circle_perimeter(shape *self) {
return 2 * M_PI * self->params[0];
}
void circle_print(shape *self) {
printf("circle(r=%.2f)", self->params[0]);
}
shape new_circle(double r) {
shape s = {
.type = "circle",
.area = circle_area,
.perimeter = circle_perimeter,
.print = circle_print,
.params = {r}
};
return s;
}
// מלבן
double rect_area(shape *self) {
return self->params[0] * self->params[1];
}
double rect_perimeter(shape *self) {
return 2 * (self->params[0] + self->params[1]);
}
void rect_print(shape *self) {
printf("rectangle(w=%.2f, h=%.2f)", self->params[0], self->params[1]);
}
shape new_rectangle(double w, double h) {
shape s = {
.type = "rectangle",
.area = rect_area,
.perimeter = rect_perimeter,
.print = rect_print,
.params = {w, h}
};
return s;
}
// משולש
double tri_area(shape *self) {
return 0.5 * self->params[0] * self->params[1];
}
double tri_perimeter(shape *self) {
return self->params[0] + self->params[2] + self->params[3];
}
void tri_print(shape *self) {
printf("triangle(base=%.2f, h=%.2f)", self->params[0], self->params[1]);
}
shape new_triangle(double base, double height, double side2, double side3) {
shape s = {
.type = "triangle",
.area = tri_area,
.perimeter = tri_perimeter,
.print = tri_print,
.params = {base, height, side2, side3}
};
return s;
}
int main(void) {
shape shapes[] = {
new_circle(5.0),
new_rectangle(4.0, 6.0),
new_triangle(3.0, 4.0, 5.0, 4.0),
new_circle(1.0),
new_rectangle(10.0, 2.0),
};
int n = sizeof(shapes) / sizeof(shapes[0]);
for (int i = 0; i < n; i++) {
shapes[i].print(&shapes[i]);
printf(" -> area=%.2f, perimeter=%.2f\n",
shapes[i].area(&shapes[i]),
shapes[i].perimeter(&shapes[i]));
}
return 0;
}
קומפילציה (צריך -lm לפונקציות מתמטיות):
פלט:
circle(r=5.00) -> area=78.54, perimeter=31.42
rectangle(w=4.00, h=6.00) -> area=24.00, perimeter=20.00
triangle(base=3.00, h=4.00) -> area=6.00, perimeter=12.00
circle(r=1.00) -> area=3.14, perimeter=6.28
rectangle(w=10.00, h=2.00) -> area=20.00, perimeter=24.00
הנקודה המרכזית: הלולאה ב-main לא יודעת מה הסוג של כל צורה. היא פשוט קוראת ל-print, area, perimeter - וכל צורה מפעילה את הפונקציה הנכונה שלה. זה פולימורפיזם בC.
פתרון 5 - טעינת פלאגין - plugin loading¶
הפלאגין - myplugin.c:
#include <stdio.h>
int plugin_add(int a, int b) {
return a + b;
}
const char *plugin_name(void) {
return "My Calculator Plugin v1.0";
}
קומפילציה:
התוכנית הראשית - main.c:
#include <stdio.h>
#include <dlfcn.h>
int main(void) {
// טוענים את הספרייה
void *handle = dlopen("./myplugin.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen error: %s\n", dlerror());
return 1;
}
// מנקים שגיאות קודמות
dlerror();
// מחפשים את plugin_name
const char *(*get_name)(void) = dlsym(handle, "plugin_name");
char *error = dlerror();
if (error) {
fprintf(stderr, "dlsym error: %s\n", error);
dlclose(handle);
return 1;
}
// מחפשים את plugin_add
int (*plugin_add)(int, int) = dlsym(handle, "plugin_add");
error = dlerror();
if (error) {
fprintf(stderr, "dlsym error: %s\n", error);
dlclose(handle);
return 1;
}
// משתמשים בפונקציות
printf("loaded plugin: %s\n", get_name());
printf("plugin_add(3, 4) = %d\n", plugin_add(3, 4));
printf("plugin_add(100, 200) = %d\n", plugin_add(100, 200));
// סוגרים את הספרייה
dlclose(handle);
return 0;
}
קומפילציה והרצה:
פלט:
שימו לב לנתיב "./myplugin.so" - dlopen מחפש ספריות בנתיבי LD_LIBRARY_PATH. הנקודה-סלש אומרת "חפש בתיקייה הנוכחית".
הדפוס כאן הוא: dlopen פותח את הso, dlsym מחפש סמל (symbol) בתוכו, ואנחנו עושים cast ל-function pointer ומשתמשים. בפרויקט הסיכום (10.6) נרחיב את הרעיון הזה למערכת plugins שלמה.