简体   繁体   English

使用结构模拟 C 中的类

[英]Emulating Classes in C using Structs

I am constrained to using C for a competition and I have a need to emulate classes.我被限制在比赛中使用 C,我需要模拟课程。 I am trying to construct a simple "point" class that can return and set the X and Y coordinates of a point.我正在尝试构建一个简单的“点”类,它可以返回并设置一个点的 X 和 Y 坐标。 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?参考: 1. C - struct 内的函数 2. 如何在 C 中实现类?

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.正如@Olaf 所指出的,永远不要 typedef 指针 - 它隐藏了你的意图并使事情变得不清楚。 Yes, it's all over poor APIs (eg: Windows), but it reduces readability.是的,它到处都是糟糕的 API(例如:Windows),但它降低了可读性。
  • 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'.你真的不需要这些函数等同于虚函数......只需有一组你在point “事物”上调用的point_*()函数。
  • 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).不要将事物与糟糕的名称混淆......如果它是 X,Y 点,那么就这样称呼它 - 而不是对象(这是一个非常通用的概念)。
  • 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您需要调用函数...在调用printf()时,您使用point.getXCoordinate - 也就是说,您获取了它的地址并要求printf()将其显示为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.许多库/API 提供不透明的数据类型。 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点.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点.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;
}

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);
//...

I also have an example of basic class emulation in C [the OP specified for a specific application, although, this answer is to the general question]:我还有一个 C 中基本类仿真的示例 [为特定应用程序指定的 OP,虽然,这个答案是针对一般问题的]:

A header file called "c_class.h"一个名为“c_class.h”的头文件

#ifndef CLASS_HEADER_H
#define CLASS_HEADER_H

// Function pointer prototypes used by these classes
typedef int sub_func_t (int);
typedef float sub_funcf_t (int,int);

/* class type definition 
  (emulated class type definition; C doesn't really have class types) */
typedef struct {
    //Data Variables
    int a;

    /*Function (also known as Method) pointers
      (note that different functions have the same function pointer prototype)*/
    sub_func_t* add;
    sub_func_t* subt;
    sub_func_t* mult;
    sub_funcf_t* div;  
} class_name;


// class init prototypes
// These inits connect the function pointers to specific functions
// and initialize the variables.
class_name* class_init_ptr (int, sub_func_t*, sub_func_t*, sub_func_t*, sub_funcf_t*);
class_name class_init (int, sub_func_t*, sub_func_t*, sub_func_t*, sub_funcf_t*);

#endif

A source code file called "c_class.c"一个名为“c_class.c”的源代码文件

//gcc -o c_class c_class.c

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"c_class.h"

// The class function definitions.

/*
    If we make these member functions static then they are only 
    accessible via code from this file.
    However, we can still pass the class-like objects around a 
    larger program and access their member functions,
    just like in any OO language.
    
    It is possible to emulate inheritance by declaring a class object 
    from the class type definition (I don't touch on these more 
    abstract subjects though, this is only a basic class emulation).
*/
    
static int AddFunc(int num){
    num++;
    return num;
}

static int SubtFunc(int num){
    num--;
    return num;  
}

static int MultFunc(int num){
    num *= num;
    return num;
}

static float DivFunc(int num, int denom){
    float fnum = (float)num / (float)denom;
    return fnum;  
}

// The class init function definitions.
class_name* class_init_ptr (int num, sub_func_t* addition, sub_func_t* subtraction, sub_func_t* multiplication, sub_funcf_t* division) 
{ 
    class_name* new_class = malloc(sizeof(*new_class)); 
    assert(new_class != NULL);
    *new_class = (class_name){num, addition, subtraction, multiplication, division};
    /*We could also just type:
    new_class->a = num;
    new_class->add = addition;
    new_class->subt = subtraction;   
    new_class->mult = multiplication; 
    new_class->div = division;  
    */
    return new_class; 
}

class_name class_init(int num, sub_func_t* addition, sub_func_t* subtraction, sub_func_t* multiplication, sub_funcf_t* division) 
{ 
    class_name new_class; 
    new_class = (class_name){num, addition, subtraction, multiplication, division};
    /* We could also just type:
    new_class.a = num;
    new_class.add = addition;
    new_class.subt = subtraction;   
    new_class.mult = multiplication; 
    new_class.div = division;  
    */
    return new_class; 
}

//Working Function Prototypes
class_name* Working_Function(class_name*);
class_name Working_Function_Two(class_name);

int main(){
    /* It's possible to connect the functions within the init also,
       w/o sending them. */
    class_name *MyClass = class_init_ptr(5, AddFunc, SubtFunc, MultFunc, DivFunc);
    class_name MyOtherClass = class_init(0, AddFunc, SubtFunc, MultFunc, DivFunc);
    
    printf("%i\n",MyClass->add(100));// 101
    
    printf("%i\n",MyClass->subt(100));// 99

    printf("%i\n",MyClass->mult(100));// 10000

    printf("%f\n",MyClass->div(MyClass->a,2)); // 2.5
    
    printf("%i\n",MyClass->mult(MyClass->mult(100))); //100000000

    MyClass = Working_Function(MyClass);
    //This would work also (because we're passing a pointer):
    //Working_Function(MyClass); 
   printf("%i\n",MyClass->a); //a = 5000

    MyOtherClass = Working_Function_Two(MyOtherClass);
    printf("%i\n",MyOtherClass.a); //a = 9999

    MyOtherClass.a = 25;
    Working_Function_Two(MyOtherClass); //pass by value
    printf("%i\n",MyOtherClass.a); //a = 25  (no value change)

    Working_Function(&MyOtherClass); //pass by reference
    printf("%i\n",MyOtherClass.a); //a = 5000 (value changed)

    return 0;
}

//Working Functions
class_name* Working_Function(class_name* PassedClass){
    printf("%i\n",PassedClass->a);// 5, then 25
    printf("%i\n",PassedClass->add(PassedClass->a));// 6, then 26
    PassedClass->a = 5000;
    return PassedClass;
}

class_name Working_Function_Two(class_name PassedClass){
    printf("%i\n",PassedClass.a);// 0, then 25
    printf("%i\n",PassedClass.add(PassedClass.a));// 1, then 26
    PassedClass.a = 9999;
    return PassedClass;
}

/* We're passing emulated class objects and emulated class pointers 
   by reference and value, if everything works it should print this:

101
99
10000
2.500000
100000000
5
6
5000
0
1
9999
25
26
25
25
26
5000

*/

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.) 编辑:现在是 MCVE,但对于真正的代码,重构为头文件和单独的源文件。)

#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.如果您不需要多态性,请参阅 Attie 的更简单的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM