OPENEDU Seu Conhecimento Cresce a Cada Questão Resolvida

Introdução A Objetos No C

Introdução A Objetos No C

Objetos em C não existem nativamente

O C é uma linguagem procedural. Não há classes, herança, polimorfismo ou construtores no padrão da linguagem. Mas isso não impede que você simule comportamento orientado a objetos usando structs e funções. É um exercício comum para quem está começando a migrar do C para C++ ou precisa manter codebases legacy. A base é simples: structs substituem classes, e funções separadas substituem métodos. Cada struct pode ter ponteiros para funções que atuam sobre ela, ou funções externas podem receber a struct como primeiro parâmetro. A convenção mais usada é passar um ponteiro para a struct como o primeiro argumento de qualquer função que a manipule. Veja um exemplo mínimo de uma struct que representa um retângulo:

```c typedef struct { float x; float y; float largura; float altura; } Retangulo; ```

Para criar métodos, você escreve funções normais: Chamar essas funções parece quase orientação a objetos quando você organiza bem: Isso é praticamente tudo que existe. structs, ponteiros, funções. Sem magic.

Se você precisa de algo mais parecido com herança e polimorfismo, o caminho é usar tabelas de funções (vtable), inspirado no que o GCC faz por trás das cortinas no C++. A ideia é colocar um ponteiro de função dentro da struct e usar herança de struct via composição. Por exemplo, para criar uma hierarquia simples de formas geométricas:

C# Introdução a Programação Orientada a Objetos, por Nelson Sadala ...
C# Introdução a Programação Orientada a Objetos, por Nelson Sadala ...
```c typedef struct { float (*area)(void *); void (*mover)(void *, float, float); char *nome; } Forma; typedef struct { Forma base; float lado; } Quadrado; float area_quadrado(void *q) { return ((Quadrado*)q)->lado * ((Quadrado*)q)->lado; } ```

Aqui, a struct Forma age como uma interface. Cada tipo concreto preenche esses ponteiros de função durante a inicialização. Você chama através do ponteiro genérico, e o código correto roda. Esse padrão é útil em sistemas embarcados onde você precisa trocar implementações em tempo de execução sem depender de dynamic dispatch do C++. Mas traz um custo: cada chamada indireta via ponteiro de função adiciona overhead, e o debugger fica mais difícil de seguir. Já perdi meia hora rastreando um bug porque o ponteiro de função estava apontando para um stub não implementado em uma struct aninhada.

Encapsulamento real em C

Uma coisa que muitos iniciantes ignoram: você pode esconder definições de structs usando arquivos de cabeçalho separados. Se você declara apenas um tipo incompleto no cabeçalho público e define a struct completa no .c, ninguém fora daquele arquivo consegue acessar os campos diretamente. Isso é encapsulamento de verdade. A API pública só expõe funções, nunca dados. É como uma classe com members privados, mas escrito manualmente. O trade-off é que todo acesso aos dados deve passar por getters/setters, o que aumenta a verbosidade do código consideravelmente. Em projetos pequenos, isso costuma ser overkill. Em bibliotecas destinadas a múltiplos times, economiza horas de debugging. Como o C não tem construtores, você cria funções que alocam e inicializam a struct. O padrão típico é uma função que retorna um ponteiro:

Repare que o erro de alocação é tratado de forma explícita. Isso é crucial em C. Um NULL mal tratado aqui causaria um segfault em qualquer chamada subsequente. Já vi códigos que puxavam o ponteiro sem verificar NULL depois de criar uma lista, e o bug só aparecia em produção sob carga alta. O destrutor segue o mesmo princípio:

Livro C# Introdução A Programação Orientada A Objetos | MercadoLivre
Livro C# Introdução A Programação Orientada A Objetos | MercadoLivre
```c void lista_destruir(Lista *l) { if (l) { free(l->itens); free(l); } } ```

Simples. Nada de RAII. Se você esquecer de chamar o destrutor, o memory leak persiste até o processo morrer. Em programas de longa execução, isso se acumula rapidamente. Um problema frequente é confundir cópia de struct com compartilhamento de estado. Em C, atribuir uma struct copia todos os campos por valor. Se a struct contém ponteiros, o ponteiro é copiado, não o dado apontado. Duas variáveis apontarão para o mesmo buffer. Isso funciona bem para structs pequenas e POD. Mas se você estiver gerenciando recursos dinâmicos (memória, arquivos, locks), cópias superficiais vão gerar duplo-free ou use-after-free. Nesse caso, implemente uma função de cópia profunda explicitamente.

Outro problema: ordenação de campos em structs afeta o layout em memória. Campos de tipos diferentes podem ter padding inserido pelo compilador. Se você precisar de um layout fixo para comunicação com hardware ou protocolos de rede, use `#pragma pack` ou attributes específicos, mas saiba que isso desabilita otimizações de alinhamento e pode degradar performance em certas arquiteturas.

Quando isso não vale a pena

Simular OOP em C funciona bem para bibliotecas pequenas e middleware. Mas se o projeto cresce para centenas de tipos com hierarquias complexas, a verbosidade explode. Cada novo tipo exige funções de criação, destruição, cópia e métodos manualmente. Em C++, o mesmo código seria 70% menor com templates e classes. Se você está entrando em um projeto novo e não tem restrição de linguagem, C++ ou Rust oferecem tudo que a simulação em C oferece, mas com segurança de memória e menos boilerplate. Use a simulação apenas quando obrigado por restrições de plataforma ou legacy code.

Introdução À Programação Orientada A Objetos Com C++ E | MercadoLivre
Introdução À Programação Orientada A Objetos Com C++ E | MercadoLivre