C και αντικειμενοστραφής προγραμματισμός

...IDE, compilers, κλπ

Συντονιστής: konnn

C και αντικειμενοστραφής προγραμματισμός

Δημοσίευσηαπό alkismavridis » 19 Αύγ 2014, 17:45

Καλησπέρα σε όλους!

Γενικά γνωρίζω και δουλεύω κυρίως τη Java. Παρ όλα αυτά έχω κατά καιρούς ασχοληθεί με native γλώσσες, κατά κύριο λόγο με την assembly. C ξέρω λίγα πράγματα, θεωρώ τον εαυτό μου αρχάριο.

Τελευταία έψαχνα μία native και αντικειμενοστραφή γλώσσα να μάθω να δουλεύω.
Ψάχνοντας κατάλαβα ότι ακόμα και σε απλή C, μπορείς να υλοποιήσεις σε πολύ καλό βαθμό τη λογική του αντικειμενοστραφούς προγραμματισμού!
Φυσικά υπάρχουν οι γλώσσες όπως C++, Objective-C κτλ. Αλλά αν κάποιος για οποιονδήποτε λόγο θέλει να μείνει στη C, δε χρειάζεται να στερείται της δύναμης του oop.

Προσπαθώντας να βρω τον τρόπο να κάνω oop στην C, κρατούσα κάποιες σημειώσεις (και ταυτόχρονα έφτιαχνα προγραμματάκια για να δω πως λειτουργεί).
Σκέφτηκα λοιπόν να μοιραστώ τις σημειώσεις μου αυτές μαζί σας, να μου πείτε τη γνώμη σας! Βέβαια ακόμα ψάχνω βελτιώσεις οπότε κάθε παρατήρηση είναι ευπρόσδεκτη!

Η παρακάτω διαδικασία επιτρέπει και τον πολυμορφισμό!
Παραθέτω:


Η C μπορεί κάλλιστα να λειτουργήσει με αντικειμενοστραφή λογική.
Η σύνταξή της ίσως είναι λίγο πιο "άβολη" από μία γνήσια αντικειμενοστραφή γλώσσα, μιας και θα
πρέπει να κάνουμε χειροκίνητα κάποια πράγματα που κάνει ο compiler της αντικειμενοστραφούς γλώσσας.
Θα μας λείπει επίσης η πολυτέλεια της υπερφόρτωσης συναρτήσεων.

Πέρα όμως από τη σύνταξη, μπορούμε να "χτίσουμε" όλη τη λογική του αντικειμενοστραφούς προγραμματισμού
στη C, και να χρησιμοποιήσουμε τα οφέλη του. Αυτό μπορεί να γίνεται ως εξής:

1. Η κάθε "κλάση" περιέχει δύο structs. Θα τα πούμε Struct1 και Struct1$. Επίσης θα έχει ένα static πεδίο:
Μορφοποιημένος Κώδικας: Επιλογή όλων
static Struct1$ stat;


Η Struct1$ περιέχει πληροφορίες που αφορούν όλη την κλάση, δηλαδή static fields, και pointers
των συναρτήσεων (και static και non-static). Θα υπάρχει ΜΟΝΟ ένα instance αυτής της δομής που θα αφορά
όλη την κλάση.
Οι non-static συναρτήσεις θα παίρνουν σαν όρισμα ένα επιπλέον pointer για το struct που
τις "καλεί". Ο void pointer δε μας πειράζει, αρκεί να προσέχουμε όταν θα καλούμε τις συναρτήσεις.
H Struct1 περιγράφει το instance του αντικειμένου. Δηλαδή περιέχει τα non-static fields, ΚΑΙ ένα pointer για
Struct1$. Αυτή είναι η "σύνδεση" του αντικειμένου μας με τα static στοιχεία της κλάσης του.

Να πως θα μοιάζουν τα δύο struct:

Μορφοποιημένος Κώδικας: Επιλογή όλων
typedef struct
{
SuperStruct$* super; //1. Το πρώτο στοιχείο είναι pointer στην "$" struct της "κλάσης" που επεκτείνουμε.
// αν δεν επεκτείνουμε καμία κλάση, παραλείπουμε

int some_static_field; //2. Νέα fields και συναρτήσεις. ΟΧΙ αυτά που θέλουμε να κάνουμε Override, ΜΟΝΟ νέα!

void* (*setValue)(void*, int);
int (*getValue)(void*);
}
Struct1$;

typedef struct
{
SuperStruct* super; //1. pointer στην υπερ-κλάση. Αν δεν επεκτείνουμε κάποια "κλάση" παραλείπουμε...
int value; //2. Νέα πεδία...
}
Struct1;


