Σάββατο 28 Αυγούστου 2010

ΠΟΪΝΤΕΡΣ- pointers- δείκτες!!! Γιατί είναι τόσο δυσνόητα στη c/c++;

Έχουν γραφτεί εκατομμύρια βοηθήματα πάνω στους δείκτες, αλλά και πάλι δεν γίνετε να τους μάθεις μέσα σε μια μέρα. Προσωπικά μου πήρε ένα τρίμηνο να τους μάθω. Λοιπόν θα γράψω και εγώ ένα βοήθημα μέσα στα εκατομμύρια των βοηθημάτων, με την δική μου οπτική γωνία.

Λίγα μπλα μπλα.
Η γλώσσα C και ένα κομμάτι της C++ είναι low level, δηλαδή εσύ ο programmer έχεις τον πλήρη έλεγχο του hardware του PCιου. Μπορείς να γράψεις στη μνήμη ότι θες και οπότε θες WOW μπορείς να στείλεις ένα μήνυμα από τη σειριακή super WOW, μπορεί να σβήσει την οθόνη super duper WOW!!! Φυσικά και όλα αυτά είναι μπούρδες όταν μιλάμε για PC και προγράμματα. Γιατί; Γιατί ότι χρησιμοποιεί το πρόγραμμα μας είναι virtual-εικονικό.

Μνήμη
Είναι ένα κομμάτι hardware το οποίο μπορεί να κρατάει κάποια δεδομένα. Το interface που βλέπει ο προγραμματιστής είναι μια στίβα
+--Stack--+
|----5------|
|----4------|
|----3------|
|----2------|
|----1------|
|----0------|
+--End----+
στην οποία μπορεί να πάρει τα δεδομένα από οποιαδήποτε θέση.

Memory Management
Εδώ είναι όλο το ζουμί. Η διαχείριση της μνήμης από το λειτουργικό. Η φυσική (physical) μνήμη ενός συστήματος 4 gb με windows 32bit(x86) (εάν γνωρίζει κανείς το management άλλων συστημάτων ας μου το πει) χωρίζεται σε δυο κομμάτια. α) Kernel mode 2gb β) user mode 2gb. Το user mode αρχίζει από τη χαμηλή θέση, και το kernel παίρνει το υπόλοιπο. Πρόσβαση σε όλη τη μνήμη έχει μόνο ο kernel.

Τα παραπάνω είναι μονό μια μικρή εικόνα για να ξέρουμε τι παίζει πάνω κάτω. Στη πραγματικότητα όταν φτιάχνουμε ένα πρόγραμμα, δεν έχουμε πρόσβαση στη φυσική μνήμη αλλά στην εικονική-virtual. Αυτή προκύπτει από έναν μηχανισμό που λέγετε TLB. Χάρης αυτού του μηχανισμού (+ τις νέες δυνατότητες των x86 cpu) το κάθε πρόγραμμα μας έχει 4 gb μνήμη για πάρτη του, και το κάθε πρόγραμμα μας δεν μπορεί να παρέμβει στη μνήμη αλλού προγράμματος.

Εμάς δεν μας ενδιαφέρει πως δουλεύει ο TLB, μας ενδιαφέρει πως ακριβός "κάθετε" το πρόγραμμα μας πάνω στη εικονική μνήμη που μας δίνει το σύστημα. Ένα πρόγραμμα στην ουσία είναι data, τα οποία περνάνε από το cpu και αυτό κάνει τις αντίστοιχες πράξεις.


Το πρόγραμμα πάνω στη μνήμη
Όταν αρχίσει το πρόγραμμα μας να τρέχει, τότε το σύστημα μας δίνει ένα κομμάτι μνήμης το οποίο αρχίζει από το 0x0000 0000 και τελειώνει στο 0xFFFF FFFF (αυτό μπορεί να είναι μεγαλύτερο, εξαρτάται από την αρχιτεκτονική). Αυτό το κομμάτι χωρίζετε σε 4 κατηγορίες, text,bss,heap,stack.
Η σειρά τους είναι συγκεκριμένη, έστω οτι έχουμε την παρακάτω μνήμη (εικονική, αλλά πλέον θα τη λέω σκέτο μνήμη) η σειρά θα ήταν κάπως έτσι.


