Domanda [PROBLEMA] Emulazione in C delle classi e quindi degli oggetti

Stato
Discussione chiusa ad ulteriori risposte.

Erchomai

Utente Silver
14 Novembre 2016
92
17
5
51
Salve a tutti,
Volevo chiedervi se esiste un modo efficace di implementare le classi in C, dovrei creare una struttura dati non influenzabile dall'utente in modo che si emulino i numeri complessi (z= a + ib).


Distinti Saluti
 
Finché si tratta di cose così semplice (niente OOP vera, solo plain objects), puoi utilizzare una struct e una serie di funzioni che la manipolano. Per rendere l'idea, una cosa di questo tipo:
C:
struct Complex {
    int real, imm;
};

struct Complex add(struct Complex a, struct Complex b)
{
    struct Complex result;
    result.real = a.real + b.real;
    result.imm = a.imm + b.imm;
    return result;
}
 
Grazie.... Pensavo di usare un typedef sulla struct e due puntatori a funzione in quanto devo proteggere la struttura dal utente per simulare l'incapsulamento

Inviata da ONE A2003 tramite app ufficiale di Inforge.net
 
La typedef puoi anche metterla, ma aggiungere dei puntatori tanto per aggiungerli non è una buona idea. Nel C non c'è un meccanismo di protezione dei dati, è tutto public. Potresti utilizzare un approccio alla Python: metti un underscore davanti al campo e per convenzione dici che l'utente non dovrebbe toccarlo.

I puntatori a funzione ti possono servire se vuoi dire che i metodi fanno parte dello stato dell'oggetto, altrimenti anche delle funzioni normalissime vanno più che bene. Se vuoi agire sullo stato dell'oggetto puoi fare una cosa di questo tipo:
C:
int is_real(struct Complex *this)
{
    return !(this->imm);
}

Non scomoderei i puntatori a funzione amenoché non ne hai effettivamente bisogno. Non so precisamente quali siano le tue idee, ma a me non sembrano necessari.
 
La typedef puoi anche metterla, ma aggiungere dei puntatori tanto per aggiungerli non è una buona idea. Nel C non c'è un meccanismo di protezione dei dati, è tutto public. Potresti utilizzare un approccio alla Python: metti un underscore davanti al campo e per convenzione dici che l'utente non dovrebbe toccarlo.

I puntatori a funzione ti possono servire se vuoi dire che i metodi fanno parte dello stato dell'oggetto, altrimenti anche delle funzioni normalissime vanno più che bene. Se vuoi agire sullo stato dell'oggetto puoi fare una cosa di questo tipo:
C:
int is_real(struct Complex *this)
{
    return !(this->imm);
}

Non scomoderei i puntatori a funzione amenoché non ne hai effettivamente bisogno. Non so precisamente quali siano le tue idee, ma a me non sembrano necessari.

io avevo in mente che le funzioni fossero i metodi....il tutto però deve essere in un file *.h e *.c
 
Inizia a buttare giù qualche riga di codice, poi se vuoi qualche consiglio ne possiamo discutere.
Dividere in .h e .c è banale.

Il mio parere te l'ho dato, per le informazioni che mi hai dato per me il modo più pulito è:
  • funzioni normali (tipo la add che ti ho scritto) per le operazioni che non toccano lo stato;
  • funzioni che prendono l'istanza come primo parametro (tipo la is_real che ti ho scritto) per le operazioni che lavorano sullo stato corrente;
  • underscore per simulare i campi privati;
  • meglio ancora se le strutture le passi sempre come puntatori, almeno eviti di fare copie (anche se sono soltanto due int).
 
i due file sono cosi :

file complesso.h
C:
#include <math.h>
typedef struct complex{
    int a;    //Parte Reale
    int b;    //Parte immaginaria
    int (*c_real)(complex);
    int (*c_immaginary)(complex);
    float (*c_module)(int,int)
};

int rem    (complex); //Ritorna parte reale
int imm (complex); //Ritorna parte immaginaria
float modulo (complex); //Ritorna il modulo

file complesso.c
C:
#include <math.h>


int rem    (complex){
    return complex.a;
}

int imm(complex){
    return complex.b;
}

float modulo (complex){
    return sqrt(((complex.a *complex.a)+(complex.b * complex.b)));
   
}


Consigliami pure :)
 
Se usi i function pointers un costruttore è essenziale, inoltre il programma è sbagliato sintatticamente. Ecco le correzioni:
C:
typedef struct complex {
    int a;    //Parte Reale
    int b;    //Parte immaginaria
    int (*c_real)(struct complex);
    int (*c_immaginary)(struct complex);
    float (*c_module)(int,int);
} complex;

