¿Cómo escribiría un código orientado a objetos en C? [cerrado]


500

¿Cuáles son algunas formas de escribir código orientado a objetos en C? Especialmente con respecto al polimorfismo.


Véase también esta pregunta desbordamiento de pila La orientación a objetos en C .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Programación orientada a objetos en C </a> por Laurent Deniau


25
@Camilo Martin: Me preguntó intencionadamente puede no deberían . No estoy realmente interesado en usar OOP en C. Sin embargo, al ver las soluciones de OO en C, yo / nosotros aprendemos más sobre los límites y / o flexibilidad de C y también sobre formas creativas de implementar y usar el polimorfismo.
Dinah

55
OO es solo un patrón. Marque aquí, incluso se puede hacer en archivos .bat: dirk.rave.org/chap9.txt ( creo que cualquier patrón se puede aplicar a cualquier lenguaje de programación si está lo suficientemente interesado). Sin embargo, esta es una buena comida para pensar. Y probablemente se pueda aprender mucho aplicando patrones que damos por sentado en idiomas que no los tienen.
Camilo Martin

66
GTK - 'perdón, GObject - es en realidad un muy buen ejemplo de OOP (más o menos) en C. Entonces, para responder a @Camilo, por la interpoliabilidad de C.
nuevo123456

Respuestas:


362

Si. De hecho, Axel Schreiner proporciona su libro "Programación orientada a objetos en ANSI-C" de forma gratuita, que cubre el tema con bastante profundidad.


28
Si bien los conceptos de este libro son sólidos, perderá la seguridad del tipo.
diapir

22
Antes de lo que conocemos como patrones de diseño, era el patrón de diseño conocido como "orientación a objetos"; lo mismo con la recolección de basura, y otros similares. Ahora están tan arraigados que tendemos a olvidar que, cuando se diseñaron por primera vez, era de la misma manera que con lo que hoy consideramos patrones de diseño
Dexygen

11
Puede obtenerlo directamente del sitio del autor: cs.rit.edu/~ats/books/ooc.pdf otros documentos del mismo autor: cs.rit.edu/~ats/books/index.html
pakman

10
La colección adecuada (Libro + ejemplos de código fuente) está disponible en este índice rit.edu Programación orientada a objetos con ANSI-C
David C. Rankin

3
¿Este libro es revisado por pares? Hay un error tipográfico en la primera oración del primer párrafo de la primera página.
Dagrooms

343

Ya que estás hablando de polimorfismo, entonces sí, puedes, estábamos haciendo ese tipo de cosas años antes de que surgiera C ++.

Básicamente, utiliza a structpara mantener los datos y una lista de punteros de función para apuntar a las funciones relevantes para esos datos.

Por lo tanto, en una clase de comunicaciones, tendría una llamada abierta, de lectura, escritura y cierre que se mantendría como cuatro punteros de función en la estructura, junto con los datos para un objeto, algo así como:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

Por supuesto, esos segmentos de código anteriores estarían realmente en un "constructor" como rs232Init().

Cuando 'hereda' de esa clase, simplemente cambia los punteros para señalar sus propias funciones. Todos los que llamaron a esas funciones lo harían a través de los punteros de función, dándole su polimorfismo:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Algo así como una vtable manual.

Incluso podría tener clases virtuales configurando los punteros a NULL: el comportamiento sería ligeramente diferente a C ++ (un volcado de núcleo en tiempo de ejecución en lugar de un error en tiempo de compilación).

Aquí hay un fragmento de código de muestra que lo demuestra. Primero la estructura de clase de nivel superior:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

Luego tenemos las funciones para la 'subclase' de TCP:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

Y el HTTP también:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

Y finalmente un programa de prueba para mostrarlo en acción:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

Esto produce la salida:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

para que pueda ver que se llaman las diferentes funciones, dependiendo de la subclase.


52
La encapsulación es bastante fácil, el polimorfismo es factible, pero la herencia es complicada
Martin Beckett

