Como alguém escreveria código orientado a objeto em C? [fechadas]


500

Quais são algumas maneiras de escrever código orientado a objeto em C? Especialmente no que diz respeito ao polimorfismo.


Veja também esta pergunta Stack Overflow Object-orientação no C .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Programação orientada a objetos em C </a> por Laurent Deniau


25
@Camilo Martin: Eu perguntei intencionalmente pode não deveria . Na verdade, não estou interessado em usar OOP em C. No entanto, ao ver soluções OO em C, aprendemos mais sobre os limites e / ou flexibilidade de C e também sobre maneiras criativas de implementar e usar o polimorfismo.
Dinah

5
OO é apenas um padrão. Verifique aqui, isso pode até ser feito em arquivos .bat: dirk.rave.org/chap9.txt (qualquer padrão pode ser aplicado a qualquer linguagem de programação, se você estiver interessado o suficiente). Este é um bom alimento para o pensamento, no entanto. E provavelmente muito pode ser aprendido com a aplicação de tais padrões que tomamos como garantidos em idiomas que não os possuem.
Camilo Martin

6
GTK - 'com licença, GObject - é realmente um bom exemplo de OOP (sorta) em C. Então, para responder @Camilo, para interpolabilidade em C.
new123456

Respostas:


362

Sim. De fato, Axel Schreiner fornece seu livro "Programação Orientada a Objetos em ANSI-C" de graça, que cobre o assunto completamente.


28
Embora os conceitos neste livro sejam sólidos, você perderá a segurança do tipo.
Diapir '

22
Antes do que conhecemos como padrões de design, era o padrão de design conhecido como "orientação a objetos"; o mesmo com a coleta de lixo e outros. Eles são tão arraigado agora, temos a tendência de esquecer, quando eles foram os primeiros a ser inventado, era praticamente da mesma forma que com o que nós pensamos como padrões de projeto hoje
Dexygen

11
Você pode obtê-lo diretamente no site do autor: cs.rit.edu/~ats/books/ooc.pdf outros artigos do mesmo autor: cs.rit.edu/~ats/books/index.html
pakman

10
A colheita (Livro + exemplos de código-fonte) está disponível a partir deste índice rit.edu programação orientada objecto com as normas ANSI-C
David C. Rankin

3
Este livro é revisado por pares? Há um erro de digitação na primeira frase do primeiro parágrafo da primeira página.
Dagrooms

343

Desde que você está falando sobre polimorfismo, sim, você pode, estávamos fazendo esse tipo de coisa anos antes do C ++ surgir.

Basicamente você usa um struct para armazenar os dados e uma lista de indicadores de função para apontar para as funções relevantes para esses dados.

Portanto, em uma classe de comunicação, você teria uma chamada de abertura, leitura, gravação e fechamento que seria mantida como quatro ponteiros de função na estrutura, juntamente com os dados de um objeto, algo 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;

Obviamente, esses segmentos de código acima estariam realmente em um "construtor", como rs232Init().

Quando você 'herda' dessa classe, basta alterar os ponteiros para apontar para suas próprias funções. Todos que chamavam essas funções o faziam através dos ponteiros de função, fornecendo seu polimorfismo:

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

Mais ou menos como uma tabela manual.

Você pode até ter classes virtuais definindo os ponteiros como NULL - o comportamento seria um pouco diferente do C ++ (um dump principal em tempo de execução em vez de um erro em tempo de compilação).

Aqui está um pedaço de código de exemplo que o demonstra. Primeiro, a estrutura de classe de nível superior:

#include <stdio.h>

// The top-level class.

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

Então temos as funções para a 'subclasse' do 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;
}

E o HTTP também:

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

E, finalmente, um programa de teste para mostrá-lo em ação:

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

Isso produz a saída:

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

para que você possa ver que as diferentes funções estão sendo chamadas, dependendo da subclasse.


52
Encapsulamento é muito fácil, polimorfismo é factível - mas inheritence é complicado
Martin Beckett

5
O lwn.net publicou recentemente um artigo intitulado Design Orientado a Objetos Padrões no kernel sobre o assunto de estruturas semelhantes à resposta acima - ou seja, uma estrutura contendo ponteiros de função ou um ponteiro para uma estrutura que possui funções que levam um ponteiro para o estrutura com os dados com os quais trabalhamos como parâmetro.
radicalmatt