0x0000 0000
+---------------+
|------TEXT-----|
|------BSS------|
|------HEAP-----|
|- - - - - - - -|
|- - - - - - - -|
|----STACK------|
|---------------|
+---------------+

0xFFFF FFFF


Όταν φτιάχνουμε ένα πρόγραμμα, αυτό χρησιμοποιεί και τις 4 κατηγορίες. Φυσικά υπαρχή περίπτωση να χρησιμοποιήσουμε μόνο 2 ή και 1 κατηγορία, όταν έχουμε να κάνουμε υπερβολικά απλά πράγματα.

TEXT (+bss)
Εδώ αποθηκεύεται:
1) Ο κώδικας (χοντρά χοντρά)
2) Οι global/static variables
3) Και τα strings
4) Στο κομμάτι bss ζουν οι global/static variables που είναι initiliased
Ένα παράδειγμα
int g = 0;     //BSS
int ug;        //TEXT
int main(void) //TEXT
{
int i;
return 0;
}


Stack
Εδώ μένουν οι local variables της συνάρτησης που εκτελείται. Μέτα την εκτέλεση, ο χώρος ελευθερώνεται. Πως ακριβός γίνεται αυτό;
Έστω ότι έχουμε το παρακάτω πρόγραμμα:
int main(int arg,char** args) 
{
int a,b,c;  //STACK
return 0;
}

Όταν τρέξουμε το πρόγραμμα, το σύστημα καλεί την main, αυτή θέλει χώρο για arg,argc,a,b,c αυτά μπαίνουν στη stack, όταν τελειώσει η εκτέλεση της συνάρτησης τότε ο χώρος αυτός στη stack απελευθερώνεται.

Pointer
Τόσα μπλα μπλα για μνήμες stacks και διάφορα άλλα, αλλά από pointers τίποτα..
Λοιπόν οπός είπα και πιο πάνω, το πρόγραμμα μας είναι χωρισμένο στη μνήμη σε κάποια κομμάτια. Ο pointer μας δίνει την δυνατότητα επεξεργασίας μέσου της θέσεις του κάθε αντικειμένου στη μνήμη. Τι είπα τώρα ε; Ας αναλύσουμε πρώτα τι είναι που είναι κλπ.

Ο τύπος του pointer
Ο pointer είναι ένας τύπος, οπός λεμέ int,char,long etc.. και ο pointer είναι void* . Μην το μπερδεύεται με int* char* long* etc, αυτά είναι reference. Θα μου πει κάποιος ότι και το void* είναι reference to void, να αλλά το void είναι κενό, αρά ο pointer μας είναι "μονός". Το τι είναι reference θα το αναλύσω πιο κάτω.

Το μέγεθος του pointer
Οπός και οι άλλοι τύποι έτσι και αυτός έχει μέγεθος με τη μόνη διαφορά ότι δεν είναι συγκεκριμένος. Τέλος πάντων αν μας ενδιαφέρει το μέγεθος μπορούμε να το πάρουμε με το sizeof(void*).

Ο pointer ως αριθμός
Ο pointer μπορεί να προσδιοριστεί ως αριθμός, με όλα αυτά που μπορείς να κανείς με έναν αριθμό αριθμητικές/λογικές πράξεις.


Ο pointer στο πρόγραμμα μας ως lval
Σε ένα πρόγραμμα υπάρχει παντού ένας pointer ως lval. Τι είναι lval; Είναι η συντομογραφία του left value, τι είναι αυτό; Λοιπόν ας δούμε πρώτα τι είναι το rval (right value). Έστω ότι έχουμε το super duper πρόγραμμα
int main(void) 
{
int value = 1000;

return 0;
}