55
lwn.net publicó recientemente un artículo titulado Patrones de diseño orientados a objetos en el núcleo sobre el tema de los conceptos similares a la respuesta anterior, es decir, una estructura que contiene punteros de función o un puntero a una estructura que tiene funciones que llevan un puntero al struct con los datos con los que estamos trabajando como parámetro.
radicalmatt

11
+1 ¡Buen ejemplo! Aunque si alguien realmente quiere seguir este camino, sería más apropiado que las estructuras de "instancia" tengan un solo campo que apunte a su instancia de "tabla virtual", que contenga todas las funciones virtuales para ese tipo en un solo lugar. Es decir tCommClass, se cambiaría su nombre tCommVTy una tCommClassestructura solo tendría campos de datos y un solo tCommVT vtcampo que apuntara a la "única tabla virtual". Llevar todos los punteros con cada instancia agrega una sobrecarga innecesaria y se asemeja más a cómo haría cosas en JavaScript que C ++, en mi humilde opinión.
Groo

1
Entonces, esto demuestra la implementación de una única interfaz, pero ¿qué pasa con la implementación de múltiples interfaces? ¿O herencia múltiple?
weberc2

Weber, si quieres toda la funcionalidad de C ++, probablemente deberías estar usando C ++. La pregunta se hizo específicamente sobre el polimorfismo, la capacidad de los objetos para tomar una "forma" diferente. Ciertamente, puede hacer interfaces y herencia múltiple en C, pero es un poco de trabajo extra, y debe administrar la inteligencia usted mismo en lugar de usar cosas integradas en C ++.
paxdiablo

86

Los espacios de nombres a menudo se hacen haciendo:

stack_push(thing *)

en vez de

stack::push(thing *)

Para convertir una estructura C en algo así como una clase C ++ , puede convertir:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

Dentro

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

Y hacer:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

No hice el destructor ni eliminé, pero sigue el mismo patrón.

this_is_here_as_an_example_only es como una variable de clase estática, compartida entre todas las instancias de un tipo. Todos los métodos son realmente estáticos, excepto que algunos toman esto *


1
@nategoose - en st->my_type->push(st, thing2);lugar dest->my_type.push(st, thing2);
Fabricio

@nategoose: O en struct stack_type my_type; lugar destruct stack_type * my_type;
Fabricio

3
Me gusta el concepto de tener una estructura para la clase. ¿Pero qué tal una Classestructura genérica ? Eso haría que el OO C sea más dinámico que C ++. ¿Qué hay sobre eso? Por cierto, +1.
Linuxios

54

Creo que, además de ser útil por derecho propio, implementar OOP en C es una excelente manera de aprender OOP y comprender su funcionamiento interno. La experiencia de muchos programadores ha demostrado que para usar una técnica de manera eficiente y segura, un programador debe comprender cómo se implementan los conceptos subyacentes. Emular clases, herencia y polimorfismo en C enseña exactamente esto.

Para responder a la pregunta original, aquí hay un par de recursos que enseñan cómo hacer POO en C:

La publicación del blog EmbeddedGurus.com "Programación basada en objetos en C" muestra cómo implementar clases y herencia única en C portátil: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c / /

La nota de aplicación "" C + "- Programación orientada a objetos en C" muestra cómo implementar clases, herencia única y enlace tardío (polimorfismo) en C utilizando macros de preprocesador: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , el código de ejemplo está disponible en http://www.state-machine.com/resources/cplus_3.0.zip


44
Nueva url para el manual de C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Lo he visto hecho. No lo recomendaría C ++ originalmente comenzó de esta manera como un preprocesador que produjo código C como un paso intermedio.

Esencialmente, lo que termina haciendo es crear una tabla de despacho para todos sus métodos donde almacena sus referencias de función. Obtener una clase implicaría copiar esta tabla de despacho y reemplazar las entradas que desea anular, con sus nuevos "métodos" teniendo que llamar al método original si desea invocar el método base. Finalmente, terminas reescribiendo C ++.


55
"Eventualmente, terminas reescribiendo C ++" Me preguntaba si temía que ese fuera el caso.
Dinah

39
O bien, podría terminar reescribiendo el Objetivo C, que sería un resultado mucho más atractivo.
contrato del Prof. Falken incumplió el

3
Existe el sabor sin clase de OOP, como en Javascript , donde el gurú dice: "No necesitamos clases para hacer muchos objetos similares". Pero me temo que esto no es fácil de lograr en C. Sin embargo, todavía no estoy en condiciones de decirlo. (¿Hay una rutina clone () para clonar una estructura?)
Lumi

1
Otros tipos inteligentes, que tuvieron que implementar eso y hacer que esa implementación sea rápida (Google, motor V8) han hecho todo lo posible para agregar clases (ocultas) a JavaScript.
cubuspl42

¿No está glibescrito en C de manera objetiva?
Kravemir

26

Claro que es posible. Esto es lo que hace GObject , el marco en el que se basan todos los GTK + y GNOME .


¿Cuáles son las ventajas y desventajas de este enfoque? Es decir. es mucho más fácil escribirlo usando C ++.
Kravemir

@kravemir Bueno, C ++ no es tan portátil como C, y es un poco más difícil vincular C ++ al código que podría ser compilado por un compilador de C ++ diferente. Pero sí, es más fácil escribir clases en C ++, aunque GObject tampoco es realmente tan difícil (suponiendo que no le importe un poco de caldera).
Edwin Buck

17

La subbiblioteca C stdio FILE es un excelente ejemplo de cómo crear abstracción, encapsulación y modularidad en C. no adulterado

La herencia y el polimorfismo, los otros aspectos que a menudo se consideran esenciales para la OOP, no proporcionan necesariamente los aumentos de productividad que prometen y se han hecho argumentos razonables de que pueden obstaculizar el desarrollo y pensar sobre el dominio del problema.


¿No se extrae stdio en la capa del núcleo? Si no me equivoco, la biblioteca C los trata como archivos / dispositivos de caracteres, y los controladores del núcleo hacen el trabajo, ...
Kravemir

15

Ejemplo trivial con un animal y un perro: usted refleja el mecanismo vtable de C ++ (en gran medida de todos modos) También separa la asignación y la creación de instancias (Animal_Alloc, Animal_New) para que no llamemos a malloc () varias veces. También debemos pasar explícitamente elthis puntero.

Si tuviera que hacer funciones no virtuales, eso es trivial. Simplemente no los agrega a vtable y las funciones estáticas no requieren unthis puntero. La herencia múltiple generalmente requiere múltiples vtables para resolver ambigüedades.

Además, debería poder usar setjmp / longjmp para hacer el manejo de excepciones.

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PD. Esto se prueba en un compilador de C ++, pero debería ser fácil hacer que funcione en un compilador de C.


typedefdentro de a structno es posible en C.
masoud

13

Echa un vistazo a GObject . Está destinado a ser OO en C y una implementación de lo que estás buscando. Sin embargo, si realmente quieres OO, ve con C ++ o algún otro lenguaje OOP. A veces es muy difícil trabajar con GObject si estás acostumbrado a lidiar con lenguajes OO, pero como todo, te acostumbrarás a las convenciones y al flujo.


12

Esto ha sido interesante de leer. He estado reflexionando sobre la misma pregunta, y los beneficios de pensarlo son los siguientes:

  • Intentar imaginar cómo implementar conceptos de OOP en un lenguaje que no sea de OOP me ayuda a comprender las fortalezas del lenguaje de OOP (en mi caso, C ++). Esto me ayuda a juzgar mejor si usar C o C ++ para un tipo de aplicación dado, donde los beneficios de uno superan al otro.

  • En mi navegación por la web para obtener información y opiniones sobre esto, encontré un autor que estaba escribiendo código para un procesador incorporado y solo tenía un compilador de C disponible: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Creación-Fundación-Clases-Parte-1

