In the following code I have created the something like the behavior of inheritance and methods in C99 (without vtable). The code compiles without any warnings even with pedantic
and is also Valgrind Pristine. I would like to hear about design patterns and techniques to improve code quality, readability and portability to major compilers (as gcc, clang, vsc and icc).
The code is divided to "simulate" multiple files, the first part defines some basic struct and functions.
Second part defines the parent polygon
class.
Third part defines a polygon
inherited class called triangle
.
Fourth part defines a square
from polygon
mostly defined using function placeholders.
And the fifth part defines the main program that uses the structs defined before for simple calls and changes in struct behaviors.
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <stdbool.h> /* ************************************************** * The basics * **************************************************/ struct point { float x; float y; }; static inline float distance (struct point p1, struct point p2); // a.k.a. l2 norm static inline float distance (struct point p1, struct point p2) { return sqrt ((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); } /* ************************************************** * POLYGON * **************************************************/ /* Header */ struct polygon { int sides; // All polygons have sides struct point vertex[20]; // No more than 20 sided polygon void (*destroy) (); // Unknown parameters float (*area) (); // Unknown parameters float (*perimeter) (); // Unknown parameters void (*action) (); // A polygon also needs some action }; float null_area (); float null_perimeter (); void null_action (); float generic_area (struct polygon *p); float generic_perimeter (struct polygon *p); void generic_action (void); struct polygon *polygon_init (int sides); void polygon_destroy (struct polygon *p); float polygon_area (struct polygon *poly); float polygon_perimeter (struct polygon *p); void polygon_action (struct polygon *p); bool all_sides_are_congruent (struct polygon *p); /* NULL function */ float null_area (void) { return -1; } float null_perimeter (void) { return -1; } void null_action (void) { return; } float generic_area (struct polygon *poly) { return null_area (); // Not implemented } float generic_perimeter (struct polygon *poly) { float p = 0; for (int i = 0; i < poly->sides - 1; i++) p += distance (poly->vertex[i], poly->vertex[i + 1]); return p + distance (poly->vertex[poly->sides - 1], poly->vertex[0]); } void generic_action (void) { printf ("I'm idle!\n"); // Lazy Polygon - not much action } /* Specialized functions */ struct polygon * polygon_init (int sides) { struct polygon *poly; poly = malloc (sizeof *poly); poly->sides = sides; poly->perimeter = generic_perimeter; poly->area = generic_area; poly->action = generic_action; poly->destroy = free; // Like a generic destroy ;-) return poly; } void polygon_destroy (struct polygon *poly) { poly->destroy (poly); } float polygon_perimeter (struct polygon *poly) { return poly->perimeter (poly); } float polygon_area (struct polygon *poly) { return poly->area (poly); } void polygon_action (struct polygon *poly) { printf ("Poly addr %p: ", (void *) poly); poly->action (); } bool all_sides_are_congruent (struct polygon *p) { int i = 0; float t = 0.; float s = 0.; s = distance (p->vertex[p->sides - 1], p->vertex[0]); for (i = 0; i < p->sides - 1; i++) { t = distance (p->vertex[i], p->vertex[i + 1]); if (s != t) { return false; } } return true; } /* ************************************************** * TRIANGLE * **************************************************/ /* HEADER */ struct triangle { // Triangles are like polygons, but for sake of this example // they have heights (shhh. all polygons have heights) struct polygon polygon; float (*height) (); }; // Not triangle struct dependant float heron_area_formula (float a, float b, float c); float shoelace_area_formula (struct point p0, struct point p1, struct point p2); /* Triangle struct dependant functions */ struct triangle *triangle_init (void); /* First ones from polygon */ void triangle_destroy (struct triangle *tri); float triangle_area (struct triangle *tri); float triangle_perimeter (struct triangle *tri); void triangle_action (struct triangle *tri); /* More specilized ones */ /* These two extract the needed information to the non triangle dependant * functions */ float triangle_heron_area (struct triangle *tri); float triangle_shoelace_area (struct triangle *tri); /* These are specilized to triangle struct only, not existing in general * polygons */ float triangle_height (struct triangle *tri); float triangle_height_from_base1 (struct triangle *tri); float triangle_height_from_base2 (struct triangle *tri); float triangle_height_from_base3 (struct triangle *tri); /* Functions */ /* Some known formulas to calculate triangle area */ // Calculates triangle area given face three distances float heron_area_formula (float a, float b, float c) { float p = (a + b + c) / 2; return sqrt (p * (p - a) * (p - b) * (p - c)); } // Calculates triangle area given three vertices float shoelace_area_formula (struct point p0, struct point p1, struct point p2) { return .5 * fabs (p0.x * p1.y - p0.y * p1.x + p1.x * p2.y - p1.y * p2.x + p2.x * p0.y - p2.y * p0.x); } struct triangle * triangle_init (void) { struct triangle *tri; tri = malloc (sizeof *tri); tri->polygon.sides = 3; tri->polygon.destroy = free; tri->polygon.action = generic_action; tri->polygon.perimeter = generic_perimeter; tri->polygon.area = triangle_heron_area; // By default we use heron's formula tri->height = triangle_height_from_base1; // By default height is related to first 2 points return tri; } void triangle_destroy (struct triangle *tri) { tri->polygon.destroy (tri); } float triangle_heron_area (struct triangle *tri) { return heron_area_formula (distance (tri->polygon.vertex[0], tri->polygon.vertex[1]), distance (tri->polygon.vertex[1], tri->polygon.vertex[2]), distance (tri->polygon.vertex[2], tri->polygon.vertex[0])); } float triangle_shoelace_area (struct triangle *tri) { return shoelace_area_formula (tri->polygon.vertex[0], tri->polygon.vertex[1], tri->polygon.vertex[2]); } float triangle_area (struct triangle *tri) { return tri->polygon.area (tri); } float triangle_perimeter (struct triangle *tri) { return tri->polygon.perimeter ((struct polygon *) tri); } void triangle_action (struct triangle *tri) { printf ("Triangle addr %p: ", (void *) tri); printf ("Triple action\n"); tri->polygon.action (); tri->polygon.action (); tri->polygon.action (); } // Height from three bases float triangle_height_from_base1 (struct triangle *tri) { return 2 * triangle_area (tri) / distance (tri->polygon.vertex[0], tri->polygon.vertex[1]); } float triangle_height_from_base2 (struct triangle *tri) { return 2 * triangle_area (tri) / distance (tri->polygon.vertex[1], tri->polygon.vertex[2]); } float triangle_height_from_base3 (struct triangle *tri) { return 2 * triangle_area (tri) / distance (tri->polygon.vertex[2], tri->polygon.vertex[0]); } // Pick one float triangle_height (struct triangle *tri) { return tri->height (tri); } /* ************************************************** * SQUARE * **************************************************/ struct square { struct polygon polygon; float (*height) (); bool (*is_square) (struct square * s); }; struct square *square_init (void); void square_destroy (struct square *s); float square_area (struct square *s); float square_perimeter (struct square *s); void square_action (struct square *s, int num); float square_height (struct square *s); bool square_check (struct square *s); bool square_check_by_side (struct square *s); float height_from_side (struct square *s); struct square * square_init (void) { struct square *s; s = malloc (sizeof *s); s->polygon.sides = 4; s->polygon.destroy = free; s->polygon.area = null_area; s->polygon.perimeter = null_perimeter; s->polygon.action = null_action; s->is_square = square_check_by_side; s->height = height_from_side; return s; } void square_destroy (struct square *s) { s->polygon.destroy (s); } float square_area (struct square *s) { return s->polygon.area (s); } float square_perimeter (struct square *s) { return s->polygon.perimeter (s); } void square_action (struct square *s, int num) { s->polygon.action (s); } float square_height (struct square *s) { return s->height (s); } bool square_check_by_side (struct square * s) { if ((s->polygon.sides == 4) && all_sides_are_congruent (&(s->polygon))) { return true; } else { return false; } } float height_from_side (struct square *s) { return distance (s->polygon.vertex[0], s->polygon.vertex[1]); } bool square_check (struct square * s) { return s->is_square (s); } /* ************************************************** * MAIN CODE * **************************************************/ int main (void) { // Method 1 struct polygon p1; p1.sides = 3; p1.vertex[0] = (struct point) { .x = 0,.y = 0}; p1.vertex[1] = (struct point) { .5, 1}; p1.vertex[2].x = 1; p1.vertex[2].y = 0; p1.perimeter = &generic_perimeter; p1.action = &generic_action; printf ("Perimeter of P1 %.2f\n", polygon_perimeter (&p1)); polygon_action (&p1); printf ("\n"); // Method 2 struct polygon *p2 = polygon_init (3); struct point t[3] = { {0, 0}, {.5, 1}, {1, 0} }; memcpy (p2->vertex, t, sizeof (t)); printf ("Perimeter of P2 %.2f\n", polygon_perimeter (p2)); polygon_action (p2); polygon_destroy (p2); printf ("\n"); // A triangle struct triangle *t1 = triangle_init (); memcpy (t1->polygon.vertex, t, sizeof (t)); printf ("Perimeter of T1 %.2f\n", triangle_perimeter (t1)); printf ("Area of T1 %.2f\n", triangle_area (t1)); t1->polygon.area = triangle_shoelace_area; // Let's use another area formula printf ("Area of T1 %.2f\n", triangle_area (t1)); printf ("Height of T1 base 1 %.2f\n", triangle_height (t1)); t1->height = triangle_height_from_base2; printf ("Height of T1 base 2 %.2f\n", triangle_height (t1)); t1->height = triangle_height_from_base3; printf ("Height of T1 base 3 %.2f\n", triangle_height (t1)); triangle_action (t1); triangle_destroy (t1); printf ("\n"); // Now a square; struct square *s1 = square_init (); struct point s[4] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} }; memcpy (s1->polygon.vertex, s, sizeof (s)); printf ("Perimeter of S1 %.2f\n", square_perimeter (s1)); printf ("Area of S1 %.2f\n", square_area (s1)); printf ("Height of S1 %.2f\n", square_height (s1)); // Why conditional jump here? printf ("Is S1 a square? %s\n", (square_check (s1) ? "true" : "false")); printf ("Square action\n"); square_action (s1, 5); square_destroy (s1); return 0; } ```