11
+1 Bom exemplo! Embora se alguém realmente quiser seguir esse caminho, seria mais apropriado que as estruturas de "instância" tenham um único campo apontando para sua instância de "tabela virtual", contendo todas as funções virtuais desse tipo em um único local. Ou seja, seu tCommClassnome seria renomeado tCommVTe uma tCommClassestrutura só teria campos de dados e um único tCommVT vtcampo apontando para a "única e única" tabela virtual. Carregar todos os ponteiros com cada instância adiciona uma sobrecarga desnecessária e se assemelha mais a como você faria coisas em JavaScript do que em C ++, IMHO.
Groo 23/10

1
Portanto, isso demonstra a implementação de uma única interface, mas e sobre a implementação de várias interfaces? Ou herança múltipla?
weberc2

Weber, se você deseja todas as funcionalidades do C ++, provavelmente deve estar usando o C ++. A pergunta feita especificamente sobre polimorfismo, a capacidade de os objetos assumirem uma "forma" diferente. Você certamente pode fazer interfaces e herança múltipla em C, mas é um pouco de trabalho extra, e você precisa gerenciar os smarts por conta própria, em vez de usar coisas internas do C ++.
21714 Paxdiablo

86

Os namespaces geralmente são feitos da seguinte maneira:

stack_push(thing *)

ao invés de

stack::push(thing *)

Para transformar uma estrutura C em algo como uma classe C ++, você pode transformar:

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

Para 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

E fazer:

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
}

Não fiz o destruidor ou excluí, mas segue o mesmo padrão.

this_is_here_as_an_example_only é como uma variável de classe estática - compartilhada entre todas as instâncias de um tipo. Todos os métodos são realmente estáticos, exceto que alguns fazem isso *


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

@nategoose: OR em struct stack_type my_type; vez destruct stack_type * my_type;
Fabricio

3
Eu gosto do conceito de ter uma estrutura para a classe. Mas que tal um genéricoClass estrutura ? Isso tornaria o OO C mais dinâmico que o C ++. Que tal isso? A propósito, +1.
LinuxJul

54

Acredito que, além de ser útil por si só, implementar OOP em C é uma excelente maneira de aprender OOP e entender seu funcionamento interno. A experiência de muitos programadores mostrou que, para usar uma técnica de maneira eficiente e confiável, o programador deve entender como os conceitos subjacentes são finalmente implementados. Emular classes, herança e polimorfismo em C ensina exatamente isso.

Para responder à pergunta original, aqui estão alguns recursos que ensinam como fazer POO em C:

Postagem no blog EmbeddedGurus.com "Programação baseada em objeto em C" mostra como implementar classes e herança única em C portátil: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Nota da aplicação "" C + "- Programação Orientada a Objetos em C" mostra como implementar classes, herança única e ligação tardia (polimorfismo) em C usando macros de pré-processador: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , o código de exemplo está disponível em http://www.state-machine.com/resources/cplus_3.0.zip


4
Novo URL para o manual C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Eu já vi isso. Eu não recomendaria. O C ++ começou originalmente dessa maneira como um pré-processador que produzia o código C como uma etapa intermediária.

Essencialmente, o que você acaba fazendo é criar uma tabela de despacho para todos os seus métodos nos quais você armazena suas referências de função. A derivação de uma classe implicaria copiar esta tabela de expedição e substituir as entradas que você desejava substituir, com seus novos "métodos" tendo que chamar o método original se desejar invocar o método base. Eventualmente, você acaba reescrevendo C ++.


5
"Eventualmente, você acaba reescrevendo C ++". Eu me perguntava se eu temia que fosse esse o caso.
Dinah

39
Ou então, você pode reescrever o Objetivo C, o que seria um resultado muito mais atraente.
O contrato do Prof. Falken violou

3
Existe o sabor do OOP sem classe, como no Javascript , onde o guru diz: "Não precisamos de classes para criar muitos objetos semelhantes". Mas temo que isso não seja fácil de alcançar em C. No entanto, ainda não estou em posição de dizer. (Existe um clone () rotina para clone um struct?)
Lumi

1
Mais caras inteligentes, que tinham para realmente implementar isso e fazer que a implementação rápida (motor Google, V8) tem feito tudo fazer add (oculto) classes para JavaScript para trás.
cubuspl42

Não está glibescrito em C de maneira objetiva?
Kravemir

26

Claro que isso é possível. É nisso que GObject , a estrutura na qual todo o GTK + e GNOME se baseia, faz.


