I am currently working with a C program that uses structs composed of xyz coordinates, but sometimes these coordinate may refer to vectors (Force/velocity type, not the data
What about using typedef
s?
typedef struct general3d {
float x;
float y;
float z;
} general3d_t;
typedef general3d position;
typedef general3d velocity;
This way, when you come across something that's a velocity
type, you encode that into the variable type, but under the hood, it's still just 3 points, x
, y
, z
. Then, readers of the code will know you're talking about a velocity
and not a position
. If you want to get really crazy with it, you hide general3d
away in some implementation file, so a user can never instantiate a general3d
on their own, since they should be using either position
or velocity
as the situation requires; this may or may not be reasonable for your task at hand/worth the extra effort.
EDIT: I'm not positive about variable-renaming or about adding more variables directly to the same struct, but I would start to head in the direction of a different design at that point.
On the one hand, if you have two struct
s that have the same underlying types but require different names, you probably just have two separate struct
s. For example:
struct point3d {
float x;
float y;
float z;
};
struct person {
float age;
float weight;
float salary;
};
Yes, those are both 3 floats, but their understanding is very different, and they should be able to vary on their own if one or the other changes. Perhaps I want to add a name
field to the person
, but there's no reasonable analogue for a char *
on point3d
. Just define them separately if they mean different things.
As for adding more variables, that sounds like struct
s that contain other struct
s:
struct point3d {
float x;
float y;
float z;
};
struct person {
point3d position;
float age;
float weight;
float salary;
};
// access like:
person.position.x;
I've done this sort of thing before. I embed a copy of the base type at the front of the derived type struct. This more closely mimics what c++
might do. Here are two methods I've used.
Using simple type:
#define XYZDEF \
int type; \
float x; \
float y; \
float z
// base type
struct xyzdata {
XYZDEF;
};
// derived type 1
struct vector {
XYZDEF;
int vector_info;
...
};
// derived type 2
struct position {
XYZDEF;
int position_info;
...
};
#define BASEOF(_ptr) \
((struct xyzdata *) (_ptr))
// vector_rotate -- rotate a vector
void
vector_rotate(vector *ptr)
{
}
// position_rotate -- rotate a position
void
position_rotate(position *ptr)
{
}
// xyzrotate -- rotate
void
xyzrotate(xyzdata *ptr)
{
switch (ptr->type) {
case TYPE_POSITION:
vector_rotate((vector *) ptr);
break;
case TYPE_VECTOR:
position_rotate((position *) ptr);
break;
}
}
Using a virtual function table pointer:
#define XYZDEF \
int type; \
vtbl *vtbl; \
float x; \
float y; \
float z
// forward definitions
struct xyzdata;
struct vector;
struct position;
// virtual function table
struct vtbl {
void (*rotate)(struct xyzdata *);
};
// base type
struct xyzdata {
XYZDEF;
};
// derived type 1
struct vector {
XYZDEF;
int vector_info;
...
};
// derived type 2
struct position {
XYZDEF;
int position_info;
...
};
#define BASEOF(_ptr) \
((struct xyzdata *) (_ptr))
// vector_rotate -- rotate a vector
void
vector_rotate(struct xyzdata *ptr)
{
struct vector *vec = (void *) ptr;
...
}
// position_rotate -- rotate a position
void
position_rotate(struct xyzdata *ptr)
{
struct position *pos = (void *) ptr;
...
}
// xyzrotate -- rotate
void
xyzrotate(xyzdata *ptr)
{
ptr->vtbl->rotate(ptr);
}
You can use a union
for this. Your main struct
would contain a union
of the "derived" structs as well as a "flag" field telling you which member of the union is valid:
enum { DERIVED11, DERIVED2, DERIVED3 };
struct derived1 { int x1; };
struct derived2 { char x2; };
struct derived3 { float x3; };
struct ThreeDCartesianData
{
float x;
float y;
float z;
int derivedType;
union {
struct derived1 d1;
struct derived2 d2;
struct derived3 d3;
} derived;
};
Then you can use them like this:
struct ThreeDCartesianData data1;
data1.x=0;
data1.y=0;
data1.z=0;
data1.derivedType = DERIVED1;
data1.derived.d1.x1 = 4;
You could alternately define them like this:
struct common
{
int type;
float x;
float y;
float z;
};
struct derived1
{
int type;
float x;
float y;
float z;
int x1;
};
struct derived2
{
int type;
float x;
float y;
float z;
char x2;
};
struct derived3
{
int type;
float x;
float y;
float z;
float x3;
};
union ThreeDCartesianData {
struct common c;
struct derived1 d1;
struct derived2 d2;
struct derived3 d3;
};
And use them like this:
union ThreeDCartesianData data1;
data1.c.type=DERIVED1;
data1.d1.x=0;
data1.d1.y=0;
data1.d1.z=0;
data1.d1.x1 = 4;
If all the structs in a union have an initial list elements of the same type in the same order, the standard allows you to access those fields from any of the sub-structs safely.