En su caso, analizar y adaptar los conceptos de OOP en el plano C era una búsqueda válida. Parece que estaba abierto a sacrificar algunos conceptos de OOP debido al impacto en el rendimiento que resulta de intentar implementarlos en C.

La lección que he tomado es, sí, se puede hacer hasta cierto punto, y sí, hay algunas buenas razones para intentarlo.

Al final, la máquina está haciendo girar los bits del puntero de la pila, haciendo que el contador del programa salte y calculando las operaciones de acceso a la memoria. Desde el punto de vista de la eficiencia, cuantos menos cálculos realice su programa, mejor ... pero a veces tenemos que pagar este impuesto simplemente para poder organizar nuestro programa de una manera que lo haga menos susceptible al error humano. El compilador de lenguaje OOP se esfuerza por optimizar ambos aspectos. El programador debe ser mucho más cuidadoso al implementar estos conceptos en un lenguaje como C.


10

Puede que le resulte útil consultar la documentación de Apple para su conjunto de API de Core Foundation. Es una API de C pura, pero muchos de los tipos están unidos a equivalentes de objeto Objective-C.

También puede resultarle útil observar el diseño de Objective-C. Es un poco diferente de C ++ en que el sistema de objetos se define en términos de funciones de C, por ejemplo, objc_msg_sendpara llamar a un método en un objeto. El compilador traduce la sintaxis de corchetes en esas llamadas de función, por lo que no tiene que saberlo, pero teniendo en cuenta su pregunta, puede resultarle útil aprender cómo funciona bajo el capó.


10

Hay varias técnicas que se pueden usar. El más importante es más cómo dividir el proyecto. Utilizamos una interfaz en nuestro proyecto que se declara en un archivo .h y la implementación del objeto en un archivo .c. La parte importante es que todos los módulos que incluyen el archivo .h ven solo un objeto comovoid * , y el archivo .c es el único módulo que conoce las internas de la estructura.

Algo así para una clase que llamamos FOO como ejemplo:

En el archivo .h

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

El archivo de implementación C será algo así.

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

Entonces le doy el puntero explícitamente a un objeto para cada función de ese módulo. Un compilador de C ++ lo hace implícitamente, y en C lo escribimos explícitamente.

Realmente uso this en mis programas, para asegurarme de que mi programa no se compila en C ++, y tiene la excelente propiedad de estar en otro color en mi editor de resaltado de sintaxis.

Los campos de FOO_struct se pueden modificar en un módulo y no es necesario volver a compilar otro módulo para poder seguir utilizándolo.

Con ese estilo ya manejo una gran parte de las ventajas de OOP (encapsulación de datos). Al usar punteros de función, incluso es fácil implementar algo como la herencia, pero honestamente, en realidad rara vez es útil.


66
Si lo hace en typedef struct FOO_type FOO_typelugar de un typedef para anular en el encabezado, obtendrá el beneficio adicional de la verificación de tipos, sin exponer su estructura.
Scott Wales

8

Puede simularlo utilizando punteros de función y, de hecho, creo que es teóricamente posible compilar programas C ++ en C.

Sin embargo, rara vez tiene sentido forzar un paradigma en un idioma en lugar de elegir un idioma que utilice un paradigma.


55
El primer compilador de C ++ hizo exactamente eso: convirtió el código de C ++ en código C equivalente (pero feo y no legible para humanos), que luego fue compilado por el compilador de C.
Adam Rosenfield

2
EDG, Cfront y algunos otros aún son capaces de hacer esto. Con una muy buena razón: no todas las plataformas tienen un compilador de C ++.
Jasper Bekkers

Por alguna razón, pensé que C-front solo admitía ciertas extensiones de C ++ (p. Ej., Referencias) pero no la emulación completa de despacho dinámico / OOP.
Uri