Quais são os prós / contras dessa abordagem? Ou seja. é muito mais fácil escrever usando C ++.
Kravemir

@ kravemir Bem, o C ++ não é tão portátil quanto o C, e é um pouco mais difícil vincular o C ++ ao código que pode ser compilado por um compilador C ++ diferente. Mas sim, é mais fácil escrever classes em C ++, embora o GObject também não seja tão difícil (supondo que você não se importe com uma pequena placa de caldeira).
Edwin Buck

17

A sub-biblioteca C stdio FILE é um excelente exemplo de como criar abstração, encapsulamento e modularidade em C. não adulterado.

Herança e polimorfismo - os outros aspectos muitas vezes considerados essenciais para OOP - não necessariamente fornecer os ganhos de produtividade que eles prometem e razoáveis argumentos têm sido feitos que eles podem realmente prejudicar o desenvolvimento e pensar sobre o domínio do problema.


O stdio não é abstraído na camada do kernel? Se não me engano, trata-los C-biblioteca como arquivos de caracteres / dispositivos e drivers do kernel fazer o trabalho, ...
kravemir

15

Exemplo trivial com um animal e um cachorro: Você reflete o mecanismo de tabela de tabelas do C ++ (de qualquer maneira). Você também separa alocação e instanciação (Animal_Alloc, Animal_New) para não chamarmos malloc () várias vezes. Também devemos passar explicitamente o thisponteiro.

Se você executasse funções não virtuais, isso é uma coisa rival. Você simplesmente não as adiciona à vtable e as funções estáticas não requerem um thisponteiro. A herança múltipla geralmente requer várias vtables para resolver ambiguidades.

Além disso, você deve poder usar setjmp / longjmp para manipular exceções.

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

PS. Isso é testado em um compilador C ++, mas deve ser fácil fazê-lo funcionar em um compilador C.


typedefdentro de um structnão é possível em C.
Masoud

13

Confira GObject . Ele deve ser OO em C e uma implementação do que você está procurando. Se você realmente deseja OO, vá com C ++ ou alguma outra linguagem OOP. Às vezes, é muito difícil trabalhar com o GObject se você estiver acostumado a lidar com idiomas OO, mas, como qualquer coisa, você se acostumará às convenções e fluxo.


12

Isso foi interessante de ler. Eu mesmo ponderei a mesma pergunta, e os benefícios de pensar nisso são os seguintes:

  • Tentar imaginar como implementar conceitos OOP em uma linguagem não OOP me ajuda a entender os pontos fortes da linguagem OOp (no meu caso, C ++). Isso me ajuda a avaliar melhor se deve usar C ou C ++ para um determinado tipo de aplicativo - onde os benefícios de um superam o outro.

  • Ao navegar na web para obter informações e opiniões sobre isso, encontrei um autor que escrevia o código para um processador incorporado e tinha apenas um compilador C disponível: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Criando-Foundation-Classes-Parte-1

No caso dele, analisar e adaptar os conceitos de POO na linguagem C era uma busca válida. Parece que ele estava aberto a sacrificar alguns conceitos de OOP devido à sobrecarga de desempenho resultante da tentativa de implementá-los em C.

A lição que aprendi é que sim, isso pode ser feito até certo ponto, e sim, existem algumas boas razões para tentar.

No final, a máquina está girando os bits do ponteiro da pilha, fazendo o contador do programa pular e calcular as operações de acesso à memória. Do ponto de vista da eficiência, quanto menos desses cálculos forem feitos pelo seu programa, melhor ... mas às vezes temos que pagar esse imposto para que possamos organizar nosso programa de maneira a torná-lo menos suscetível a erros humanos. O compilador de linguagem OOP se esforça para otimizar ambos os aspectos. O programador deve ter muito mais cuidado ao implementar esses conceitos em uma linguagem como C.


10

Talvez seja útil consultar a documentação da Apple para o conjunto de APIs da Core Foundation. É uma API C pura, mas muitos dos tipos são conectados a equivalentes de objeto Objective-C.

Você também pode considerar útil o design do próprio Objective-C. É um pouco diferente do C ++, pois o sistema de objetos é definido em termos de funções C, por exemplo, objc_msg_sendpara chamar um método em um objeto. O compilador converte a sintaxe entre colchetes nessas chamadas de função, para que você não precise saber disso, mas, considerando sua pergunta, pode ser útil aprender como ele funciona sob o capô.


10