2. Η κλάση πρέπει να έχει έναν "constructor" για την δομή Struct1$, και ένα (ή περισσότερους)
για την Struct1.
Ο πρώτος χρειάζεται να εκτελεστεί μόνο μία φορά από την κλάση μας, και μία
φορά από κάθε κλάση που επεκτείνει τη δική μας. Η "κλάση" Struct1 (και κάθε "κλάση"
που την επεκτείνει) χρειαζεται μόνο ΕΝΑ αντικείμενο Struct1$. Ο constructor θα μοιάζει κάπως έτσι:

Μορφοποιημένος Κώδικας: Επιλογή όλων
void newStruct1$(Struct1$* ptrs)
{
newSuperStruct$((SuperStruct$*)ptrs); //1. Το πρώτο πράγμα που κάνει ένας constructor είναι να καλεί τον constructor
// της μητρικής του "κλάσης". Αν δεν επεκτείνουμε κάποια "κλάση", παραλείπουμε

((SuperStruct$*)ptrs)->old_field = 5; //2. Κάνουμε Override όποια fields ή functions θέλουμε από την μητρική κλάση...
((SuperStruct$*)ptrs)->oldFunction = &newFunction; //Το όνομα της συνάρτησης θα συνεχίζει να είναι oldFunction.
//newFunction είναι απλά η διεύθυνση του νέου κώδικα που θα εκτελείται!


ptrs->some_static_field=8; //3. Δίνουμε τιμές στα νέα πεδία... Για functions δίνουμε τις διευθύνσεις
ptrs->getValue = &getValue; // του κώδικα, βάζοντας απλά το όνομά του με το σύμβολο &.
ptrs->setValue = &setValue;
}


3. Ο "constructor" της κλάσης Struct1 θα δέχεται ένα pointer Struct1, ένα char, συν ό,τι άλλο χρειάζεται. Θα
επιστρέφει pointer με το αντικείμενο που δημιούργησε.

Η λογική των δύο ορισμάτων είναι η εξής:
Πρώτο όρισμα: Αν θέλουμε ο constructor να δημιουργήσει χώρο στη μνήμη για το νέο struct,
δίνουμε NULL, αλλιώς δίνουμε την διεύθυνση του Struct1 που ήδη έχουμε.
Δεύτερο όρισμα: Πάντα 0 όταν δημιουργούμε δημιουργούμε ένα νέο αντικείμενο. Πάντα 1 όταν καλούμε τον constructor
μέσα από έναν άλλο constructor.

Ο pointer επιστροφής μπορεί να είναι void για να αποφύγουμε το typecasting.
Ο constructor θα μοιάζει κάπως έτσι:

Μορφοποιημένος Κώδικας: Επιλογή όλων
void* newStruct1(Struct1* ptr, char old)
{
if (ptr==NULL) ptr = malloc(sizeof(Struct1)); //Βήμα 1: ΤΥΦΛΟΣΟΥΡΤΗΣ, κάθε constructor θα αρχίζει έτσι, αντικαθιστώντας
if (!old) // όμως τα Struct1 και Struct1$ με τα αντίστοιχα ονόματα.
{
if (*(char*)(&stat)==0) newStruct1$(&stat);
*(Struct1$**)ptr = &stat;
}


newSuperStruct((SuperStruct*)ptr, 1); //Βήμα 2: καλούμε τον constructor της μητρικής κλάσης
//με ορίσματα ptr και 1. Αν δεν επεκτείνουμε κάποια κλάση παραλείπουμε...

ptr->value = 5; //Βήμα 3. Τακτοποιούμε τα fields και ό,τι άλλο χρειάζεται.
//Για field της υπερκλάσης γράφουμε ((SuperStruct*)ptr)->old_field=...;

return ptr; //Βήμα 4. Επιστρέφουμε το pointer...
}


Σημείωσε ότι τα 2 πρώτα βήματα του constructor είναι τυφλοσούρτης. "Ελεύθερη βούληση" υπάρχει μόνο στο βήμα 3.

4. Χρησιμοποιούμε τα αντικείμενα που φτιάξαμε:

Μορφοποιημένος Κώδικας: Επιλογή όλων
void main()
{
Struct1* s = newStruct1(NULL,0);

printf("Value: %d\n", s->st->getValue(s));
s->st->setValue(s, 10);
printf("New value: %d\n", s->st->getValue(s));

free(s);
}