2
También puede hacer lo mismo con LLVM y el backend de C.
Zifre

7

Se puede hacer C orientado a objetos, he visto ese tipo de código en producción en Corea, y fue el monstruo más horrible que había visto en años (esto fue como el año pasado (2007) cuando vi el código). Entonces sí, se puede hacer, y sí, las personas lo han hecho antes, y aún lo hacen incluso hoy en día. Pero recomendaría C ++ u Objective-C, ambos son lenguajes nacidos de C, con el propósito de proporcionar orientación a objetos con diferentes paradigmas.


3
si Linus ve tu comentario. Definitivamente se reirá o te maldecirá
Anders Lind

7

Si está convencido de que un enfoque OOP es superior para el problema que está tratando de resolver, ¿por qué trataría de resolverlo con un lenguaje que no sea OOP? Parece que estás usando la herramienta incorrecta para el trabajo. Utilice C ++ o algún otro lenguaje de variantes C orientado a objetos.

Si está preguntando porque está comenzando a codificar un proyecto grande ya existente escrito en C, entonces no debe tratar de forzar sus propios paradigmas OOP (o los de cualquier otra persona) en la infraestructura del proyecto. Siga las pautas que ya están presentes en el proyecto. En general, las API limpias y bibliotecas y módulos aislados que recorrer un largo camino para tener un OOP- limpia ish diseño.

Si, después de todo esto, realmente está listo para hacer OOP C, lea esto (PDF).


36
Realmente no estoy respondiendo la pregunta ...
Brian Postow

2
@Brian, el enlace al PDF parece responder la pregunta directamente, aunque no he tenido tiempo de verificarlo por mí mismo.
Mark Ransom

55
El enlace al PDF parece ser un libro de texto completo sobre el tema ... Una hermosa prueba, pero no cabe en el margen ...
Brian Postow

55
Sí, responde la pregunta. es perfectamente válido preguntar cómo usar un idioma de una manera particular. no hubo solicitud de opiniones en otros idiomas ...
Tim Ring

99
@Brian & Tim Ring: la pregunta solicitó recomendaciones de libros sobre un tema; Le di un enlace a un libro que aborda específicamente este tema. También di mi opinión sobre por qué el enfoque del problema puede no ser óptimo (con lo que creo que muchas personas aquí parecen estar de acuerdo, en base a los votos y otros comentarios / respuestas). ¿Tienes alguna sugerencia para mejorar mi respuesta?
RarrRarrRarr

6

Sí tu puedes. La gente escribía C orientado a objetos antes de que C ++ u Objective-C aparecieran en escena. Tanto C ++ como Objective-C fueron, en parte, intentos de tomar algunos de los conceptos de OO utilizados en C y formalizarlos como parte del lenguaje.

Aquí hay un programa realmente simple que muestra cómo puedes hacer algo que se vea como una llamada a un método (hay mejores formas de hacerlo. Esto es solo una prueba de que el lenguaje admite los conceptos):

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}

6

Por supuesto, simplemente no será tan bonito como usar un lenguaje con soporte incorporado. Incluso he escrito "ensamblador orientado a objetos".


6

Un pequeño código OOC para agregar:

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}

5

He estado cavando esto por un año:

Como el sistema GObject es difícil de usar con C puro, intenté escribir algunas buenas macros para facilitar el estilo OO con C.

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

Aquí está el sitio de mi proyecto (no tengo suficiente tiempo para escribir en. Doc, sin embargo, el documento en chino es mucho mejor).

OOC-GCC


la clase estática ASM NUEVO BORRAR ST ... son macros en el OOC-CCG
Dameng


4

¿Qué artículos o libros son buenos para usar los conceptos de OOP en C?

Las interfaces e implementaciones C de Dave Hanson son excelentes en encapsulación y denominación y muy buenas en el uso de punteros de función. Dave no intenta simular la herencia.


4