Βλέπουμε ότι έχουμε μια μεταβλητή int η οποία έχει τη τιμή 1000, λοιπόν αυτή η τιμή είναι το rval. Για να γίνει πιο εύκολο στη κατανόηση ας φτιάξουμε ένα define το οποίο θα παίρνει την rval από το αντικείμενο μας:
#define rval_of(x) (x)
Και ας το εφαρμόσουμε στην εκτύπωση της
#define rval_of(x) (x)
int main(void)
{
int value = 1000;
printf("%d",rval_of(value));
return 0;
}

Αυτό το super define μας δείχνει την rval. Φοβερό;
Τώρα για να πάρουμε την lval θα πρέπει να χρησιμοποιήσουμε το σύμβολο &, αυτό το βάζουμε μπροστά από το όνομα της μεταβλητής με αποτέλεσμα να πάρουμε την lval. Για την εύκολη κατανόηση θα φτιάξω και άλλο define το οποίο θα παίρνει το lval.
#define lval_of(x) (&x)
Ας εκτυπώσουμε την lval του παραπάνω προγράμματος.
#include <stdio.h>

#define rval_of(x) (x)
#define lval_of(x) (&x)
int main(void)
{
int value = 1000;
printf("rval of value:\t%d\n",rval_of(value));
printf("lval of value:\t%p\n",lval_of(value));//%p ektypwnei pointers
return 0;
}

το αποτέλεσμα θα είναι
rval of value:  1000
lval of value:  0014FD90

Οπός είπα και πιο πάνω, το lval είναι ο pointer του αντικειμένου, άρα lval = pointer.

Reference - Dereference
Είναι η δυνατότητα των pointers να μας δίνουν τα δεδομένα που δείχνουν.
Reference είναι διαδικασία που περνούμε τον pointer από ένα αντικείμενο, θα μου πεις "μια στιγμή αυτό δε κάναμε πιο πάνω;" Ακριβός. Λοιπόν το reference γίνετε οπός είδαμε και πιο πάνω με το σύμβολο & μπροστά από το αντικείμενο το dereference γίνετε με το σύμβολο * μπροστά από τον pointer. Για αυτό ας κάνουμε δυο define ώστε να μη χάσουμε τη μπάλα.
#reference(x) (&x)
#dereference(x) (*x)

Είπαμε ότι ο pointer είναι ένας τύπος void*, το reference δεν μας επιστρέφει void* αλλά pointed to type. Αυτό είναι που μπερδεύει, για αυτό ας αναλύσουμε ακόμα περισσότερο το παρακάτω:
#define reference(x)   (&x)
#define dereference(x) (*x)

int main(void)
{
    int value  = 1000;
/*   ^             ^
     +-------------|----- size of element
                   +----- right value
*/
    int *pvalue = reference(value);
/*   ^     ^
     |     +------------- left value (pointer)
     +------------------- size of element
*/
return 0;
}


Βλέπουμε ότι ένα αντικείμενο δεν έχει μονό τη lval και rval αλλά άλλη μια που είναι το size. Για ποιο λόγο υπάρχει αυτό; Λοιπόν αυτό γίνετε γιατί ναι μεν ο pointer δείχνει την διεύθυνση οπού είναι το αντικείμενο μας αλλά δε ξέρει το μέγεθος του αντικειμένου. Τι εννοώ; Έστω o,τι έχουμε έναν pointer οποίος δείχνει 0x00d0 0001 αρά η μνήμη θα είναι κάπως έτσι:
            +--------+
0x0d00 0001 |___2____|
0x0d00 0002 |__55____|
0x0d00 0003 |___0____|
0x0d00 0004 |___1____|
            +--------+

Ωραία, ο pointer μας δείχνει το 0x0d00 0001 αλλά που ξέρω τι ακριβός είναι αυτό στη μνήμη; Μπορεί να είναι 1 char μπορεί να είναι 1 int μπορεί να είναι ένα long κλπ κλπ. Για αυτό και υπάρχει το "σύστημα" pointed to type. Έτσι εάν πχ ο pointer είναι pointed to short τότε θα ξέρει ότι πρέπει να διαβάσει από το 0x0d00 0001 μέχρι 0x0d00 0002.

