TheJach.com

Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

OOP in C with Function Pointers and Structs

In the beginning PyGame book (google around for it) the author presents a text-based Tank Game in Chapter 2 where you select which tanks to shoot and stats accordingly change. It is used as an introduction to object oriented programming. My team at school has to use C to make a video game, but we really want to use OOP-like idioms, and classes really are just glorified structs. However, googling around for how to use function pointers, I never saw a clear way of doing what I wanted: classes in separate source files with static functions. So after a morning of hacking away at it, I figured it out. Here's the source code for the three files:


/* tank.h, the definition for the Tank class */
typedef struct Tank {
/* attributes */
char *name;
int alive;
int ammo;
int armor;

/* methods */
void (*stats)(const struct Tank *);
void (*fire_at)(struct Tank *, struct Tank *);
void (*hit)(struct Tank *);
void (*explode)(struct Tank *);

} Tank;

Tank * new_Tank(const char *);
void delete_Tank(Tank *);


Caveats: inside the struct, you must explicitly say "struct Tank" or else the compiler will freak out. Also, all of those first arguments refer to "self", just like Python does. (I had a "click" moment after this.) This is so each function in the struct can modify the struct attributes, as you'll see.


/* tank.c, the implementation of the Tank class */
#include <stdio.h> /* printf */
#include <malloc.h> /* malloc, free */
#include <string.h> /* strlen, strcpy */
#include "tank.h"

static void stats(const Tank *self) {
if (self->alive) {
printf("%s (%i armor, %i shells)\n", self->name, self->armor, self->ammo);
} else {
printf("%s (DEAD)\n", self->name);
}
}

static void fire_at(Tank *self, Tank *enemy) {
if (self->ammo > 0) {
self->ammo -= 1;
printf("%s fires on %s\n", self->name, enemy->name);
enemy->hit(enemy);
} else {
printf("%s has no shells!\n", self->name);
}
}

static void hit(Tank *self) {
self->armor -= 20;
printf("%s is hit!\n", self->name);
if (self->armor <= 0) {
self->explode(self);
}
}

static void explode(Tank *self) {
self->alive = 0;
printf("%s explodes!\n", self->name);
}

Tank * new_Tank(const char *name) {
Tank *tank = (Tank *) malloc(sizeof(Tank));

tank->name = (char *) malloc((strlen(name) + 1)* sizeof(char));
strcpy(tank->name, name);
tank->alive = 1;
tank->ammo = 5;
tank->armor = 60;

tank->stats = stats;
tank->fire_at = fire_at;
tank->hit = hit;
tank->explode = explode;

return tank;
}

void delete_Tank(Tank * tank) {
free(tank->name);
free(tank);
}


Starting to look a little Pythonic? Having the functions be static makes sure they're properly encapsulated in the file and don't leak out into the global namespace. new_Tank() is unique enough and it's clear what's going on. Consider it to be like the constructor of a class. And like a destructor, you need to have delete_Tank() to clean up memory.


/* tankgame.c, drives the class */
#include <stdio.h>
#include "tank.h"

int main(void) {
Tank *tank_a = new_Tank("Alice");
Tank *tank_b = new_Tank("Bob");
Tank *tank_c = new_Tank("Carol");

int alive_tanks = 3;

while (alive_tanks > 1) {
printf("\n");
printf("a: ");
tank_a->stats(tank_a);
printf("b: ");
tank_b->stats(tank_b);
printf("c: ");
tank_c->stats(tank_c);

printf("Who fires? ");
char first = getchar();
getchar(); /* junky \n */
Tank *first_tank;
if (first == 'a') first_tank = tank_a;
else if (first == 'b') first_tank = tank_b;
else if (first == 'c') first_tank = tank_c;
else {
printf("No such tank!\n");
continue;
}
printf("Whom at? ");
char second = getchar();
getchar(); /* junky \n */
Tank *second_tank;
if (second == 'a') second_tank = tank_a;
else if (second == 'b') second_tank = tank_b;
else if (second == 'c') second_tank = tank_c;
else {
printf("No such tank!\n");
continue;
}

if (!first_tank->alive || !second_tank->alive) {
printf("One of those tanks is dead!\n");
continue;
}

printf("\n******************************\n");
first_tank->fire_at(first_tank, second_tank);
if (!second_tank->alive) {
alive_tanks--;
}
printf("******************************\n");
}

if (tank_a->alive) {
printf("%s is the winner!\n", tank_a->name);
} else if (tank_b->alive) {
printf("%s is the winner!\n", tank_b->name);
} else if (tank_c->alive) {
printf("%s is the winner!\n", tank_c->name);
}

delete_Tank(tank_a);
delete_Tank(tank_b);
delete_Tank(tank_c);

return 0;
}


The key to "classes" in C is using malloc to allocate the struct. As always with malloc'ing, you also have to make sure you free the object. This is easiest done by making a destructor--delete_Tank().

So there you have it. I can give more information to anyone who asks if they need it.


Posted on 2010-01-28 by Jach

Tags: c, programming

Permalink: https://www.thejach.com/view/id/70

Trackback URL: https://www.thejach.com/view/2010/1/oop_in_c_with_function_pointers_and_structs

Back to the top

OO May 20, 2012 12:19:44 PM Hey,

this looks very interesting! However, I couldn't get it working (tankgame.c:(.text+0xe): undefined reference to `new_Tank').

Email me please!
OO
Anonymous April 18, 2014 12:16:18 PM Make sure to include tank.c when compiling tankgame.c
Anonymous February 20, 2021 12:26:56 AM super nice tutorial for function pointer!
Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.