Μπορεί να θεωρείς ότι το s->st είναι περιττό, και θα μπορούσαμε να καλούμε κατ ευθείαν το
&stat. Αλλά δεν είναι έτσι! Αν το Struct1 μας... δεν είναι πραγματικά Struct1, αλλά κάτι που επεκτείνει
το Struct1, τότε το s->st ΔΕΝ είναι το &stat της "κλάσης" μας, αλλά το &stat της "κλάσης"
που επεκτείνει τη "δική μας".
Τελευταία επεξεργασία από alkismavridis και 20 Αύγ 2014, 22:41, έχει επεξεργασθεί 2 φορά/ες συνολικά
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: C και αντικειμενοστραφής προγραμματισμός

Δημοσίευσηαπό Ilias95 » 20 Αύγ 2014, 06:37

Και τα παραπάνω είναι ένα πολύ καλό παράδειγμα του ότι όταν θέλουμε να δουλέψουμε με OOP δεν χρησιμοποιούμε C. :P
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: C και αντικειμενοστραφής προγραμματισμός

Δημοσίευσηαπό alkismavridis » 20 Αύγ 2014, 18:56

Γιατί όχι;
Το τελικό αποτέλεσμα (στο βήμα 4) είναι νομίζω αρκετά εύχρηστο.
Αλλά και τα τρία πρώτα (όταν φτιάχνεις τις "κλάσεις") είναι απλός τυφλοσούρτης. Τα μαθαίνεις μία φορά, και μετά τα χρησιμοποιείς, σχεδόν από μνήμης.

Που θεωρείς ότι βρίσκεται το αδύναμο σημείο της παραπάνω διαδικασίας (ή κάποιας παρόμοιας);
Ευχαριστώ για την απάντηση!
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: C και αντικειμενοστραφής προγραμματισμός

Δημοσίευσηαπό Ilias95 » 22 Αύγ 2014, 17:39

Συγγνώμη για την καθυστερημένη απάντηση.
Να πω την αλήθεια επειδή έχω και πολύ καιρό να ασχοληθώ με C και μου φαίνονται ψιλοκινέζικα και δεν είδα πολύ προσεκτικά τον κώδικα. Θα σου απαντήσω αργότερα πιο αναλυτικά.

Μπορείς να βάλεις σε ένα code tag όλο τον κώδικα που χρειάζεται για την δημιουργία μιας κλάσης και μια μεθόδου αυτής που να χρησιμοποιεί μια instance variable;
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: C και αντικειμενοστραφής προγραμματισμός

Δημοσίευσηαπό alkismavridis » 23 Αύγ 2014, 00:00

Φυσικά! Σου παραθέτω μία κλάση που δεν επεκτείνει κάποια άλλη, για να επεκτείνεις η διαδικασία διαφέρει σε 1-2- σημεία.
Το βασικό που καταφέρνεις με την παρακάτω διαδικασία είναι ότι επιτρέπεις τον πολυμορφισμό. Δηλαδή όταν επεκτείνεις την κλάση αυτή, μπορείς να κάνεις override όποιες συναρτήσεις θες...

Μορφοποιημένος Κώδικας: Επιλογή όλων
//HEADER, Μπορεί να είναι σε ένα /h αρχείο.
#include <stdlib.h>
#include <stdio.h>
#define MyClass(X) (*(MyClass$**)(X))

typedef struct //static fields and method pointers
{
void (*myFunction)(void*);
}
MyClass$;

typedef struct
{
MyClass$* st;
int field;
}
MyClass;

void newMyClass$(MyClass$*); //Δηλώνουμε τους 2 constructor και τη μέθοδό μας
void* newMyClass(MyClass*, char);
static void myFunction(void*);


//Το .c αρχείο μας
//#include "MyClass.h"
static MyClass$ stat;

void newMyClass$(MyClass$* ptrs) //Ο constructor της κλάσης, χρειάζεται να καλεστεί μόνο μία φορά.
{
ptrs->myFunction = &myFunction;
}

void* newMyClass(MyClass* ptr, char old) //Ο constructor του instance, αρχικοποιεί ένα MyClass.
{
if (ptr==NULL) ptr = malloc(sizeof(MyClass)); //1. ο τυφλοσούρτης
if (!old)
{
if (*(char*)(&stat)==0) newMyClass$(&stat);
*(MyClass$**)ptr = &stat;
}

ptr->field=8; //2. η επεξεργασία των fields...
return ptr;
}

static void myFunction(void* th) //Η μέθοδός μας
{
MyClass* this = (MyClass*)th;
printf("Είναι ένα αντικείμενο MyClass. Η τιμή του field μου είναι %d.\n", (this->field));
}

