resolucion - miembros de una clase en programacion orientada a objetos
¿Cómo se implementa una clase en C? (19)
¿Quieres métodos virtuales?
Si no, entonces simplemente define un conjunto de punteros de funciones en la propia estructura. Si asigna todos los punteros a las funciones C estándar, podrá llamar funciones desde C en sintaxis muy similar a como lo haría bajo C ++.
Si quieres tener métodos virtuales, se vuelve más complicado. Básicamente, deberá implementar su propia tabla VT a cada estructura y asignar punteros a la tabla V, según la función a la que se llame. Entonces necesitaría un conjunto de punteros de función en la propia estructura que a su vez llama al puntero de función en la tabla VTable. Esto es, esencialmente, lo que hace C ++.
Sin embargo, TBH ... si quieres lo último, probablemente sea mejor que encuentres un compilador de C ++ que puedas usar y volver a compilar el proyecto. Nunca he entendido la obsesión de que C ++ no se pueda usar en embedded. Lo he usado muchas veces y funciona rápido y no tiene problemas de memoria. Claro que tienes que ser un poco más cuidadoso con lo que haces, pero realmente no es tan complicado.
Asumiendo que tengo que usar C (sin compiladores C ++ u orientados a objetos) y no tengo asignación de memoria dinámica, ¿cuáles son algunas de las técnicas que puedo usar para implementar una clase, o una buena aproximación de una clase? ¿Siempre es una buena idea aislar la "clase" en un archivo separado? Supongamos que podemos preasignar la memoria suponiendo un número fijo de instancias, o incluso definiendo la referencia a cada objeto como una constante antes del tiempo de compilación. Siéntase libre de hacer suposiciones sobre qué concepto de POO necesitaré implementar (variará) y sugerir el mejor método para cada uno.
Restricciones
- Tengo que usar C y no un OOP porque estoy escribiendo código para un sistema incrustado, y el compilador y la base de código preexistente están en C.
- No hay una asignación de memoria dinámica porque no tenemos suficiente memoria para suponer razonablemente que no se nos agotará si comenzamos a asignarla dinámicamente.
- Los compiladores con los que trabajamos no tienen problemas con los indicadores de función
C no es un lenguaje de programación orientada a objetos (OOP), como bien lo señala, por lo que no hay una forma integrada de escribir una clase verdadera. Lo mejor es mirar las structs y los indicadores de función , estos le permitirán construir una aproximación de una clase. Sin embargo, como C es de procedimiento, es posible que desee considerar escribir más código tipo C (es decir, sin intentar usar clases).
Además, si puede usar C, puede usar C ++ probalmente y obtener clases.
Daré un ejemplo simple de cómo OOP se debe hacer en C. Me doy cuenta de que este thead es de 2009, pero me gustaría añadir esto de todos modos.
/// Object.h
typedef struct Object {
uuid_t uuid;
} Object;
int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);
/// Person.h
typedef struct Person {
Object obj;
char *name;
} Person;
int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);
/// Object.c
#include "object.h"
int Object_init(Object *self)
{
self->uuid = uuid_new();
return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don''t actually create getters in C...
return self->uuid;
}
int Object_clean(Object *self)
{
uuid_free(self->uuid);
return 0;
}
/// Person.c
#include "person.h"
int Person_init(Person *self, char *name)
{
Object_init(&self->obj); // Or just Object_init(&self);
self->name = strdup(name);
return 0;
}
int Person_greet(Person *self)
{
printf("Hello, %s", self->name);
return 0;
}
int Person_clean(Person *self)
{
free(self->name);
Object_clean(self);
return 0;
}
/// main.c
int main(void)
{
Person p;
Person_init(&p, "John");
Person_greet(&p);
Object_get_uuid(&p); // Inherited function
Person_clean(&p);
return 0;
}
El concepto básico implica colocar la ''clase heredada'' en la parte superior de la estructura. De esta forma, el acceso a los primeros 4 bytes en la estructura también accede a los primeros 4 bytes en la ''clase heredada'' (Asumiendo optimizaciones no locas). Ahora, cuando el puntero de la estructura se convierte en la ''clase heredada'', la ''clase heredada'' puede acceder a los ''valores heredados'' de la misma forma que accedería a sus miembros normalmente.
Esta y algunas convenciones de nomenclatura para constructores, destructores, asignación y funciones deallocarion (recomiendo init, clean, new, free) te llevarán por un largo camino.
En cuanto a las funciones virtuales, use punteros de función en la estructura, posiblemente con Class_func (...); envoltorio también. En cuanto a las plantillas (simples), agregue un parámetro size_t para determinar el tamaño, requiera un puntero nulo * o requiera un tipo de ''clase'' con solo la funcionalidad que le interese. (por ej. int GetUUID (Object * self); GetUUID (& p);)
El primer compilador de C ++ en realidad fue un preprocesador que tradujo el código de C ++ a C.
Por lo tanto, es muy posible tener clases en C. Podría intentar desenterrar un viejo preprocesador de C ++ y ver qué tipo de soluciones crea.
En su caso, la buena aproximación de la clase podría ser un ADT . Pero aún así no será lo mismo.
Eso depende del conjunto de características "orientado a objetos" exacto que desee tener. Si necesita cosas como sobrecarga y / o métodos virtuales, probablemente necesite incluir indicadores de función en las estructuras:
typedef struct {
float (*computeArea)(const ShapeClass *shape);
} ShapeClass;
float shape_computeArea(const ShapeClass *shape)
{
return shape->computeArea(shape);
}
Esto le permitiría implementar una clase, "heredando" la clase base e implementando una función adecuada:
typedef struct {
ShapeClass shape;
float width, height;
} RectangleClass;
static float rectangle_computeArea(const ShapeClass *shape)
{
const RectangleClass *rect = (const RectangleClass *) shape;
return rect->width * rect->height;
}
Esto, por supuesto, requiere que también implemente un constructor, que se asegure de que el puntero de la función esté configurado correctamente. Normalmente, asignarás dinámicamente memoria para la instancia, pero también puedes permitir que la persona que llama lo haga:
void rectangle_new(RectangleClass *rect)
{
rect->width = rect->height = 0.f;
rect->shape.computeArea = rectangle_computeArea;
}
Si quieres varios constructores diferentes, tendrás que "decorar" los nombres de las funciones, no puedes tener más de una función rectangle_new()
:
void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
rectangle_new(rect);
rect->width = width;
rect->height = height;
}
Aquí hay un ejemplo básico que muestra el uso:
int main(void)
{
RectangleClass r1;
rectangle_new_with_lengths(&r1, 4.f, 5.f);
printf("rectangle r1''s area is %f units square/n", shape_computeArea(&r1));
return 0;
}
Espero que esto te dé algunas ideas, al menos. Para obtener un marco orientado a objetos rico y exitoso en C, busque en la biblioteca GObject de glib.
También tenga en cuenta que no hay una "clase" explícita modelada anteriormente, cada objeto tiene sus propios punteros de método, que es un poco más flexible de lo que normalmente encontraría en C ++. Además, cuesta memoria. Puede alejarse de eso rellenando los punteros de método en una estructura de class
e inventar una forma para que cada instancia de objeto haga referencia a una clase.
GTK está construido completamente en C y utiliza muchos conceptos de OOP. He leído el código fuente de GTK y es bastante impresionante, y definitivamente más fácil de leer. El concepto básico es que cada "clase" es simplemente una estructura y funciones estáticas asociadas. Todas las funciones estáticas aceptan la estructura de "instancia" como parámetro, hacen lo que sea necesario y devuelven los resultados si es necesario. Por ejemplo, puede tener una función "GetPosition (CircleStruct obj)". La función simplemente cavaría a través de la estructura, extraería los números de posición, probablemente construiría un nuevo objeto PositionStruct, pegaría la xey en el nuevo PositionStruct y lo devolvería. GTK incluso implementa la herencia de esta manera mediante la incrustación de estructuras dentro de las estructuras. muy inteligente.
Hay un libro muy extenso sobre el tema, que podría valer la pena visitar:
Mi enfoque sería mover la struct
y todas las funciones primordialmente asociadas a un archivo (s) fuente separado para que pueda ser utilizado "portably".
Dependiendo de tu compilador, podrías incluir funciones en la struct
, pero esa es una extensión muy específica del compilador, y no tiene nada que ver con la última versión del estándar que utilicé habitualmente :)
Mi estrategia es:
- Definir todo el código para la clase en un archivo separado
- Definir todas las interfaces para la clase en un archivo de encabezado separado
- Todas las funciones miembro toman un "ClassHandle" que representa el nombre de instancia (en lugar de o.foo (), call foo (oHandle)
- El constructor se reemplaza por una función void ClassInit (ClassHandle h, int x, int y, ...) O ClassHandle ClassInit (int x, int y, ...) según la estrategia de asignación de memoria
- Todas las variables miembro se almacenan como miembros de una estructura estática en el archivo de clase, encapsulándolo en el archivo, evitando que los archivos externos accedan a él
- Los objetos se almacenan en una matriz de la estructura estática anterior, con identificadores predefinidos (visibles en la interfaz) o un límite fijo de objetos que se pueden instanciar
- Si es útil, la clase puede contener funciones públicas que recorrerán la matriz y llamarán a las funciones de todos los objetos instanciados (RunAll () llama a cada ejecución (oHandle)
- Una función Deinit (ClassHandle h) libera la memoria asignada (índice de matriz) en la estrategia de asignación dinámica
¿Alguien ve algún problema, agujeros, peligros potenciales o beneficios / inconvenientes ocultos para cualquiera de las variaciones de este enfoque? Si estoy reinventando un método de diseño (y supongo que debo estarlo), ¿puede indicarme su nombre?
Miro Samek desarrolló un marco de C orientado a objetos para su marco de máquina de estado: http://sourceforge.net/projects/qpc/ . Y también escribió un libro sobre él: http://www.state-machine.com/psicc2/ .
Puede ser este libro en línea podría ayudarle a resolver su problema.
Si solo desea una clase, use una matriz de struct
como los datos de "objetos" y páseles punteros a las funciones de "miembro". Puede usar typedef struct _whatever Whatever
antes de declarar struct _whatever
para ocultar la implementación del código del cliente. No hay diferencia entre dicho "objeto" y el objeto FILE
biblioteca estándar C.
Si desea más de una clase con herencia y funciones virtuales, entonces es común tener punteros a las funciones como miembros de la estructura, o un puntero compartido a una tabla de funciones virtuales. La biblioteca GObject utiliza este truco y el truco typedef, y es ampliamente utilizado.
También hay un libro sobre técnicas para este disponible en línea: Programación Orientada a Objetos con ANSI C.
Tuve que hacerlo una vez también para hacer la tarea. Seguí este enfoque:
- Defina sus miembros de datos en una estructura.
- Defina los miembros de su función que toman un puntero a su estructura como primer argumento.
- Haga esto en un encabezado y una c. Encabezado para definición de estructuras y declaraciones de funciones, c para implementaciones.
Un ejemplo simple sería este:
/// Queue.h
struct Queue
{
/// members
}
typedef struct Queue Queue;
void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///
Use una struct
para simular los miembros de datos de una clase. En términos del alcance del método, puede simular métodos privados colocando los prototipos de funciones privadas en el archivo .c y las funciones públicas en el archivo .h.
Ver también esta respuesta y esta
Es posible. Siempre parece una buena idea en ese momento, pero luego se convierte en una pesadilla de mantenimiento. Su código se llena de pedazos de código que lo une todo. Un programador nuevo tendrá muchos problemas para leer y comprender el código si usa punteros a función ya que no será obvio a qué funciones se llama.
La ocultación de datos con funciones get / set es fácil de implementar en C, pero se detiene allí. He visto varios intentos de esto en el entorno integrado y, al final, siempre es un problema de mantenimiento.
Como todos ustedes tienen problemas de mantenimiento, me mantendría alejado.
puedes echarle un vistazo a GOBject. es una biblioteca de sistema operativo que le da una forma detallada de hacer un objeto.
C Interfaces e implementaciones: técnicas para crear software reutilizable , David R. Hanson
Este libro hace un excelente trabajo al cubrir tu pregunta. Está en la serie Addison Wesley Professional Computing.
El paradigma básico es algo como esto:
/* for data structure foo */
FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>
/**
* Define Shape class
*/
typedef struct Shape Shape;
struct Shape {
/**
* Variables header...
*/
double width, height;
/**
* Functions header...
*/
double (*area)(Shape *shape);
};
/**
* Functions
*/
double calc(Shape *shape) {
return shape->width * shape->height;
}
/**
* Constructor
*/
Shape _Shape() {
Shape s;
s.width = 1;
s.height = 1;
s.area = calc;
return s;
}
/********************************************/
int main() {
Shape s1 = _Shape();
s1.width = 5.35;
s1.height = 12.5462;
printf("Hello World/n/n");
printf("User.width = %f/n", s1.width);
printf("User.height = %f/n", s1.height);
printf("User.area = %f/n/n", s1.area(&s1));
printf("Made with /xe2/x99/xa5 /n");
return 0;
};