Emulating Classes in C using Structs

2019-06-14 07:43发布

I am constrained to using C for a competition and I have a need to emulate classes. I am trying to construct a simple "point" class that can return and set the X and Y coordinates of a point. Yet, the below code returns errors such as "unknown type name point", "expected identifier or (" and "expected parameter declarator." What do these errors mean? How do I correct them? Is this the correct approach to writing a "pseudo-class"?

typedef struct object object, *setCoordinates;

struct object {
    float x, y;
    void (*setCoordinates)(object *self, float x, float y);
    void (*getYCoordinate)(object *self);
    void (*getXCoordinate)(object *self);
};

void object_setCoordinates(object *self, float x, float y){
    self->x = x;
    self->y = y;
}

float object_getXCoordinate(object *self){
    return self->x;
}

float object_getYCoordinate(object *self){
    return self->y;
}

object point;
point.setCoordinates = object_setCoordinates;
point.getYCoordinate = object_getYCoordinate;
point.getXCoordinate = object_getXCoordinate;

point.setCoordinates(&point, 1, 2);
printf("Coordinates: X Coordinate: %f, Y Coordinate: %f", point.getXCoordinate, point.getYCoordinate);

Reference: 1. C - function inside struct 2. How do you implement a class in C?

标签: c class struct
3条回答
甜甜的少女心
2楼-- · 2019-06-14 08:17

You would do much better to implement it as follows:

#include <stdio.h>

struct point {
    float x;
    float y;
};

void point_setCoordinates(struct point *self, float x, float y){
    self->x = x;
    self->y = y;
}

float point_getXCoordinate(struct point *self){
    return self->x;
}

float point_getYCoordinate(struct point *self){
    return self->y;
}

int main(void) {
    struct point my_point;

    point_setCoordinates(&my_point, 1, 2);

    printf("Coordinates: X Coordinate: %f, Y Coordinate: %f\n",
           point_getXCoordinate(&my_point),
           point_getYCoordinate(&my_point));

    return 0;
}

A few things to note:

  • As @Olaf has pointed out, never typedef a pointer - it hides your intent and makes things unclear. Yes, it's all over poor APIs (e.g: Windows), but it reduces readability.
  • You really don't need these functions to be the equivalent to virtual functions... just have a set of point_*() functions that you call on the point 'thing'.
  • Don't confuse things with poor names... if it's an X,Y point, then call it such - not an object (which is a very generic concept).
  • You need to call functions... in your call to printf() you used point.getXCoordinate - that is to say you took it's address and asked printf() to display it as though it were a float
  • You might start to wonder why you'd care about calling a function to get access to a variable that is inside a transparent struct... See below.

Many libraries / APIs provide opaque datatypes. This means that you can get a 'handle' to a 'thing'... but you have no idea what's being stored within the 'thing'. The library then provides you with access functions, as shown below. This is how I'd advise you approach the situation.

Don't forget to free the memory!

I've implemented an example below.

point.h

#ifndef POINT_H
#define POINT_H

struct point;

struct point *point_alloc(void);
void point_free(struct point *self);

void point_setCoordinates(struct point *self, float x, float y);
float point_getXCoordinate(struct point *self);
float point_getYCoordinate(struct point *self);

#endif /* POINT_H */

point.c

#include <stdlib.h>
#include <string.h>

#include "point.h"

struct point {
    float x;
    float y;
};

struct point *point_alloc(void) {
    struct point *point;

    point = malloc(sizeof(*point));
    if (point == NULL) {
        return NULL;
    }

    memset(point, 0, sizeof(*point));

    return point;
}

void point_setCoordinates(struct point *self, float x, float y) {
    self->x = x;
    self->y = y;
}

float point_getXCoordinate(struct point *self) {
    return self->x;
}

float point_getYCoordinate(struct point *self) {
    return self->y;
}

void point_free(struct point *self) {
    free(self);
}

main.c

#include <stdio.h>

#include "point.h"

int main(void) {
    struct point *point;

    point = point_alloc();

    point_setCoordinates(point, 1, 2);

    printf("Coordinates: X Coordinate: %f, Y Coordinate: %f\n",
           point_getXCoordinate(point),
           point_getYCoordinate(point));

    point_free(point);

    return 0;
}
查看更多
对你真心纯属浪费
3楼-- · 2019-06-14 08:28

Another way to write a pseudo-class that needs polymorphism, with less overhead per instance, is to create a single virtual function table and have your constructor or factory function set that. Here’s a hypothetical example. (Edit: Now a MCVE, but for real code, refactor into header and separate source files.)

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

struct point; // Abstract base class.

struct point_vtable {
  void (*setCoordinates)(struct point *self, float x, float y);
  float (*getYCoordinate)(const struct point *self);
  float (*getXCoordinate)(const struct point *self);
};

typedef struct point {
  const struct point_vtable* vtable;
} point;

typedef struct cartesian_point {
  const struct point_vtable* vtable;
  float x;
  float y;
} cartesian_point;