void main()
{
MyClass* m = newMyClass(NULL,0);
MyClass(m)->myFunction(m);
m->field=3;
MyClass(m)->myFunction(m);
}


Επίσης! Για να διευκολυνθώ, έφτιαξα ένα πρόγραμμα που δημιουργεί αυτόματα ένα .c και ένα .h αρχείο, με τα "βασικά" κομμάτια κώδικα έτοιμα!
Μπορείς να κάνεις compile κατ' ευθείαν τα output αυτού του προγράμματος!
Το παραθέτω και αυτό
Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>
#include <string.h>

char *class, *super;

static void writeHFile(FILE* fl)
{
fprintf(fl, "#ifndef %s_h\n", class);
fprintf(fl, "#define %s_h\n\n", class);
if (super!=NULL) fprintf(fl, " #include \"%s.h\"\n", super);
else fprintf(fl, " #include <stdio.h>\n #include <stdlib.h>\n\n");

fprintf(fl, " #define %s(X) (*(%s$**)(X))\n\n typedef struct\n {\n", class, class);

if (super!=NULL) fprintf(fl, "\t%s$* super;\n", super);

fprintf(fl, "\t//new static data and methods...\n }\n");
fprintf(fl, "\t%s$;\n\n typedef struct\n {\n", class);

if (super!=NULL) fprintf(fl, "\t%s super;\n", super);
else fprintf(fl, "\t%s$* st;\n\n", class);

fprintf(fl, "\t//new fields go here...\n }\n");
fprintf(fl, "\t%s;\n\n", class);

fprintf(fl, " void new%s$(%s$*);\n", class, class);
fprintf(fl, " void* new%s(%s*, char /*more arguments*/);\n\n", class, class);
fprintf(fl, " //Other Methods go here...\n#endif");
}


static void writeCFile(FILE* fl)
{
fprintf(fl, "#include \"%s.h\"\n\n", class);
fprintf(fl, "static %s$ stat;\n\n", class);
fprintf(fl, "void new%s$(%s$* ptrs)\n{\n", class, class);

if (super!=NULL) fprintf(fl, "\tnew%s$((%s$*)ptrs);\n", super, super);
if (super!=NULL) fprintf(fl, "\t//To override: ((%s$*)ptrs)->method = &method;\n", super);

fprintf(fl, "\t//New method: ptrs->method = &method;\n}\n\n");
fprintf(fl, "void* new%s(%s* ptr, char old /*more arguments*/)\n{\n", class, class);
fprintf(fl, "\tif (ptr==NULL) ptr = malloc(sizeof(%s));\n\tif (!old)\n\t{\n", class);
fprintf(fl, "\t\tif (*(char*)(&stat)==0) new%s$(&stat);\n\t\t*(%s$**)ptr = &stat;\n\t}\n\n", class, class);

if (super!=NULL) fprintf(fl, "\tnew%s((%s*)ptr, 1 /*other arguments*/);\n\n", super, super);
fprintf(fl, "\t//field initialization goes here...\n");
fprintf(fl, "\treturn ptr;\n}\n\n");

fprintf(fl, "//Other methods go here...\n\n//void main() {}");
}


int main(int argc, char** argv)
{
char cfilename[50], hfilename[50];
FILE *fl;

if (argc==1) //1. Check if argument exists
{
printf("\nUsage:\n\tc_class_creator Classname\n\tc_class_creator Classname SuperClassName\n\n");
return 1;
}

class = argv[1];
if (argc>2) super=argv[2];
else super=NULL;

strcpy(cfilename, class);
strcat(cfilename, ".c");
strcpy(hfilename, class);
strcat(hfilename, ".h");

fl = fopen(cfilename, "r"); //2. See if files exist...
if (fl != NULL) { fclose(fl); printf("File %s already exists.\n", cfilename); return 1; }

fl = fopen(hfilename, "r");
if (fl != NULL) { fclose(fl); printf("File %s already exists.\n", hfilename); return 1; }

fl = fopen(cfilename, "w"); //3. Do the job!
writeCFile(fl);
fclose(fl);

fl = fopen(hfilename, "w");
writeHFile(fl);
fclose(fl);

return 0;
}


Χρήση: αν του δώσεις ένα όρισμα, φτιάχνει μία κλάση με αυτό το όνομα, η οποία δεν επεκτείνει καμία.
αν του δώσεις δύο ορίσματα φτιάχνει μία κλάση με όνομα το πρώτο, που επεκτείνει μία κλάση με όνομα το δεύτερο.
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση


Επιστροφή στο Εφαρμογές για Ανάπτυξη Λογισμικού

cron