【问题标题】:Emulating Classes in C using Structs使用结构模拟 C 中的类
【发布时间】:2017-10-21 11:44:20
【问题描述】:

我被限制在比赛中使用 C,并且我需要模拟类。我正在尝试构建一个简单的“点”类,它可以返回并设置一个点的 X 和 Y 坐标。然而,下面的代码返回诸如“未知类型名称点”、“预期标识符或(”和“预期参数声明符”之类的错误。这些错误是什么意思?如何纠正它们?这是编写“伪类”?

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

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

【问题讨论】:

  • 在 C 中,最好将数据保存在结构中并直接使用辅助函数。你只是在用point.<func>(&point, ...) 的东西重复自己。
  • object 是一个糟糕的名字。改为point。除非你打算让你的点多态,(点几乎从来不是多态的)你不需要让每一个函数都虚拟化。
  • 永远不会 typedef 指向数据类型的指针!
  • 为什么你的 typedef 将 setCoordinates 声明为 struct object* 类型?
  • @InternetAussie:WinAPI 是一个好的 API 的例子吗?好笑话!它们甚至不是正式的 C90 兼容,更少标准的 C。typedef 甚至不允许使用限定符正确的代码(当然,除非你想爆炸全局命名空间)..

标签: c class struct


【解决方案1】:

您最好按如下方式实现它:

#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;
}

需要注意的几点:

  • 正如@Olaf 所指出的,永远不要 typedef 指针 - 它隐藏了您的意图并使事情变得不清楚。是的,这都是糟糕的 API(例如:Windows),但它降低了可读性。
  • 您确实不需要将这些函数等同于虚函数...只需在 point“事物”上调用一组 point_*() 函数即可。
  • 不要把东西和不好的名字混为一谈……如果它是一个 X、Y 点,那么就这样称呼它 - 而不是一个对象(这是一个非常通用的概念)。
  • 您需要调用函数...在您对printf() 的调用中,您使用了point.getXCoordinate - 也就是说,您获取了它的地址并要求printf() 将其显示为float
  • 您可能开始想知道为什么要关心调用函数来访问透明结构内的变量...见下文。

许多库/API 提供不透明的数据类型。这意味着您可以获得“事物”的“句柄”......但是您不知道“事物”中存储了什么。然后该库为您提供访问功能,如下所示。这就是我建议你处理这种情况的方式。

别忘了释放内存!

我在下面实现了一个示例。

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);
}

ma​​in.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;
}

【讨论】:

    【解决方案2】:

    您的代码有一些小错误。这就是它无法编译的原因。

    固定在这里:

    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));
    }
    

    至于方法,当您可以简单地直接调用它们时,可能不需要将指向您的方法的指针存储在结构中:

    object x;
    object_setCoordinates(x, 1, 2);
    //...
    

    【讨论】:

      【解决方案3】:

      另一种编写需要多态性且每个实例开销更少的伪类的方法是创建单个虚函数表并让您的构造函数或工厂函数对其进行设置。这是一个假设的例子。 (编辑:现在是一个 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;  
      }
      

      这利用了结构的公共初始子序列可以通过指向它们中的任何一个的指针来寻址的保证,并且每个实例仅存储一个类开销指针,而不是每个虚函数一个函数指针。您可以使用虚拟表作为变体结构的类标识符。此外,虚拟表不能包含垃圾。虚函数调用需要解引用两个指针而不是一个,但是任何正在使用的类的虚表都极有可能在缓存中。

      我还注意到这个界面非常简陋;拥有一个只能转换回笛卡尔坐标的极坐标类是很愚蠢的,并且任何像这样的实现都至少需要某种方法来初始化动态内存。

      如果您不需要多态性,请参阅 Attie 的简单得多的答案。

      【讨论】:

        【解决方案4】:

        我还有一个 C 中基本类仿真的示例[为特定应用程序指定的 OP,虽然,这个答案是针对一般问题的]:

        一个名为“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
            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 (note that different functions have the same function pointer prototypes).
        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
        

        一个名为“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 a class (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);
            /* It isn't a good idea to connect the same function definitions to different class objects without using a mutex.
            However, this is a single threaded program so we aren't concerned here. 
            */
            
            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);
            //Working_Function(MyClass); //This would work also (because we're passing a pointer);
           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
        
        */
        

        【讨论】:

          猜你喜欢
          • 2012-10-20
          • 2020-10-29
          • 2016-02-07
          • 1970-01-01
          • 1970-01-01
          • 2010-12-01
          • 2017-04-18
          • 1970-01-01
          • 2021-07-08
          相关资源
          最近更新 更多