C polymorphism [duplicate]

1

Is it possible to use Polymorphism in a structured language like C ?

Languages like c++ , Java , etc (Object-oriented languages), have structures capable of inheriting functionality for code reuse (One of the reasons), but in C, you can use these programming techniques ?

    
asked by anonymous 04.02.2017 / 06:42

3 answers

4

A form of "polymorphism" widely used in C programs is using function pointers, as they have already commented. You'll find this kind of implementation in the Linux kernel, OpenSSL, etc.

See an example. I declare several different animals and each one emits the appropriate sound to it. It is enough to define the correct behavior of each one at startup.

#include <stdio.h>

// ponteiro para função de emitir som
typedef void (*func_emitir_som)(void);

// tipo genérico
typedef struct {
  func_emitir_som soar;
} Animal;

void latir(void) {
  puts("au! au!");
}

void miar(void) {
  puts("miau! miau!");
}

void mugir(void) {
  puts("muuu! muuu!");
}

int main(void) {
  Animal vaca     = { &mugir };
  Animal cachorro = { &latir };
  Animal gato     = { &miar  };

  vaca.soar();     // a vaca muge
  cachorro.soar(); // o cachorro late
  gato.soar();     // o gato mia

  return 0;
}
    
06.02.2017 / 17:17
2

Yes. You will be responsible for implementation. There are ready-made libraries like link . I recommend using C ++ if possible.

It is possible to create a struct to represent the class, with function pointers to access the variables of this class. For each function, include the parameter this , with the same type as struct . To construct the class, create a function that returns the object by parameter and, in this function, initialize its functions already declared the variables of struct . These functions can be reassigned at runtime (I find that very cool).

For inheritance, create a variable in struct with the type of the inherited class (or array for multiple inheritance).

For encapsulation, create an opaque pointer for struct .

However, this practice is very labor-intensive and achievement may not be achieved, depending on the complexity of the code. I recommend using functions in separate modules, treating structs as if they were objects, without creating function pointers in the struct (this practice only increases the complexity of the code and makes bugs difficult), solving the interfaces , when needed.

    
04.02.2017 / 07:48
0

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.

    
06.02.2017 / 17:19