Existem várias técnicas que podem ser usadas. O mais importante é mais como dividir o projeto. Usamos uma interface em nosso projeto que é declarada em um arquivo .h e a implementação do objeto em um arquivo .c. A parte importante é que todos os módulos que incluem o arquivo .h veem apenas um objeto como void *ae o arquivo .c é o único módulo que conhece os elementos internos da estrutura.

Algo assim para uma classe que chamamos de FOO como exemplo:

No arquivo .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

O arquivo de implementação C será algo parecido.

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

Então, eu dou o ponteiro explicitamente a um objeto para todas as funções desse módulo. Um compilador C ++ faz isso implicitamente, e em C o escrevemos explicitamente.

Eu realmente uso this nos meus programas para garantir que meu programa não seja compilado em C ++ e tenha a propriedade de estar em outra cor no meu editor de destaque de sintaxe.

Os campos da FOO_struct podem ser modificados em um módulo e outro módulo nem precisa ser recompilado para ainda ser utilizável.

Com esse estilo, eu já manejo grande parte das vantagens do OOP (encapsulamento de dados). Usando ponteiros de função, é ainda fácil implementar algo como herança, mas, honestamente, é realmente raramente útil.


6
Se você fizer isso em typedef struct FOO_type FOO_typevez de um typedef para anular no cabeçalho, obterá o benefício adicional da verificação de tipo, embora ainda não exponha sua estrutura.
Scott Wales

8

Você pode fingir usando ponteiros de função e, de fato, acho que é teoricamente possível compilar programas C ++ em C.

Entretanto, raramente faz sentido forçar um paradigma em um idioma, em vez de escolher um idioma que use um paradigma.


5
O primeiro compilador C ++ fez exatamente isso - ele converteu o código C ++ em código C equivalente (mas feio e não legível por humanos), que foi compilado pelo compilador C.
Adam Rosenfield

2
EDG, Cfront e alguns outros ainda são capazes de fazer isso. Com uma boa razão: nem toda plataforma possui um compilador C ++.
Jasper Bekkers

Por alguma razão, pensei que o C-front suportasse apenas certas extensões C ++ (por exemplo, referências), mas não a emulação completa de OOP / envio dinâmico.
Uri

2
Você também pode fazer o mesmo com o LLVM e o back-end C.
Zifre

7

C orientado a objetos, pode ser feito, eu já vi esse tipo de código em produção na Coréia, e foi o monstro mais horrível que eu já vi em anos (foi como no ano passado (2007) que vi o código). Então, sim, isso pode ser feito, e sim, as pessoas já fizeram isso antes, e ainda o fazem até hoje em dia. Mas eu recomendaria C ++ ou Objective-C, ambas são linguagens nascidas de C, com o objetivo de fornecer orientação a objetos com diferentes paradigmas.


3
se Linus vê seu comentário. Definitivamente ele vai rir ou amaldiçoar você
Anders Lind

7

Se você está convencido de que uma abordagem OOP é superior para o problema que você está tentando resolver, por que você tentaria resolvê-la com um idioma que não seja OOP? Parece que você está usando a ferramenta errada para o trabalho. Use C ++ ou alguma outra linguagem variante C orientada a objeto.

Se você está perguntando porque está começando a codificar um projeto grande já existente, escrito em C, não deve tentar forçar seus próprios paradigmas de OOP (ou de qualquer outra pessoa) na infraestrutura do projeto. Siga as diretrizes que já estão presentes no projeto. Em geral, APIs limpas e bibliotecas e módulos isolados irá percorrer um longo caminho para ter um OOP- limpo ish design.

Se, depois de tudo isso, você realmente estiver decidido a fazer OOP C, leia isto (PDF).


36
Não realmente responder a pergunta ...
Brian Postow

2
@Brian, o link para o PDF parece responder diretamente à pergunta, embora eu não tenha tido tempo de verificar por mim mesmo.
Mark Ransom

5
O link para o PDF parece ser um livro inteiro sobre o assunto ... A prova bonito, mas não se encaixa na margem ...
Brian Postow

5
sim, responda a pergunta. é perfeitamente válido perguntar como usar um idioma de uma maneira específica. não havia nenhum pedido de opiniões sobre outros idiomas ....
Tim Anel

9
@Brian & Tim Ring: A pergunta fazia recomendações de livros sobre um tópico; Dei a ele um link para um livro que aborda especificamente esse tópico. Também dei minha opinião sobre por que a abordagem do problema pode não ser ótima (com a qual acho que muitas pessoas aqui parecem concordar, com base em votos e outros comentários / respostas). Você tem alguma sugestão para melhorar minha resposta?
RarrRarrRarr