Λοιπόν, για να κάνουμε dereference πρέπει το αντικείμενο μας να είναι pointed σε κάποιο τύπο. Οπωσδήποτε πρέπει να είναι pointer σε αντίθεση με το reference που μπορεί να γίνει και σε pointer.

Πχ reference σε pointer
#define reference(x)   (&x)
#define dereference(x) (*x)

int main(void)
{
    int value  = 1000;

int *pointer_of_value
= reference(value);

int **pointe_of_pointer_of_value
= reference(pointer_of_value);

return 0;
}



Αυτό σίγουρα μπερδεύει, ας το αναλύσουμε.
Πρώτα έχουμε μια μεταβλητή value που έχει τιμή 1000
Μέτα έχουμε μια άλλη που είναι pointed to int (η απλά int*) αυτή με τη σειρά της έχει το pointer της value τέλος έχουμε άλλη μια μεταβλητή η οποία έχει το pointer της pointer_of_value. Λοιπών ας δούμε τι παίζει στη μνήμη
+----address-----+----data------+
|0x0d00 000f     | 0x0000 000b  |
|0x0d00 000b     | 0x0000 0008  |
|0x0d00 0008     | 1000         |
+----------------+--------------+










Απ'ο,τι βλέπουμε το μονό που κάναμε είναι να αλλάξουμε την lval με την rval τίποτα το σπουδαίο.

Και τώρα έρχεται το dereference, τι θα κάνουμε για να πάρουμε τα δεδομένα από τη value έχοντας την pointer_of_pointr_of_value;
int main(void) 








{
    int value  = 1000;
   
int *pointer_of_value
= reference(value);
int **pointe_of_pointer_of_value
= reference(pointer_of_value);

int *p_of_value = dereference(pointe_of_pointer_of_value);

int dereferenced_value = dereference(p_of_value);

return 0;
}


Casting a pointer


Οι δείκτες έχουν την δυνατότητα να μετατραπούν, είτε σε άλλο τύπο (πχ int, long, double etc) είτε σε άλλο  pointed type (πχ int* long* double* etc..). Μας ενδιαφέρει η μετατροπή σε άλλο pointed type.
Πχ μετατροπή int* σε char*



int main(void)    
{
int i = 213;
char *c = (char*) &i;
return 0;
}


Τι πρέπει να προσέχουμε όταν κάνουμε casting;
α) Το αντικείμενο στο οποίο γίνετε το cast πρέπει να είναι μικρότερο ή ίσο με το αντικείμενο το οποίο γίνετε το casting


int main(void)    
{
    int i = 213;
    char *c = (char*) &i;
//        ^            ^
//        +------------|------> sizeof(c) = 1
//                     +------> sizeof(i) = 4
return 0;
}

Αν κάνουμε το αντίθετο, τότε υπάρχει πιθανότητα να πάρουμε ένα stackoverflow ή heapoverflow (θα τα εξηγήσω πιο κάτω)
β) Όταν γίνετε cast σε δείκτες, το αντικείμενο ΔΕΝ μετατρέπετε! Το μονό που μετατρέπετε είναι το size (με την εννιά του τρίτου value, οπός είπα πιο πάνω για τα lval,rval)  του pointer.
Πχ


int main(void)    
{
float f = 23.0f;
int *i = (int*) &f;
printf("%d",*i);//θα βγάλει κάποιο άκυρο αριθμό
return 0;
}


Θα μου πεις για πιο λόγο να κάνω cast έναν δείκτη, η απάντηση είναι για μεταφορά δεδομένων. Πχ αν θες να στείλεις ένα int μέσου socket θα πρέπει να το μετατρέψεις σε buffer (unsigned char array) γιατί πολύ απλά η socket δεν στέλνει ints αλλά buffers.

