לדלג לתוכן

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;
}

פלט:

tripled: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
evens: [2, 4, 6, 8, 10]
sum: 55
product: 3628800

הפונקציות 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 לפונקציות מתמטיות):

gcc -lm shapes.c -o shapes

פלט:

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";
}

קומפילציה:

gcc -shared -fPIC -o myplugin.so myplugin.c

התוכנית הראשית - 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;
}

קומפילציה והרצה:

gcc -ldl main.c -o main
./main

פלט:

loaded plugin: My Calculator Plugin v1.0
plugin_add(3, 4) = 7
plugin_add(100, 200) = 300

שימו לב לנתיב "./myplugin.so" - dlopen מחפש ספריות בנתיבי LD_LIBRARY_PATH. הנקודה-סלש אומרת "חפש בתיקייה הנוכחית".

הדפוס כאן הוא: dlopen פותח את הso, dlsym מחפש סמל (symbol) בתוכו, ואנחנו עושים cast ל-function pointer ומשתמשים. בפרויקט הסיכום (10.6) נרחיב את הרעיון הזה למערכת plugins שלמה.