You can simulate simple inheritance in C very easily. However, this depends on typecasting (typecasts), which prevents you from using the typed system to catch some simple bugs. Let's look at an example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define ANOS 31556952 /* segundos por ano */
#define MKOBJ(obj) do { obj = malloc(sizeof(obj[0])); if (!obj) exit(1); } while (0)
#define RMOBJ(obj) (free(obj), obj = NULL)
/* Estrutura base */
typedef struct pessoa_t {
char * nome;
time_t dt_nascimento;
unsigned char sexo;
} pessoa_t;
/* Estrutura derivada */
typedef struct empregado_t {
pessoa_t base; /* NÃO é um ponteiro */
unsigned int matricula;
time_t dt_admissao;
} empregado_t;
void
imprimir_pessoa(pessoa_t * p) {
printf("%s é %s de %d anos de idade.\n",
p->nome,
p->sexo == 'F' ? "uma moça" : "um rapaz",
(time(NULL) - p->dt_nascimento) / ANOS
);
}
void
imprimir_empregado(empregado_t * e) {
imprimir_pessoa((pessoa_t *) e);
printf("%s entrou na empresa há %d anos.\n",
((pessoa_t *) e)->sexo == 'F' ? "Ela" : "Ele",
(time(NULL) - e->dt_admissao) / ANOS
);
}
static void
criar_pessoa_aux(pessoa_t * self,
const char * nome,
char sexo,
int dia_nasc,
int mes_nasc,
int ano_nasc) {
static struct tm tm;
self->nome = strdup(nome);
self->sexo = sexo;
tm.tm_mday = dia_nasc;
tm.tm_mon = mes_nasc - 1;
tm.tm_year = ano_nasc - 1900;
self->dt_nascimento = mktime(&tm);
}
pessoa_t *
criar_pessoa(const char * nome,
char sexo,
int dia_nasc,
int mes_nasc,
int ano_nasc) {
pessoa_t * result;
MKOBJ(result);
criar_pessoa_aux(result, nome, sexo, dia_nasc, mes_nasc, ano_nasc);
return result;
}
void
destruir_pessoa(pessoa_t * p) {
RMOBJ(p->nome);
RMOBJ(p);
}
empregado_t *
criar_empregado(const char * nome,
char sexo,
int dia_nasc,
int mes_nasc,
int ano_nasc,
int matricula,
int dia_adm,
int mes_adm,
int ano_adm) {
empregado_t * result;
static struct tm tm;
MKOBJ(result);
criar_pessoa_aux((pessoa_t *) result, nome, sexo, dia_nasc, mes_nasc, ano_nasc);
result->matricula = matricula;
tm.tm_mday = dia_adm;
tm.tm_mon = mes_adm - 1;
tm.tm_year = ano_adm - 1900;
result->dt_admissao = mktime(&tm);
return result;
}
void
destruir_empregado(empregado_t * e) {
pessoa_t * p = (pessoa_t *) e;
RMOBJ(p->nome);
RMOBJ(e);
}
int
main(int argc, char ** argv) {
pessoa_t * Alice;
empregado_t * Beto, * Carol;
/* -------nome------- sexo -nascimento-- -matr- ---admissão-- */
Alice = criar_pessoa ("Alice da Silva" , 'F', 6, 12, 1999);
Beto = criar_empregado("Beto da Costa" , 'M', 1, 5, 1974, 11111, 1, 1, 2007);
Carol = criar_empregado("Carol dos Santos", 'F', 9, 3, 1990, 22222, 1, 10, 2015);
imprimir_pessoa(Alice);
imprimir_pessoa((pessoa_t *) Beto);
imprimir_pessoa((pessoa_t *) Carol);
puts("----------\n");
// imprimir_empregado((empregado_t *) Alice); /* SEGFAULT! */
imprimir_empregado(Beto);
imprimir_empregado(Carol);
return 0;
}
As you can see, it does a lot of work: builders have to automatically allocate variables and handle allocation errors, and then initialize them; the destructors have to delete the strings and then erase themselves, etc. Of course, this will be the case for any architecture that is not language-specific.
That said, you can see how empregado_t *
, passed to a function that expects a pessoa_t *
, does the right thing and works perfectly well since the first element of empregado_t
is a pessoa_t
structure. When the cast is made, the employee remains a valid person.
On the other hand, since you're doing type coercion, the compiler does not stop you from forcing a function that gets a empregado_t *
to receive a pessoa_t *
that is not a empregado_t *
. In this case, you will only see the running error (with luck; coercion can simply access data from another object as if it were the missing fields, and it silently keeps running with inconsistent data).
Like everything else in C, it's a valid technique, but the programmer has to take responsibility for the casts that does.