Pointer as function arguments
Η πιο κλασική χρήση των δεικτών είναι να μπαίνουν ως παραμέτρους στις συναρτήσεις. Και η πιο κλασική απορία ενός αρχαρίου (οπός εγώ) είναι:
Ποια η διαφορά του void foo(int i) με void foo(int* i)
Η απάντηση:
Η πρώτη παίρνει αντικείμενα by val και η δεύτερη by ref. Για να μην ξαναγράφω τα ιδία πράματα.
by ref (by reference) είναι συνώνυμο του by lval το οποίο έχω αναλύσει πιο πάνω. (Ο pointer στο πρόγραμμα μας ως lval)
Και το by val (by value) είναι συνώνυμο του by rval.

Να δούμε τι παίζει με αυτές τις δυο συναρτήσεις.


void foo(int *p)
{
*p = 0;// <------+
} //               |
void fee(int q) //   |
{ //               |
q = 0;// <-------+---------+
} //               |         |
int main(void)//     |         |
{ //               |         |
int i = 1000;//  |         |
fee(i);//--------+---------+ by val
foo(&i);//-------+ by ref
return 0;
}


Στο παραπάνω παράδειγμα η fee παίρνει τη τιμή του i δηλαδή το 1000. Πιο αναλυτικά:
Η main καλεί την fee
Αυτή θέλει 8 byte για τα agruments (int q : 4 bytes) (return address 4 byte)
η fee μπαίνει στη stack
η main βάζει στη stack τον αριθμό 1000
η fee βάζει το 0 στο agrument q
η fee φτάνει στη return address
η stack ξεφορτώνει τη fee (διαγράφεται το int q )
Άρα η fee δεν έκανε τίποτα, απλός πήρε μνήμη, έφτιαξε ένα αντικείμενό, του έβαλε τη τιμή 0 και μετά το διέγραψε. Αποτέλεσμα, το i (στη main) έχει τη τιμή που είχε.

Στη δεύτερη περίπτωση:
Η main καλεί την fee
Αυτή θέλει 8 byte για τα agruments (void* p: 4 bytes) (return address 4 byte)
η fee μπαίνει στη stack
η main βάζει στη stack τον δείκτη (lval) του i και όχι τη τιμή
στη fee γίνεται το dereference του p και βάζει τη τιμή 0 (δηλαδή βάζει τη τιμή 0 στη θέση μνήμης που είναι το i)
φτάνει στο return και ξεφορτώνεται από τη stack (διαγράφεται ο δείκτης).
Έτσι στη main η μεταβλητή i άλλαξε τιμή από 1000 σε 0.

Pointer & static arrays
Ένα λάθος που γίνετε σε πολλά βοηθήματα για δείκτες (κατά τη γνώμη μου), είναι η ταύτιση του δείκτη με στατικό (και μη)  πινάκα. Ο δείκτης δεν είναι πινάκας, αλλά μπορεί να δείχνει έναν πινάκα. Το μαγικό κλειδί σε αυτή την υπόθεση δεν είναι ο δείκτη αλλά ο πινάκας, για την ακρίβεια, η στοίχιση των αντικειμένων.


int main(void)
{
int arr[3] = {1,2,3};
/*
+-address----+-data-+
|0x0d00 0000 | 1    |
|0x0d00 0004 | 2    |
|0x0d00 0008 | 3    |
+------------+------+
*/
return 0;
}



Βλέπετε ότι το κάθε στοιχειό είναι στοιχισμένο. Ο δείκτης ενός πινάκα δείχνει πάντα το πρώτο στοιχειό. Λογού στοίχισης, ξέρουμε ότι το δεύτερο στοιχειό βρίσκεται μετά από το πρώτο, το τρίτο μετά από το δεύτερο κλπ κλπ.
Reference-Dereference ενός στατικού πινάκα δεν χρειάζεται, διότι γίνετε αυτόματα
πχ
int arr[2] = {0,1};
int *parr = arr;
Το dereference γίνετε εύκολα με τα σύμβολα [n] (οπού n η θέση του στοιχείου) μπροστά από τον δείκτη και δύσκολα με τον αστερίσκο *.
με []