OOP es solo un paradigma que coloca los datos como más importantes que el código en los programas. OOP no es un idioma. Entonces, como C simple es un lenguaje simple, OOP en C simple es también simple.


3
Bien dicho, pero esto debería ser un comentario.
pqsk

4

Una cosa que quizás desee hacer es analizar la implementación del kit de herramientas Xt para X Window . Claro que se está alargando en el diente, pero muchas de las estructuras utilizadas fueron diseñadas para funcionar de manera OO dentro de la C. tradicional. En general, esto significa agregar una capa adicional de indirección aquí y allá y diseñar estructuras para colocarse una sobre la otra.

Realmente puedes hacer mucho en el camino de OO situado en C de esta manera, aunque a veces se siente así, los conceptos de OO no surgieron completamente de la mente #include<favorite_OO_Guru.h>. Realmente constituyeron muchas de las mejores prácticas establecidas de la época. Los lenguajes y sistemas OO solo destilaron y amplificaron partes del espíritu de programación del día.


4

La respuesta a la pregunta es 'Sí, puedes'.

El kit C orientado a objetos (OOC) es para aquellos que desean programar de una manera orientada a objetos, pero también se adhiere a la buena C anterior. OOC implementa clases, herencia simple y múltiple, manejo de excepciones.

Caracteristicas

• Utiliza solo macros y funciones C, ¡no se requieren extensiones de idioma! (ANSI-C)

• Código fuente fácil de leer para su aplicación. Se tuvo cuidado de hacer las cosas lo más simples posible.

• Herencia única de clases.

• Herencia múltiple por interfaces y mixins (desde la versión 1.3)

• Implementación de excepciones (¡en C puro!)

• Funciones virtuales para clases.

• Herramienta externa para una fácil implementación de clase.

Para más detalles, visite http://ooc-coding.sourceforge.net/ .


4

Parece que la gente está tratando de emular el estilo C ++ usando C. Mi opinión es que al hacer programación orientada a objetos, C realmente está haciendo programación orientada a estructuras. Sin embargo, puede lograr cosas como enlace tardío, encapsulación y herencia. Para la herencia, usted define explícitamente un puntero a las estructuras base en su subestructura y obviamente esta es una forma de herencia múltiple. También deberá determinar si su

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

compilar con c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Por lo tanto, el consejo es mantener un estilo C puro y no tratar de forzar un estilo C ++. También de esta manera se presta a una forma muy limpia de construir una API.


Por lo general, para la herencia, la estructura de instancia o clase base está incrustada en la derivada, no se asigna por separado y se hace referencia mediante punteros. De esa manera, la base superior siempre está al comienzo de cualquiera de las estructuras de sus tipos derivados, por lo que se pueden lanzar entre sí con facilidad, lo que no se puede hacer con punteros que podrían estar en cualquier desplazamiento.
underscore_d

2

Consulte http://slkpg.byethost7.com/instance.html para obtener otro giro en la OOP en C. Enfatiza los datos de instancia para la reentrada utilizando solo C. nativa La herencia múltiple se realiza manualmente utilizando envoltorios de funciones. Tipo de seguridad se mantiene. Aquí hay una pequeña muestra:

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}

2

Llego un poco tarde a la fiesta, pero quiero compartir mi experiencia sobre el tema: actualmente trabajo con cosas incrustadas, y el único compilador (confiable) que tengo es C, por lo que quiero aplicar orientado a objetos enfoque en mis proyectos integrados escritos en C.

La mayoría de las soluciones que he visto hasta ahora utilizan en gran medida los tipos de letra, por lo que perdemos la seguridad de tipos: el compilador no lo ayudará si comete un error. Esto es completamente inaceptable.

Requisitos que tengo:

  • Evite los tipos de letra tanto como sea posible, para que no perdamos la seguridad de tipos;
  • Polimorfismo: deberíamos poder usar métodos virtuales, y el usuario de la clase no debería saber si algún método en particular es virtual o no;
  • Herencia múltiple: no lo uso a menudo, pero a veces realmente quiero que alguna clase implemente múltiples interfaces (o extienda múltiples superclases).