6

Sim você pode. As pessoas estavam escrevendo C orientado a objetos antes de C ++ ou Objective-C entrar em cena. O C ++ e o Objective-C foram, em partes, tentativas de pegar alguns dos conceitos de OO usados ​​em C e formalizá-los como parte da linguagem.

Aqui está um programa realmente simples que mostra como você pode fazer algo parecido / é uma chamada de método (existem maneiras melhores de fazer isso. Isso é apenas uma prova de que a linguagem suporta os conceitos):

#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

Obviamente, não será tão bonito quanto usar um idioma com suporte embutido. Eu até escrevi "assembler orientado a objetos".


6

Um pequeno código OOC para adicionar:

#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

Venho cavando isso há um ano:

Como o sistema GObject é difícil de usar com C puro, tentei escrever algumas macros agradáveis ​​para facilitar o estilo OO com 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;
}

Aqui está o site do meu projeto (não tenho tempo suficiente para escrever en. Doc, no entanto, o documento em chinês é muito melhor).

OOC-GCC


a classe estática ASM NOVO APAGAR ST ... são macros no OOC-CCG
Dameng



4

OOP é apenas um paradigma que coloca os dados como mais importantes que o código nos programas. OOP não é um idioma. Assim, como C simples é uma linguagem simples, OOP em C simples também é simples.


3
Bem dito, mas isso deve ser um comentário.
pqsk

4

Uma coisa que você pode querer fazer é analisar a implementação do kit de ferramentas Xt para X Window . Claro que está demorando muito tempo no dente, mas muitas das estruturas usadas foram projetadas para funcionar de maneira OO no C. tradicional. Geralmente, isso significa adicionar uma camada extra de indireção aqui e ali e projetar estruturas para se sobreporem.

Você pode realmente fazer muitas coisas no caminho de OO situadas em C dessa maneira, mesmo que pareça que algumas vezes, os conceitos de OO não tenham surgido totalmente da mente de #include<favorite_OO_Guru.h>. Eles realmente constituíam muitas das melhores práticas estabelecidas da época. As linguagens e sistemas OO apenas destilam e amplificam partes do zeitgeist de programação do dia.


4

A resposta para a pergunta é 'Sim, você pode'.

O kit C (OOC) orientado a objetos é para aqueles que desejam programar de maneira orientada a objetos, mas também mantém o bom e velho C. OOC implementa classes, herança única e múltipla, tratamento de exceções.

Recursos

• Utiliza apenas macros e funções C, sem necessidade de extensões de idioma! (ANSI-C)

• Código-fonte fácil de ler para o seu aplicativo. Foi tomado cuidado para tornar as coisas o mais simples possível.

• Herança única de classes

• Herança múltipla por interfaces e mixins (desde a versão 1.3)

• Implementando exceções (em puro C!)

• Funções virtuais para aulas

• Ferramenta externa para fácil implementação de classe

Para mais detalhes, visite http://ooc-coding.sourceforge.net/ .


4

Parece que as pessoas estão tentando emular o estilo C ++ usando C. Minha opinião é que fazer programação orientada a objetos C é realmente fazer programação orientada a estruturas. No entanto, você pode obter coisas como ligação tardia, encapsulamento e herança. Para herança, você define explicitamente um ponteiro para as estruturas básicas em sua subestrutura e esta é obviamente uma forma de herança múltipla. Você também precisará determinar se o seu

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

ajuntar com c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj .

Portanto, o conselho é seguir um estilo C puro e não tentar forçar um estilo C ++. Além disso, dessa maneira se presta a uma maneira muito limpa de criar uma API.


Para herança, normalmente a classe base ou a estrutura da instância é incorporada na derivada, não alocada separadamente e referida usando ponteiros. Dessa forma, a base superior está sempre no início de qualquer estrutura de seus tipos derivados, para que possam ser convertidos entre si com facilidade, o que você não pode fazer com ponteiros que possam estar em qualquer deslocamento.
underscore_d

2

Consulte http://slkpg.byethost7.com/instance.html para mais uma reviravolta no OOP em C. Ele enfatiza os dados da instância para reentrada usando apenas o C. nativo. A herança múltipla é feita manualmente usando wrappers de função. A segurança do tipo é mantida. Aqui está uma pequena amostra:

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