int main(void)
{
int arr[3] = {1,2,3};
printf("%d", arr[1]);
return 0;
}

με *


int main(void)
{
int arr[3] = {1,2,3};
printf("%d", *(arr+1));
return 0;
}

Σημείωση: το n αρχίζει από το 0. Δηλαδή το arr[0] μας δείχνει το πρώτο στοιχειό και ΌΧΙ το arr[1] το ίδιο και για το αστερίσκο, το *(arr) δείχνει το πρώτο και όχι το *(arr+1).


Η δήλωση ενός στατικού πινάκα σα παράμετρο σε μια συνάρτηση γίνετε με δυο τρόπους.
1) void fee(int arr[3])
2) void fee(int *arr,int size) (οπού size το πλήθος (και όχι μέγεθος) του πινάκα)
Γιατί στο πρώτο περνάμε μονό το πινάκα ενώ στο δεύτερο περνάμε και το πλήθος των στοιχείων; Γιατί ο πρώτος είναι στατικός (fixed size) και μπορούμε να πάρουμε το μέγεθος του με το sizeof. Ο δεύτερος δε, δεν είναι fixed size με αποτέλεσμα να μην γνωρίζουμε το μέγεθος, επειδή απλά έχουμε μονό έναν δείκτη.
Φυσικά συνιστώ το δεύτερο τρόπο ( void fee(int *arr)).

Τέλος κρατάμε 3 πράματα.
1) Ένας δείκτης μπορεί να δείχνει και πινάκα
2)
 α) reference γίνετε αυτόματα, και περνούμε το δείκτη του πρώτου στοιχειού.
 β) Μπορούμε να κάνουμε reference και σε άλλο σημείου του πινάκα με δυο τρόπους, int *parr = &arr[1] ή int *parr = arr+1
3) Ο πινάκας έχει fixed size. Άλλα επειδή το sizeof μας επιστρέφει το μέγεθος σε byte πρέπει να το μαγειρέψουμε ώστε να πάρουμε το πλήθος. πχ int elements_count = sizeof(arr) / sizeof(typo_of_array)


Pointers & dynamic arrays
Ο δυναμικός πίνακάς έχει και αυτός τα ίδια χαρακτηρίστηκα με το στατικό πίνακά, πιο αναλυτικά:
1) Τα στοιχεία στοιχίζονται ένα μετά το άλλο.
2) Γίνετε dereference με [] και *, οπός και στο στατικό.
3) Ο δείκτης δείχνει πάντα το πρώτο στοιχείο.

Διαφέρει στο ότι δεν ζει στη stack αλλά στη heap, αυτό έχει ως αποτέλεσμα:
1) Για να φτιάξουμε έναν τέτοιο πίνακά, θέλουμε βοήθεια από το λειτουργικό.
2) Φτιάχνετε την ώρα που εκτελείτε το πρόγραμμά.
3) Το μέγεθος είναι κυμαινόμενο.
4) Ορίζουμε εμείς πότε θα δημιουργηθεί και πότε θα διαγραφεί (εάν δε το διαγράψουμε, αυτό θα διαγραφεί με το που θα τελειώσει η εκτέλεση του προγράμματος)
5) Το μέγεθος του δεν έχει περιορισμούς, σε σχέση της stack που είναι μερικά mega bytes.

Τέτοιους πίνακές φτιάχνονται με memory allocator. Η δουλεία του memory allocator είναι η διαχείριση της heap, και είναι υλοποιημένο στο λειτουργικό. Ο πιο γνωστός είναι ο malloc (memory allocator). (Θα μου πεις, κάτσε ρε δικέ μου, τα linux και τα windows έχουν τον ίδιο allocator; Η απάντησή είναι όχι. Στα win η malloc καλεί την HeapAlloc που είναι ο allocator των win) Ο δεύτερος είναι ο new. Η διάφορα τους είναι ότι ο ένας προορίζεται για Object Oriented Programming  (αυτός είναι o new) τίποτα παραπάνω.