int rem(complex);      //Ritorna parte reale
int imm(complex);      //Ritorna parte immaginaria
float modulo(complex); //Ritorna il modulo

int rem (complex comp)
{
    return comp.a;
}

int imm(complex comp)
{
    return comp.b;
}

float modulo (complex comp)
{
    return sqrt(((comp.a *comp.a)+(comp.b * comp.b)));
}

Ti faccio notare che, una volta che avrai creato il costruttore, potrai fare queste due cose:
C:
int a = comp1.modulo(comp1);
int b = modulo(comp1);
Il secondo modo è quello che ti ho consigliato io (eliminando i function pointers), il primo ha una sintassi più simile a quella dei linguaggi OOP. Nel secondo caso hai la garanzia di chiamare il metodo modulo, perché è esattamente quello che stai facendo, nel primo caso invece questa garanzia non ce l'hai:
  • se non hai chiamato il costruttore, stai dereferenziando un NULL pointer e hai un crash;
  • se hai chiamato il costruttore, qualcuno potrebbe assegnare un altra funzione al function pointer.
Questo è quel che mi riferivo quando parlavo di "metodi che fanno parte dello stato". È una caratteristica in più, ma è qualcosa che vuoi usare? In C++ si preferiscono le reference (&) ai pointers (*) anche se i pointers sono delle reference con caratteristiche in più: avere meno caratteristiche ti porta a caratterizzare meglio quello che stai definendo. Se questo esempio non ti piace, pensa a const vs non-const, in un caso hai una caratteristica in più (posso cambiare il valore), nell'altro hai qualcosa di più specifico. Levando i function pointers, sebbene hai una sintassi meno vicina a quella dei programmi OOP, hai la garanzia che i metodi siano costanti, nel senso che non cambiano funzione eseguita.
È per questa ragione che te li ho sconsigliati, il "vantaggio sintattico" è solo uno specchio per le allodole, le due cose non sono perfettamente equivalenti.
 
Ultima modifica:
Scusa il primo si riferisce sempre al file header e il secondo al file .c

Il problema è che io devo emulare la classe e creare il famoso incapsulamento.... Ma quindi come dovrei fare scusa dichiarare le funzioni fuori e usare i costruttori per creare i puntatori alla funzione???




Inviata da ONE A2003 tramite app ufficiale di Inforge.net
 
Guarda senza offesa ma stai cercando di fare qualcosa di molto più complesso di quel che credi, anche se, naturalmente, è molto interessante.
Personalmente creai, quasi per sport, un'intera libreria per programmare in C in modo molto OOP, seguendo naturalmente delle tecniche ben conosciute e ben spiegate in questo pdf:
https://www.cs.rit.edu/~ats/books/ooc.pdf

Non è warez o altro, come vedete non è nemmeno un libro ma una specie di raccolta di tecniche di programmazione in ANSI C per ottenere un accettabile livello di object oriented programming.
E' stato scritto nel 1993 ma è ancora attualissimo.

Ora, ripeto, può essere molto istruttivo seguire quel libro e ampliare le conoscenze lì acquisite e creare una propria libreria personale ma, una volta finito, ti accorgerai che in realtà è inutile, solo un cosa per sport.
Se vuoi programmare OOP il C non è il linguaggio adatto.
Certo puoi avere uno stile che "ricorda vagamente" la programmazione OOP, ma molto vagamente, il resto è solo una complicazione e perdita di prestazioni.
Ciò non toglie che è molto istruttivo e anche divertente (dipende dal tuo concetto di divertimento ma per me lo è stato).

Morale del discorso, quel che cerchi di fare è molto più complesso di una typedef e di due puntatori a funzione in una struct.
Se vuoi ottenere un risultato accettabile la strada è molto più lunga, istruttiva, divertente ma inutile, e io te l'ho mostrata, ora sta a te decidere cosa fare.
 
  • Mi piace
Reazioni: Kolo93
Prova a scrivere direttamente il testo dell'esercizio e tutti potremo essere più chiari e di aiuto.
 
Simulare un classe che rappresenta i numeri complessi (z = a + bi) e con 3 funzioni rem, imm e modulo, le quali restituiscano la parte reale del numero complesso, la parte immaginari e il modulo di quest'ultimo...Creando una struttura dati adeguata e studiata durante il corso, tale che permetta l'incapsulamento
 
Ultima modifica:
Ok, non che mi piaccia come approccio, ma visto che queste sono le richieste, ecco qui la soluzione e una breve spiegazione @Erchomai :