Estou um pouco atrasado para a festa, mas quero compartilhar minha experiência sobre o assunto: hoje trabalho com coisas incorporadas e o único compilador (confiável) que tenho é C, para que eu queira aplicar a orientação a objetos abordagem em meus projetos incorporados escritos em C.

A maioria das soluções que eu vi até agora usa tipografias pesadamente, por isso perdemos a segurança de tipos: o compilador não ajudará se você cometer um erro. Isso é completamente inaceitável.

Requisitos que tenho:

  • Evite previsões de texto o máximo possível, para não perder a segurança de digitação;
  • Polimorfismo: devemos ser capazes de usar métodos virtuais, e o usuário da classe não deve saber se algum método específico é virtual ou não;
  • Herança múltipla: não a uso com frequência, mas às vezes quero que alguma classe implemente várias interfaces (ou estenda várias superclasses).

Expliquei minha abordagem em detalhes neste artigo: Programação orientada a objetos em C ; além disso, existe um utilitário para geração automática de código padrão para classes base e derivadas.


2

Construí uma pequena biblioteca onde tentei isso e para mim funciona muito bem. Então, pensei em compartilhar a experiência.

https://github.com/thomasfuhringer/oxygen

A herança única pode ser implementada facilmente usando uma estrutura e estendendo-a para todas as outras classes filho. Uma conversão simples para a estrutura pai torna possível usar métodos pai em todos os descendentes. Desde que você saiba que uma variável aponta para uma estrutura que contém esse tipo de objeto, você sempre pode converter para a classe raiz e fazer introspecção.

Como foi mencionado, os métodos virtuais são um pouco mais complicados. Mas eles são factíveis. Para simplificar, eu apenas uso uma matriz de funções na estrutura de descrição de classe que cada classe filho copia e repovoa slots individuais, quando necessário.

A herança múltipla seria bastante complicada de implementar e tem um impacto significativo no desempenho. Então eu deixo. Considero desejável e útil, em alguns casos, modelar de maneira limpa as circunstâncias da vida real, mas provavelmente 90% dos casos a herança única cobre as necessidades. E a herança única é simples e não custa nada.

Também não me importo com a segurança do tipo. Eu acho que você não deve depender do compilador para evitar erros de programação. E protege você apenas de uma parte bastante pequena dos erros de qualquer maneira.

Normalmente, em um ambiente orientado a objetos, você também deseja implementar a contagem de referências para automatizar o gerenciamento de memória na medida do possível. Então, eu também coloquei uma contagem de referência na classe raiz "Object" e algumas funcionalidades para encapsular a alocação e desalocação da memória heap.

É tudo muito simples e enxuto e me fornece o essencial do OO sem me forçar a lidar com o monstro que é C ++. E mantenho a flexibilidade de permanecer na terra C, o que, entre outras coisas, facilita a integração de bibliotecas de terceiros.


1

Proponho usar o Objective-C, que é um superconjunto de C.

Embora o Objective-C tenha 30 anos, ele permite escrever um código elegante.

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


Nesse caso, eu recomendaria C ++ em vez desde a sua realmente orientada a objetos ...
yyny

Esta não é uma resposta. Mas de qualquer maneira, @YoYoYonnY: Eu não uso Objective-C e uso C ++, mas comentários como esse não servem para nada sem base, e você não forneceu nenhum. Por que você afirma que o Objective-C não é "realmente orientado a objetos ..."? E por que o C ++ é bem-sucedido onde Objective-C falha? O engraçado é que Objective-C, literalmente, tem a palavra Objeto em seu nome, enquanto o C ++ se comercializa como uma linguagem multiparadigma, não como um POO (ou seja, não principalmente POO, ou, na opinião de algumas pessoas bastante extremas, não POO ) ... então você tem certeza de que não entendeu esses nomes da maneira errada?
Underscore_d

0

Sim, mas nunca vi alguém tentar implementar qualquer tipo de polimorfismo com C.


6
Você precisa procurar mais :) Por exemplo, o Direct X da Microsoft possui uma interface C polimórfica.
AShelly 9/12/08

8
Veja a implementação do kernel linux, por exemplo. É muito comum ea prática amplamente utilizada em C.
Ilya

3
Também glib é polimórfica, ou pode ser usado de uma forma que permite que o polimorfismo (é como C ++ você tem a dizer explicitamente que as chamadas são virtuais)
Spudd86

1
Polimorfismo não é tão raro em C, por outro lado, herança múltipla.
Johan Bjäreholt 25/01
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.