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
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!
this looks very interesting! However, I couldn't get it working (tankgame.c:(.text+0xe): undefined reference to `new_Tank').
Email me please!
OO