He explicado mi enfoque en detalle en este artículo: programación orientada a objetos en C ; Además, hay una utilidad para la autogeneración de código repetitivo para clases base y derivadas.


2

Construí una pequeña biblioteca donde probé eso y para mí funciona muy bien. Así que pensé en compartir la experiencia.

https://github.com/thomasfuhringer/oxygen

La herencia única se puede implementar con bastante facilidad utilizando una estructura y extendiéndola para cualquier otra clase secundaria. Una conversión simple a la estructura principal hace posible el uso de métodos principales en todos los descendientes. Siempre que sepa que una variable apunta a una estructura que contiene este tipo de objeto, siempre puede lanzar a la clase raíz y hacer introspección.

Como se ha mencionado, los métodos virtuales son algo más complicados. Pero son factibles. Para simplificar las cosas, solo uso una matriz de funciones en la estructura de descripción de clase que cada clase secundaria copia y repobla espacios individuales donde sea necesario.

La herencia múltiple sería bastante complicada de implementar y conlleva un impacto significativo en el rendimiento. Entonces lo dejo. Considero que es deseable y útil en bastantes casos modelar limpiamente las circunstancias de la vida real, pero en probablemente el 90% de los casos la herencia única cubre las necesidades. Y la herencia única es simple y no cuesta nada.

Además, no me importa la seguridad de los tipos. Creo que no debe depender del compilador para evitar errores de programación. Y de todos modos te protege de una pequeña parte de los errores.

Por lo general, en un entorno orientado a objetos, también desea implementar el recuento de referencias para automatizar la gestión de memoria en la medida de lo posible. Así que también puse un recuento de referencia en la clase raíz "Objeto" y alguna funcionalidad para encapsular la asignación y la desasignación de memoria de almacenamiento dinámico.

Todo es muy simple y delgado y me da lo esencial de OO sin obligarme a lidiar con el monstruo que es C ++. Y conservo la flexibilidad de permanecer en C land, lo que, entre otras cosas, facilita la integración de bibliotecas de terceros.


1

Propongo usar Objective-C, que es un superconjunto de C.

Si bien Objective-C tiene 30 años, permite escribir código elegante.

http://en.wikipedia.org/wiki/Objective-C


En ese caso, recomendaría C ++, ya que en realidad está orientado a objetos ...
yyny

Esta no es una respuesta. Pero de todos modos, @YoYoYonnY: No uso Objective-C y uso C ++, pero comentarios como ese no sirven de nada sin fundamento, y no has proporcionado ninguno. ¿Por qué afirman que Objective-C no es "realmente orientado a objetos ..."? ¿Y por qué C ++ tiene éxito donde Objective-C falla? Lo curioso es que Objective-C, literalmente, tiene literalmente la palabra Object en su nombre, mientras que C ++ se comercializa como un lenguaje multi-paradigmático, no como OOP (es decir, no principalmente OOP, y en opinión de algunas personas bastante extremas, no OOP en absoluto) ... entonces, ¿estás seguro de que no obtuviste esos nombres al revés?
underscore_d

0

Sí, pero nunca he visto a nadie intentar implementar ningún tipo de polimorfismo con C.


66
Necesita mirar más :) Por ejemplo, Direct X de Microsoft tiene una interfaz C polimórfica.
AShelly

8
Examine la implementación del kernel de Linux, por ejemplo. Es una práctica muy común y ampliamente utilizada en C.
Ilya

3
también glib es polimórfico, o se puede usar de una manera que permita el polimorfismo (es como C ++ tienes que decir explícitamente qué llamadas son virtuales)
Spudd86

1
El polimorfismo no es tan raro en C, por otro lado, es herencia múltiple.
Johan Bjäreholt
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.