complex.c
C:
#include <stdlib.h>
#include <math.h>
#include "complex.h"

#define _free(p) {if(p){free((void *)p); (p) = NULL; }}

typedef struct complex_pr {
    complex com;
    int r;
    int i;
} complex_pr;

static int rem_pr(complex_pr *comp) {
    return comp->r;
}

static int imm_pr(complex_pr *comp) {
    return comp->i;
}

static float modulo_pr(complex_pr *comp) {
    return sqrt(((comp->r *comp->r)+(comp->i * comp->i)));
}
static int rem(complex *comp) {
    if(comp == NULL) return 0;
    return rem_pr((complex_pr *) comp);
}

static int imm(complex *comp) {
    if(comp == NULL) return 0;
    return imm_pr((complex_pr *) comp);
}

static float modulo(complex *comp) {
    if(comp == NULL) return 0;
    return modulo_pr((complex_pr *) comp);
}

complex *new_complex(int r, int i) {
    complex_pr *comp_pr = NULL;
    if((comp_pr = (complex_pr *) malloc(sizeof(complex_pr))) == NULL)
        return NULL;
 
    comp_pr->com.c_real = rem;
    comp_pr->com.c_immaginary = imm;
    comp_pr->com.c_module = modulo;
 
    comp_pr->r = r;
    comp_pr->i = i;

    return (complex *) comp_pr;
}

void del_complex(complex *comp) {
    _free(comp);
}

complex.h
C:
#ifndef __INC_COMPLEX_H__
#define __INC_COMPLEX_H__

typedef struct complex {
    int (*c_real)(struct complex *);
    int (*c_immaginary)(struct complex *);
    float (*c_module)(struct complex *);
} complex;

complex *new_complex(int r, int i);
void del_complex(complex *comp);

#endif

Ed un piccolo test:
C:
#include <stdio.h>
#include "complex.h"

int main(int argc, char *argv[]){
    complex *c1 = new_complex(12, 2);
    complex *c2 = new_complex(22, 5);
 
    printf("test real c1 %d\n", c1->c_real(c1));
    printf("test imm c2 %d\n", c2->c_immaginary(c2));
    printf("test mod c1 %f\n", c1->c_module(c1));
 
    del_complex(c1);
    del_complex(c2);
 
    return 0;
}

E' abbastanza chiaro da capire, il trucco sta nel dividere un'interfaccia pubblica, e un metodo di init.
Il trick, giocando con le struct, è che inizializzi la struct privata, che però non è accessibile dall'esterno, e ritorni solo l'interfaccia pubblica.
E' importante, visto che giochiamo coi puntatori per fare questo, che l'interfaccia pubblica venga prima dei dati privati (prova a cambiare l'ordine dei membri della struct nella complex_pr e vedrai la catastrofe ;)).

Detto ciò, l'utente non ha accesso a nulla della struttura privata e non può operare direttamente su essa, visto che i metodi che eseguono le operazioni sono static.

L'utente finale potrà usare solo i metodi che sono accedibili tramite interfaccia pubblica (contenuta in complex.h) e ai metodi di init.
Abbiamo quindi realizzato un rudimentale incapsulamento.
 
Senza offesa ma già sono andato contro la mia etica avendo pubblicato il codice (fatto in 10 minuti quindi non perfetto).
Ora sta a te capirlo (anche se in realtà ho detto tutte le cose degne di nota nel mio commento) e migliorarlo.

Oppure se non ti sono chiari dei passaggi in particolare domandami di quelli, ma a parte un paio di cose che, ripeto, ho già spiegato nel commento, il resto è banale.
 
Ultima modifica:
Ok Grazie comunque... domanda ma perchè nel file .h nella struct non ci sono i due interi come li avevo usati io???? ma li hai messi nel file .c??
 
Perchè la tua richiesta era di realizzare l'incapsulamento.
Nel file .h c'è l'interfaccia pubblica, ovvero ciò che chiunque includerà l'header potrà vedere/utilizzare.
Siccome tu vuoi nascondere i dati implementativi e interni, quelli sono stati nascosti.

Infatti se per test tu provassi ad accedere ad un campo che è in complex_pr (file c) ti darebbe direttamente un errore in fase di compilazione.
Così facendo, ed usando solo metodi statici, l'unico modo per modificare quello che chiameremo impropriamente "oggetto" sarà di utilizzare i puntatori ai "metodi" presente nella struct complex presente nell'header file.
 
Stato
Discussione chiusa ad ulteriori risposte.