I'm trying to mimic classes. Is there anything with this code that I should not do with C or any ways that I can improve it at all, such as best practices or anything?
structs.h
#ifndef PLAYER_H
#define PLAYER_H
typedef struct {
char name[30];
int damage;
int durability;
} weapon;
typedef struct {
int health;
int armor_level;
int currency;
weapon player_weap;
} player;
#endif
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "structs.h"
player new_player();
void print_player(player user);
int main(void) {
player user = new_player();
print_player(user);
return 0;
}
player new_player() {
player new_player;
new_player.health = 100;
new_player.armor_level = 0;
new_player.currency = 0;
strcpy(new_player.player_weap.name, "Starter Sword");
new_player.player_weap.damage = 1;
new_player.player_weap.durability = 100;
return new_player;
}
void print_player(player user) {
printf("The players health is %d.\n", user.health);
printf("The players armor is %d.\n", user.armor_level);
printf("The players currency is %d.\n", user.currency);
printf("The players weapon is the %s.\n", user.player_weap.name);
printf("The players weapon does %d damage and has %d durability.\n", user.player_weap.damage, user.player_weap.durability);
}
-
1\$\begingroup\$ Well, which features do you want to support? Inheritance, including multiple and virtual? Late binding, using slots or names? Dynamic type information, including dynamic casting? I'm sure there are more, and different languages embrace their own flavor of their own subset of them. \$\endgroup\$Deduplicator– Deduplicator2017年12月19日 00:08:49 +00:00Commented Dec 19, 2017 at 0:08
2 Answers 2
It's pretty simple and readable as far as it goes, which is good. It doesn't do a whole lot but I have some comments that you might consider.
methods?
It's curious that you mention mimicking classes but there are no function pointers in the structs. In C it's more difficult to get the constructor in there, but print
could be a function pointer.
typedef struct {
//...
void (*print)(struct player);
//...
} player;
player new_player() {
//...
new_player.print = print_player;
//...
}
int main(){
//...
user.print(user);
//...
}
Initializers
C does have a nice initializer syntax, esp. with C99's designators. Your constructor function could be somewhat nicer by using the initializer.
player new_player() {
player new_player = {
.health = 100,
.armor_level = 0,
.currency = 0,
.player_weap = {
.name = "Starter Sword",
.damage = 1,
.durability = 100
}
};
return new_player;
}
Or even simpler with a compound literal.
player new_player() {
return (player) {
.health = 100,
.armor_level = 0,
.currency = 0,
.player_weap = {
.name = "Starter Sword",
.damage = 1,
.durability = 100
}
};
}
-
\$\begingroup\$ Thanks! Can you give me an example of a function pointer in the struct? \$\endgroup\$izzzi– izzzi2017年12月18日 07:49:05 +00:00Commented Dec 18, 2017 at 7:49
-
\$\begingroup\$ @izzzi cprogramming.com/tutorial/function-pointers.html \$\endgroup\$2017年12月18日 14:52:11 +00:00Commented Dec 18, 2017 at 14:52
-
\$\begingroup\$ @izzzi Added illustrative code. pacman's link looks pretty good too. \$\endgroup\$luser droog– luser droog2017年12月18日 21:07:45 +00:00Commented Dec 18, 2017 at 21:07
-
1\$\begingroup\$ The function pointer didnt seem to work with the typedef beacuase
void (*print)(struct player);
needs player not struct player and with just player it couldnt recognize it :/ Works great though if I switch it all back to struct player \$\endgroup\$izzzi– izzzi2017年12月18日 21:23:23 +00:00Commented Dec 18, 2017 at 21:23 -
\$\begingroup\$ Yes. Good catch. I messed that up a little. This case might work better with
typedef struct player player; struct player { ... };
as two separate statements. \$\endgroup\$luser droog– luser droog2017年12月18日 21:25:41 +00:00Commented Dec 18, 2017 at 21:25
This question stirred my curiosity about macro handling and the following horrible hack was born: a series of macros for object like structures.
Each "class" is defined in its header file and methods are defined in the corresponding .c file. All objects are passed to functions or returned from functions as pointers. In this way objects can be static, automatic or allocated.
Let's start with weapon.h:
#if !defined WEAPON_H
#define WEAPON_H
#include "objects.h"
#define CLASS weapon
MEMBERS
char name[30];
int damage;
int durability;
METHODS
void METHOD(print);
int METHOD(get_damage);
void METHOD(set_damage, int val);
#undef CLASS
#endif // WEAPON_H
Note that the class is defined, then members must follow, and later methods. This automatically defines two functions weapon_constructor, weapon_destructor which are responsible to initialize and release every resource of the class. Moreover it also creates new_weapon() which allocates and constructs and delete_weapon() which destroys and frees.
The implementations in weapon.c is:
#include "weapon.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define CLASS weapon
BEGIN_DEFINITIONS
DEFINE_CONSTRUCTOR
strcpy(self->name, "Starter Sword");
self->damage = 1;
self->durability = 100;
return self;
DEFINE_DESTRUCTOR
END_DEFINITIONS
void METHOD(print)
{
printf("The weapon is the %s.\n", self->name);
printf("The weapon does %d damage and has %d durability.\n", self->damage, self->durability);
}
int METHOD(get_damage)
{
return self->damage;
}
void METHOD(set_damage, int val)
{
self->damage = val;
}
Ok, what are all those macros? They are defined in objects.h:
#if !defined OBJECTS_H
#define OBJECTS_H
//Stuff for macro handling (https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms)
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
// ------------------------------------------------
// Requires a #define CLASS
#define OPERATOR(name, ...) CAT(name,CAT(_,CLASS))(__VA_ARGS__)
#define METHOD(name, ...) CAT(CLASS,_)##name(CLASS* self, __VA_ARGS__)
#define CALL(self, method, ...) CAT(CLASS,_)##method(self, __VA_ARGS__)
#define CONSTRUCTOR CLASS* METHOD(constructor)
#define DESTRUCTOR CLASS* METHOD(destructor)
#define NEW CLASS* OPERATOR(new)
#define DELETE void OPERATOR(delete, CLASS* obj)
#define MEMBERS typedef struct CLASS {
#define METHODS \
} CLASS; \
CONSTRUCTOR; \
DESTRUCTOR; \
NEW; \
DELETE;
#define BEGIN_DEFINITIONS
#define DEFINE_CONSTRUCTOR CONSTRUCTOR {
#define DEFINE_DESTRUCTOR return self; } DESTRUCTOR {
#define END_DEFINITIONS return self; } \
NEW { return CALL(malloc(sizeof(CLASS)), constructor); } \
DELETE { free(CALL(obj, destructor)); }
// Remember to #undef CLASS
#endif // OBJECTS_H
What about the player? It uses a weapon, so in the constructor it will construct its weapon and in the destructor it will destruct it. player.h:
#ifndef PLAYER_H
#define PLAYER_H
#include "objects.h"
#include "weapon.h"
#define CLASS player
MEMBERS
int health;
int armor_level;
int currency;
weapon weap;
METHODS
void METHOD(print);
#undef CLASS
#endif // PLAYER_H
And the corresponding player.c:
#include "player.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define CLASS player
BEGIN_DEFINITIONS
DEFINE_CONSTRUCTOR
self->health = 100;
self->armor_level = 0;
self->currency = 0;
weapon_constructor(&self->weap);
DEFINE_DESTRUCTOR
weapon_destructor(&self->weap);
END_DEFINITIONS
void METHOD(print)
{
printf("The player's health is %d.\n", self->health);
printf("The player's armor is %d.\n", self->armor_level);
printf("The player's currency is %d.\n", self->currency);
printf("Player's weapon: ");
weapon_print(&self->weap);
}
Note that in the print method it calls the print method of weapon. All methods get the class name before the method name. Their first parameter is a pointer to the object on which they should work, called self a la Python.
A test case main.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "player.h"
// Example usage of objects on free store
int main(void) {
player *user = new_player();
weapon_set_damage(&user->weap, 2);
int x = weapon_get_damage(&user->weap);
player_print(user);
delete_player(user);
return 0;
}
/*
// Example usage of objects with automatic storage duration
int main(void) {
player user;
player_constructor(&user);
weapon_set_damage(&user.weap, 2);
int x = weapon_get_damage(&user.weap);
player_print(&user);
player_destructor(&user);
return 0;
}
*/
This is a sample usage. You can follow on this horrible idea by subclassing a player in a simple way: have the first element be an element of the super class. With a little cast you can pass the derived object to the super class methods, without any problem. The problems begin when you start to need polymorphism. In this case you will definitely need to look into function pointers.
Disclaimer: I'm not supporting this idea as clever or smart. It was just a nice way to spend some time hacking C macros.
-
\$\begingroup\$ +1 for (yet another) OO macro set abusing the preprocessor. \$\endgroup\$aghast– aghast2018年01月03日 01:35:09 +00:00Commented Jan 3, 2018 at 1:35
-
\$\begingroup\$ Very cool stuff. One part makes me wonder... The
#define CLASS ...
I wonder if it could be reworked to pass this as a parameter to the macros. Maybe something likedefclass(CLASS, VARS, METHODS)
. For reference, I have a long answer about X-Macros which lies in similar territory,. With the X-macros, passing in the otherwise hidden variable is a clear win IMO. \$\endgroup\$luser droog– luser droog2018年01月12日 02:12:00 +00:00Commented Jan 12, 2018 at 2:12