Αυτοί είναι οι δείκτες σε C/C++, τώρα ας δούμε τι έχει η C++ STL

Η C++ με αντίθεση τη c έχει μια κατηγορία από δείκτες, δηλαδή δεν έχει μονό το τύπο void*. Η κατηγορία είναι:
1) Raw pointer
2) References
3) Iterators
4) Smart poiters

Raw pointer
Είναι ο κλασικός δείκτης που έχω αναλύσει πιο πάνω. Το μονό που πρέπει να έχουμε καλά στο μυαλό μας είναι ότι όταν ο raw pointer βγαίνει έξω από το scop ή διαγράφεται, τότε πριν τη διαγραφή του καλείται ο destructor του αντικειμένου που δείχνει (το ίδιο πράγμα γίνεται όταν κατασκευάζεται)
πχ
class Object
{
public:
Object(){}
~Object(){}
};


int main(void)
{
{//some scope
Object o; //--> call Object::Object() (constructor)
}//--> call Object::~Object()             (destructor)


Object *o = new Object();//--> Create memory space and call
//constructr
delete o ; //--> call destructor and free memory


return 0;
}




Reference

Με το reference (type& obj) μαρκάρουμε ένα αντικείμενο με τη "ταμπέλα" ότι γίνετε αναφορά στο γνήσιο αντικείμενο και όχι σε ένα αντιγράφω.
πχ


class Object
{
private:
int i;
public:
Object(){}
~Object(){}
void foo(int b){i=b;}
void print(){std::cout<<i<<endl;}
};


void foo(Object& obj)//real Object
{
obj.foo(1); //set 1 sto pragmatiko Object o
}
void fee(Object obj)//copy of object
{
obj.foo(2);// set 2 sto prosorini copia
}
int main(void)
{
Object o;
foo(o);
o.print();//output 1
fee(o);
o.print();//output 1
return 0;
}




Να σημειώσω ότι όταν έχουμε reference ως μεταβλητές στις συναρτήσεις, γλιτώνουμε το copy που γίνεται.




















3 σχόλια:

  1. "Το interface που βλέπει ο προγραμματιστής είναι μια στίβα στην οποία μπορεί να πάρει τα δεδομένα από *οποιαδήποτε* θέση." Δεν ισχύει το LIFO;

    ΑπάντησηΔιαγραφή
  2. LIFO ειναι "τακτοποιηση" στην δημιουργια και διαγραφη των δεδομενων. Αυτο που γραφω ειναι η "ιδανικη" δομη της μνημης. Το αν εφαρμοζεται LIFO ή FOLI ειναι αλλο θεμα.

    ΑπάντησηΔιαγραφή
  3. Πολύ ωραίο άρθρο, δε το διάβασα όλο(είναι λίγο αργά τώρα που κάνω αυτό το ποστ) αλλά θα το διαβάσω γιατί ο τρόπος που τα εξηγείς είναι απλοϊκός και μπορώ να πω ότι με τραβάει να το διαβάσω :D Ασχολούμαι και γω με c++ και Game Developement. Από τα άρθρα σου βλέπω πως έχεις αρκετές γνώσεις και μπαίνεις βαθιά στη γλώσσα. Θέλω να γίνω "Σωστός" και καλός προγραμματιστής και αν μπορείς θα ήθελα να με βοηθήσεις και να με καθοδηγήσεις λίγο αν έχεις χρόνο βέβαια.
    Αυτό είναι το blog μου θα βρεις facebook & Mail αν θες να μου στείλεις. Αν έχεις msn για να μιλήσουμε ακόμα καλύτερα. << endl;
    return thankYou; :D

    ΑπάντησηΔιαγραφή