typedef struct polar_point {
  const struct point_vtable* vtable;
  float r;
  float theta;
} polar_point;

void cartesian_setCoordinates( struct point* self, float x, float y );
float cartesian_getXCoordinate(const struct point* self);
float cartesian_getYCoordinate(const struct point* self);

void polar_setCoordinates( struct point* self, float x, float y );
float polar_getXCoordinate(const struct point* self);
float polar_getYCoordinate(const struct point* self);

const struct point_vtable cartesian_vtable = {
  .setCoordinates = &cartesian_setCoordinates,
  .getXCoordinate = &cartesian_getXCoordinate,
  .getYCoordinate = &cartesian_getYCoordinate
};

const struct point_vtable polar_vtable = {
  .setCoordinates = &polar_setCoordinates,
  .getXCoordinate = &polar_getXCoordinate,
  .getYCoordinate = &polar_getYCoordinate
};

void cartesian_setCoordinates( struct point* const self,
                               const float x,
                               const float y )
{
  assert(self->vtable == &cartesian_vtable);
  struct cartesian_point * const this = (struct cartesian_point*)self;
  this->x = x;
  this->y = y;
}

float cartesian_getXCoordinate(const struct point* const self)
{
  assert(self->vtable == &cartesian_vtable);
  const struct cartesian_point * const this = (struct cartesian_point*)self;
  return this->x;
}

float cartesian_getYCoordinate(const struct point* const self)
{
  assert(self->vtable == &cartesian_vtable);
  const struct cartesian_point * const this = (struct cartesian_point*)self;
  return this->y;
}

void polar_setCoordinates( struct point* const self,
                           const float x,
                           const float y )
{
  assert(self->vtable == &polar_vtable);
  struct polar_point * const this = (struct polar_point*)self;
  this->theta = (float)atan2((double)y, (double)x);
  this->r = (float)sqrt((double)x*x + (double)y*y);
}

float polar_getXCoordinate(const struct point* const self)
{
  assert(self->vtable == &polar_vtable);
  const struct polar_point * const this = (struct polar_point*)self;
  return (float)((double)this->r * cos((double)this->theta));
}

float polar_getYCoordinate(const struct point* const self)
{
  assert(self->vtable == &polar_vtable);
  const struct polar_point * const this = (struct polar_point*)self;
  return (float)((double)this->r * sin((double)this->theta));
}

// Suitable for the right-hand side of initializations, before the semicolon.
#define CARTESIAN_POINT_INITIALIZER { .vtable = &cartesian_vtable,\
                                      .x = 0.0F, .y = 0.0F }
#define POLAR_POINT_INITIALIZER { .vtable = &polar_vtable,\
                                  .r = 0.0F, .theta = 0.0F }

int main(void)
{
  polar_point another_point = POLAR_POINT_INITIALIZER;
  point* const p = (point*)&another_point; // Base class pointer.
  polar_setCoordinates( p, 0.5F, 0.5F ); // Static binding.
  const float x = p->vtable->getXCoordinate(p); // Dynamic binding.
  const float y = p->vtable->getYCoordinate(p); // Dynamic binding.

  printf( "(%f, %f)\n", x, y );
  return EXIT_SUCCESS;  
}

This takes advantage of the guarantee that the common initial subsequence of structs can be addressed through a pointer to any of them, and stores only one pointer of class overhead per instance, not one function pointer per virtual function. You can use the virtual table as your class identifier for your variant structure. Also, the virtual table cannot contain garbage. Virtual function calls need to dereference two pointers rather than one, but the virtual table of any class in use is highly likely to be in the cache.

I also note that this interface is very skeletal; it’s silly to have a polar class that can do nothing but convert back to Cartesian coordinates, and any implementation like this would at minimum need some way to initialize dynamic memory.

If you don’t need polymorphism, see Attie’s much simpler answer.

查看更多
Fickle 薄情
4楼-- · 2019-06-14 08:34

Your code has some minor errors. That's why it doesn't compile.

Fixed here:

typedef struct object object;

struct object {
    float x, y;
    void (*setCoordinates)(object *self, float x, float y);
    float (*getYCoordinate)(object *self);
    float (*getXCoordinate)(object *self);
};

void object_setCoordinates(object *self, float x, float y){
    self->x = x;
    self->y = y;
}

float object_getXCoordinate(object *self){
    return self->x;
}

float object_getYCoordinate(object *self){
    return self->y;
}

int main()
{

    object point;
    point.setCoordinates = object_setCoordinates;
    point.getYCoordinate = object_getYCoordinate;
    point.getXCoordinate = object_getXCoordinate;

    point.setCoordinates(&point, 1, 2);
    printf("Coordinates: X Coordinate: %f, Y Coordinate: %f", 
    point.getXCoordinate(&point), point.getYCoordinate(&point));
}

As for the approach, there's probably no need to store the pointers to your methods inside the struct when you can simply call them directly:

object x;
object_setCoordinates(x, 1, 2);
//...
查看更多
登录 后发表回答