Μάθημα - Απλά Συνδεδεμένες Λίστες

...ασύγχρονα μαθήματα γλώσσας C

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 30 Ιουν 2011, 18:54

Star_Light έγραψε:Μigf1 μας εστρωσες στο διαβασμα!!! Αποψε θα διαβασω και τα καινουργια σου!

Χαλαρά, δεν υπάρχει λόγος βιασύνης έτσι κι αλλιώς. Δεν θέλουν και βιασύνη αυτά, γιατί δεν είναι κι ότι πιο εύκολο. Όπου κολλάτε πάντως μη διστάζετε να γράφετε απορίες, για να τις λύνουμε (όπου μπορώ εννοείται, γιατί κι εγώ δεν τα θυμάμαι κι όλα).

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

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

Ιδανικά θα μπορέσετε μόνοι σας κατόπιν ή έστω με λίγη βοήθεια να φτιάξετε κι άλλες συναρτήσεις, όπως π.χ. διαγραφή κόμβου, αναζήτηση κόμβου, αντιστροφή λίστας (reverse) κλπ.

Κι αν το πάρετε και πολύ ζεστά το πράγμα, θα είστε πολύ πιο έτοιμοι να περάσετε και σε πιο σύνθετα πράγματα, όπως π.χ. διπλά συνδεδεμένες λίστες (κυκλικές και μη), δέντρα, hash tables, κλπ.

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

Π.χ. έναν πίνακα με όλα τα γράμματα της αλφαβήτου, όπου το κάθε στοιχείο του πίνακα θα είναι μια απλά συνδεδεμένη λίστα από λέξεις που ξεκινάνε από αυτό το γράμμα και θα είναι ταξινομημένες.
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό Star_Light » 30 Ιουν 2011, 19:46

Kαι μετα παμε για κρυπτογραφηση????!!!! Με sockets εχεις ασχοληθει καθολου?????
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 30 Ιουν 2011, 20:10

Δυστυχώς όχι, ούτε με sοckets, ούτε με κρυπτογράφηση. Τα sockets δεν πρέπει να είναι δύσκολα πάντως.
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό Star_Light » 30 Ιουν 2011, 21:24

migf1 έγραψε:Δυστυχώς όχι, ούτε με sοckets, ούτε με κρυπτογράφηση. Τα sockets δεν πρέπει να είναι δύσκολα πάντως.


ΟΚ!! Αν θελεις μπορεις να δεις και εδω που εχω συγκεντρωσει την θεωρια τους.

viewtopic.php?f=61&t=19103

κρυπτογραφηση δεν εχω κανει ουτε εγω στον προγραμματισμο... αλλα εχω διαβασει την θεωρια της.
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 30 Ιουν 2011, 23:00

Το έχω διαβάσει :)

TCP και Networks γενικώς έχω διδαχτεί στο πανεπιστήμιο, αλλά δεν έτυχε να ασχοληθώ ξανά έκτοτε. Μπορεί να φαντάζει ζόρικο με την πρώτη ματιά, αλλά τα περισσότερα δεν "πατάνε" σε ιδιαίτερα πολύπλοκες έννοιες. Είναι περισσότερο θέμα του να εξοικειωθεί κανείς με τη στάνταρ βιβλιοθήκη των συναρτήσεων που διαχειρίζονται τα sockets.

Φυσικά αν θελήσει να εμβαθύνει, μπορεί όντως να γίνει εξαιρετικά πολύπλοκο αλλά για τα βασικά είναι απλά θέμα χρόνου μέχρι να βρει κανείς τις κατάλληλες συναρτήσεις που πρέπει να συνδυάσει.
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό Star_Light » 30 Ιουν 2011, 23:06

migf1 έγραψε:Το έχω διαβάσει :)

TCP και Networks γενικώς έχω διδαχτεί στο πανεπιστήμιο, αλλά δεν έτυχε να ασχοληθώ ξανά έκτοτε. Μπορεί να φαντάζει ζόρικο με την πρώτη ματιά, αλλά τα περισσότερα δεν "πατάνε" σε ιδιαίτερα πολύπλοκες έννοιες. Είναι περισσότερο θέμα του να εξοικειωθεί κανείς με τη στάνταρ βιβλιοθήκη των συναρτήσεων που διαχειρίζονται τα sockets.

Φυσικά αν θελήσει να εμβαθύνει, μπορεί όντως να γίνει εξαιρετικά πολύπλοκο αλλά για τα βασικά είναι απλά θέμα χρόνου μέχρι να βρει κανείς τις κατάλληλες συναρτήσεις που πρέπει να συνδυάσει.


Nαι ναι... για σενα θα ειναι πολυ περιπλοκο χαχαχαχαχαχαχαχαχχα
ε δεν αντεξα!!!!!!

Βαζω στοιχημα σε μιση ωρα θα χεις φτιαξει δικο σου mapper!!!!!
Θα του δινεις το domain ενος σαιτ ή το host και θα σου πεταει την IP!!!!!
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 30 Ιουν 2011, 23:58

Όχι κι έτσι ρε συ! Μακάρι να ήταν τόσο εύκολος ο προγραμματισμός γενικά! Με ένα καλό tutorial όμως, επιμονή και υπομονή γίνεται λίγο πιο εύκολος :)
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 01 Ιούλ 2011, 15:33

ΑΠΛΑ ΣΥΝΔΕΔΕΜΕΝΕΣ ΛΙΣΤΕΣ (Singly Linked Lists) - ΜΕΡΟΣ 4ο

Προσθήκη Νέων Κόμβων στο Τέλος της Λίστας
ΣΥΝΑΡΤΗΣΗ: list_append



Σε συνέχεια του 3ου μέρους, ας δούμε τώρα δυο διαφορετικές υλοποιήσεις μιας συνάρτησης list_append( &list, value) που θα δημιουργεί έναν νέο κόμβο, θα εκχωρεί την τιμή value στο αντίστοιχο πεδίο του κόμβου και θα τον προσθέτει στο τέλος της λίστας.

Στην πρώτη υλοποίηση θα χρησιμοποιήσουμε τον κλασικό τρόπο, ο οποίος θα διατρέχει τη λίστα από την αρχή μέχρι το τέλος της, για να προσθέσει κατόπιν τον νέο κόμβο. Είναι φανερό πως κοστίζει πολύ σε ταχύτητα (αφού διατρέχει ολόκληρη τη λίστα). Τη λίστα θα την περνάμε BY REFERENCE και όπως θα δούμε θα χρειαστούμε 2 βοηθητικούς δείκτες για να κάνουμε την εισαγωγή.

Ας ονομάσουμε αυτή τη συνάρτηση: list_append_slow() κι ας δούμε τον κώδικά της...

Κώδικας: Επιλογή όλων

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στο τέλος της λίστας
* με τον κλάσικό, αργό τρόπο που διατρέχει όλη την λίστα.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
Bool list_append_slow( Node **list, int value )
{
Node *head = NULL, *dummy = NULL; // βοηθητικοί δείκτες
Node *new = calloc(1, sizeof(struct node) ); // δημιουργία του νέου κόμβου
if ( !new ) // αποτυχία του calloc()
return FALSE; // πρόωρος τερματισμός

new->value = value; // αρχικοποίηση του νέου κόμβου
new->next = NULL;

if ( !*list ) { // εισαγωγή σε κενή λίστα
*list = new;
return TRUE;
}

// εισαγωγή σε μη κενή λίστα
head = dummy = *list; // τοποθέτηση βοηθητικών δεικτών στην αρχή της λίστας
while ( head ) { // ο head διατρέχει τη λίστα
dummy = head; // ο dummy "θυμάται" τον εκάστοτε προηγούμενο κόμβο
head = head->next;
}
dummy->next = new; // εισαγωγή του νέου κόμβου

return TRUE;
}

Ελπίζω πως και με τη βοήθεια των σχολίων, ο παραπάνω κώδικας είναι κατανοητός σε ικανοποιητικό βαθμό. Αν όχι, τότε θα πρέπει να διαβάσετε ξανά τα προηγούμενα μέρη του tutorial, μιας και οτιδήποτε χρησιμοποιείται σε αυτή τη συνάρτηση έχει ήδη αναλυθεί με λεπτομέρεια στα προηγούμενα μέρη. Αν πάραυτα υπάρχουν ακόμα απορίες, παραθέστε τες να τις επιλύσουμε.

Ας επισημάνω όμως πως επειδή τον δείκτη list (που δείχνει στην αρχή της λίστας) τον περνάμε BY REFERENCE στη συνάρτηση, δεν μπορούμε να τον χρησιμοποιήσουμε για να διατρέξουμε τη λίστα (θα χάναμε την επαφή μας με την αρχή της λίστας, αφού BY REFERENCE σημαίνει κατ' ουσίαν πως δουλεύουμε απευθείας με την αυθεντική μεταβλητή και όχι με κάποιο αντίγραφό της). Για αυτό και τη λίστα τη διατρέχουμε με τον βοηθητικό, τοπικό δείκτη: head.

Επίσης, επειδή στις απλά συνδεδεμένες λίστες μόλις προσπεράσουμε έναν κόμβο δεν έχουμε τρόπο να γυρίσουμε πίσω (η δομή των κόμβων δεν έχει πεδίο: prev, έχει μόνο: next) χρειαζόμαστε έναν δεύτερο βοηθητικό δείκτη (τον dummy στην περίπτωσή μας) ο οποίος είναι πάντα "ένα βήμα πίσω" από τον head. Έτσι όταν ο head βρει NULL (το τέλος της λίστας) τότε ο dummy δείχνει στον τελευταίο κόμβο της λίστας.

Οπότε, για να εισαγάγουμε τον (ήδη δημιουργημένο & αρχικοποιημένο) νέο κόμβο: new στο τέλος της λίστας, μόλις τελειώσει το loop απλώς βάζουμε το πεδίο next του dummy να δείχνει στον νέο κόμβο... dummy->next = new;

Σε αυτό το σημείο, σαν εξάσκηση για τα όσα έχουμε πει μέχρι τώρα, μπορείτε να δοκιμάσετε μόνοι σας να τροποποιήσετε την παραπάνω συνάρτηση ώστε να δέχεται τον δείκτη list BY VALUE (αντί για BY REFERENCE) και να επιστρέφει στη main την ανανεωμένη λίστα, έτσι ώστε όταν την καλείτε στην main() να εκχωρείτε την τιμή επιστροφής της στον αυθεντικό δείκτη list.

Π.χ. ...

Κώδικας: Επιλογή όλων

...
int main( void )
{
Node *list=NULL;
...
list = list_append_slow( list, 52);
list = list_append_slow( list, 62);
list = list_append_slow( list, 72 );
...
list_print( list );
...
list_destroy( list );
return EXIT_SUCCESS;
}


Ως αντιπαραβολή, με το BY REFERENCE πέρασμα του list στην συνάρτηση (ο κώδικας που σας έδωσα έτοιμο) το παραπάνω παράδειγμα γράφεται ως εξής...

Κώδικας: Επιλογή όλων

...
int main( void )
{
Node *list=NULL;
...
list_append_slow( &list, 52);
list_append_slow( &list, 62);
list_append_slow( &list, 72 );
...
list_print( list );
...
list_destroy( list );
return EXIT_SUCCESS;
}


Και στα δυο παραδείγματα παρέλειψα επίτηδες τους ελέγχους αποτυχίας της κάθε εισαγωγής, για να είναι πιο ξεκάθαρος ο κώδικας. Αν θέλετε, προσθέστε τους σύμφωνα με τα παραδείγματα που έχουμε δει στα προηγούμενα μέρη του tutorial.

Τώρα, αν η λίστα μας αποτελείται από τίποτα χιλιάδες κόμβους ή ακόμα χειρότερα από τίποτε εκατομμύρια κόμβους, είναι προφανές πως το να τη διατρέχουμε ολόκληρη κάθε φορά που θέλουμε να προσθέσουμε έναν νέο κόμβο στο τέλος της δεν θα είναι κι από τις καλύτερες στιγμές του προγράμματός μας :lol:

Θα μου πείτε, και γιατί να βάζουμε τους νέους κόμβους στο τέλος της λίστας και όχι στην αρχή της; Η απάντηση είναι πως δεν είναι ίδιες οι ανάγκες όλων των projects.

Π.χ. αν οι οι κόμβοι χαρακτηρίζονται από ημερομηνίες, τότε η εισαγωγή νέων κόμβων στην αρχή της λίστας όχι μόνο έχει νόημα, αλλά ενδέχεται να είναι και η πλέον ενδεδειγμένη. Για παράδειγμα, αν βάζουμε στην αρχή της λίστας την ημερομηνία που τροποποιείται από κάποιον χρήστη ένα αρχείο μαζί με το όνομα του χρήστη, τότε έχουμε αυτόματα ένα χρονολογικά ταξινομημένο log αρχείο για το ποιοι χρήστες και πότε τροποποίησαν το εν λόγω αρχείο (με τις πρόσφατες ημερομηνίες πρώτες).

Αν από την άλλη, η λίστα χρησιμοποιείται σε editor και κάθε κόμβος είναι μια γραμμή, τότε συνήθως ο κάθε νέος κόμβος μπαίνει στο τέλος της λίστας.

Για να εισαγάγουμε λοιπόν νέους κόμβους στο τέλος της λίστας όσο γρήγορα τους εισαγάγουμε και στην αρχή της, μπορούμε να χρησιμοποιήσουμε έναν δείκτη που θα δείχνει ανά πάσα στιγμή στον τελευταίο κόμβο της λίστας. Αντίστοιχος δηλαδή του δείκτη list που έχουμε τώρα, που δείχνει ανά πάσα στιγμή στην αρχή της λίστας.

Αυτό προϋποθέτει πως μαζί με τον δείκτη list θα πρέπει να διατηρούμε στην main() και έναν δείκτη που θα δείχνει ανά πάσα στιγμή στο τέλος της λίστας. Προφανώς θα πρέπει να τον περνάμε κι αυτόν μέσα στην συνάρτηση: list_append() μαζί με τον list.

Όταν χρησιμοποιούμε δύο δείκτες για την αρχή και το τέλος της λίστας, αυτόν που δείχνει στην αρχή συνήθως τον ονομάζουμε head, αλλά επειδή τον έχουμε συνηθίσει ως list σε αυτό το tutorial, θα τον αφήσω έτσι.

Παραθέτω λοιπόν τον κώδικα της γρήγορης συνάρτησης: list_append( &list, &tail, 52); στην οποία πλέον ούτε άλλους τοπικούς βοηθητικούς δείκτες χρειαζόμαστε, ούτε καν διατρέχουμε τη λίστα. Απλώς πάμε και βάζουμε το πεδίο next του tail να δείχνει στον νέο κόμβο new: (*tail)->next = new; και κατόπιν ενημερώνουμε τον tail ώστε να δείχνει στον νέο κόμβο, μιας και αυτός είναι πια ο τελευταίος στη λίστα: *tail = new;

Κώδικας: Επιλογή όλων

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στο τέλος της λίστας
* με χρήση δείκτη tail για δραματική επίσπευση της ταχύτητας εισαγωγής.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
Bool list_append( Node **list, Node **tail, int value )
{
Node *new = calloc(1, sizeof(struct node) );
if ( !new )
return FALSE;

new->value = value;
new->next = NULL;

if ( !*list ) {
*list = *tail = new;
return TRUE;
}

(*tail)->next = new;
*tail = new;

return TRUE;
}

int main( void )
{
Node *list=NULL, *tail=NULL;
...
list_append( &list, &tail, 1);
list_append( &list, &tail, 2);
list_append( &list, &tail, 0);
...
list_destroy( list );
return EXIT_SUCCESS;
}


Και πάλι παρέλειψα επίτηδες ελέγχους αποτυχίας μετά από κάθε εισαγωγή, στο παραπάνω παράδειγμα, για να είναι πιο ευανάγνωστος ο κώδικας. Η λίστα που παράγει ο παραπάνω κώδικας είναι η εξής:
Κώδικας: Επιλογή όλων
1 -> 2 -> 0 -> NULL
με τον δείκτη list να δείχνει στον κόμβο του 1 και τον δείκτη tail να δείχνει στον κόμβο του 0.

Σε ότι αφορά τον κώδικα της συνάρτησης αυτής κάθε αυτής, ξεχωριστή αναφορά χρειάζεται η ειδική περίπτωση κατά την οποία ο νέος κόμβος πάει να προστεθεί σε κενή λίστα. Σε αυτή την περίπτωση, βάζουμε τόσο τον δείκτη *list όσο και τον δείκτη *tail να δείχνουν στον νέο κόμβο και τερματίζουμε τη συνάρτηση.

Σημειώστε επίσης πως τώρα που έχουμε να διαχειριστούμε 2 σημαντικούς δείκτες μέσα στη συνάρτηση, το πέρασμά τους BY REFERENCE είναι η ΠΡΟΦΑΝΗΣ υλοποίηση, μιας και οι συναρτήσεις μπορούν να έχουν μονάχα μια τιμή επιστροφής κι εμείς χρειαζόμαστε αλλαγμένους και τους 2 δείκτες όταν η ροή επιστρέψει στην main() ;)

Ως μικρή επιβεβαίωση της κατανόησης του πως λειτουργεί η γρήγορη: list_append() αφήνω πάνω σας την προσθήκη σχολίων στον κώδικά της. :)

Πριν κλείσω το 4ο μέρος του tutorial, χρειάζεται μια ακόμα σημαντική διευκρίνηση...

Αν εκτός του στάνταρ δείκτη list (που δείχνει πάντα στην αρχή της λίστας) επιλέξετε να χρησιμοποιήσετε ΚΑΙ δείκτη tail που θα δείχνει ανά πάσα στιγμή στον τελευταίο κόμβο της λίστας, τότε θα πρέπει να ΤΡΟΠΟΠΟΙΗΣΕΤΕ ΚΑΙ τον κώδικα της συνάρτησης: list_prepend() περνώντας της ΚΑΙ τον δείκτη tail στα ορίσματά της.

Αρχικά είπα να σας το αφήσω σαν άσκηση, αλλά μετά σκέφτηκα πως ήδη σας έχω... πυρπολήσει με πράγματα που δυσκολεύουν ακόμα κι έμπειρους προγραμματιστές (άλλων γλωσσών :P).
Οπότε σας παραθέτω και τον τροποποιημένο κώδικα της list_prepend()... βάλτε σχόλια αν είναι εσείς.

Κώδικας: Επιλογή όλων

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στην αρχή της λίστας.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
Bool list_prepend( Node **list, Node **tail, int value )
{
Node *new = calloc(1, sizeof(struct node) );
if ( !new )
return FALSE;

new->value = value;
if ( !*list ) {
new->next = NULL;
*list = *tail = new;
}
else {
new->next = *list;
*list = new;
}

return TRUE;
}


Προφανώς δεν μπορούμε να μπλέκουμε εκδόσεις συναρτήσεων που προβλέπουν μονάχα τη χρήση του δείκτη list με εκδόσεις που προβλέπουν τη χρήση και του list και του tail. Αν δηλαδή στο πρόγραμμά μας θελήσουμε να έχουμε και συνάρτηση list_prepend() και συνάρτηση list_append() θα πρέπει πρώτα να αποφασίσουμε αν θα χρησιμοποιήσουμε ΚΑΙ δείκτη tail στον τελευταίο κόμβο της λίστας μας και να υλοποιήσουμε ανάλογα τον κώδικα και των δυο συναρτήσεων.

Τελειώνοντας αυτό το 4ο μέρος, παραθέτω ολοκληρωμένο τελικό κώδικα που χρησιμοποιεί ΚΑΙ δείκτη tail στη λίστα, συνεχίζοντας από εκεί που σταματάει ο τελικός κώδικας του 3ου μέρους. Χρησιμοποιώ ΚΑΙ δείκτη tail, ενώ τις εκδόσεις των συναρτήσεων που δεν προβλέπουν χρήση του tail τις έχω συμπεριλάβει, αλλά ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΕΣ μέσα σε σχετικά μπλοκ σχολίων.

Το πρόγραμμα πρώτα εισαγάγει τις τιμές 52 και 62 στο τέλος της λίστας ( list_append() ), μετά τις τιμές 72 και 82 στην αρχή της λίστας ( list_prepend() ). Κατόπιν τυπώνει το συνολικό πλήθος των στοιχείων και ξεχωριστά το 1ο και το τελευταίο στοιχείο της λίστας. Μετά τυπώνει όλα τα στοιχεία ένα προς ένα, καταστρέφει τη λίστα και τερματίζει.

Κώδικας: Επιλογή όλων

/*
* Singly Linked List Tutorial, Part 4
* (comments are in Greek)
*/

#include <stdio.h>
#include <stdlib.h>

typedef enum { FALSE=0, TRUE } Bool; // ο δικός μας πρόσθετος τύπος Boolean

typedef struct node { // δομή του κάθε κόμβου της λίστας
int value;
struct node *next;
} Node;

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στην αρχή της λίστας.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
/*** ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΟΣ ΚΩΔΙΚΑΣ (δεν προβλέπει *tail)

Bool list_prepend_notail( Node **list, int value )
{
Node *new = calloc(1, sizeof(Node) );
if ( !new )
return FALSE;

new->value = value;
new->next = (*list);
(*list) = new;

return TRUE;
}
***/

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στο τέλος της λίστας
* με τον κλάσικό, αργό τρόπο που διατρέχει όλη την λίστα.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
/*** ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΟΣ ΚΩΔΙΚΑΣ (δεν προβλέπει *tail)

Bool list_append_slow( Node **list, int value )
{
Node *head = NULL, *dummy = NULL; // βοηθητικοί δείκτες
Node *new = calloc(1, sizeof(struct node) ); // δημιουργία του νέου κόμβου
if ( !new ) // αποτυχία του calloc()
return FALSE; // πρόωρος τερματισμός

new->value = value; // αρχικοποίηση του νέου κόμβου
new->next = NULL;

if ( !*list ) { // εισαγωγή σε κενή λίστα
*list = new;
return TRUE;
}

// εισαγωγή σε μη κενή λίστα
head = dummy = *list; // τοποθέτηση βοηθητικών δείκτών στην αρχή της λίστας
while ( head ) { // ο head διατρέχει τη λίστα
dummy = head; // ο dummy "θυμάται" τον εκάστοτε προηγούμενο κόμβο
head = head->next;
}
dummy->next = new; // εισαγωγή του νέου κόμβου

return TRUE;
}
***/

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στην αρχή της λίστας.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
Bool list_prepend( Node **list, Node **tail, int value )
{
Node *new = calloc(1, sizeof(struct node) );
if ( !new )
return FALSE;

new->value = value;
if ( !*list ) {
new->next = NULL;
*list = *tail = new;
}
else {
new->next = *list;
*list = new;
}

return TRUE;
}

/* ------------------------------------------------------------------
* Δημιουργία και εισαγωγή νέου κόμβου με τιμή value, στο τέλος της λίστας
* με χρήση δείκτη tail για δραματική επίσπευση της ταχύτητας εισαγωγής.
* Επιστρέφει FALSE σε περίπτωση αποτυχίας της calloc(), αλλιώς TRUE
* ------------------------------------------------------------------
*/
Bool list_append( Node **list, Node **tail, int value )
{
Node *new = calloc(1, sizeof(struct node) );
if ( !new )
return FALSE;

new->value = value;
new->next = NULL;

if ( !*list ) {
*list = *tail = new;
return TRUE;
}

(*tail)->next = new;
*tail = new;

return TRUE;
}

/* ------------------------------------------------------------------
* Μέτρημα κι επιστροφή του πλήθους κόμβων της λίστας
* ------------------------------------------------------------------
*/
int list_len( Node *list )
{
register int i = 0;
for (i=0; list; i++) // ισοδυναμεί με: for (i=0; list != NULL; i++)
list = list->next;

/** ΕΝΑΛΛΑΚΤΙΚΟΣ ΚΩΔΙΚΑΣ
while ( list ) { // ισοδυναμεί με: while ( list != ΝULL ) {
list = list->next;
i++;
}
**/
return i;
}

/* ------------------------------------------------------------------
* Τύπωμα του πεδίου value όλων των κόμβων της λίστας
* ------------------------------------------------------------------
*/
void list_print( Node *list )
{
while ( list ) {
printf("%d ", list->value);
list = list->next;
}
putchar('\n'); // αλλαγή γραμμής

return;
}

/* ------------------------------------------------------------------
* Απελευθέρωση της μνήμης που έχουμε δεσμεύσει για κάθε κόμβο της λίστας
* ------------------------------------------------------------------
*/
void list_destroy( Node *list )
{
if ( !list ) // ισοδυναμεί με: if ( list == NULL )
return;

Node *dummy = NULL;
while ( list ) { // ισοδυναμεί με: while ( list != NULL ) {
dummy = list->next;
free( list );
list = dummy;
}

return;
}

// -------------------------------------------------------------------
int main ( void )
{
Node *list = NULL, *tail = NULL;

// δημιουργία και εισαγωγή 1ου στοιχείου στο ΤΕΛΟΣ
if ( list_append( &list, &tail, 52) == FALSE ) { // αποτυχία
puts("*** out of memory, aborting program");
exit( EXIT_FAILURE );
}

// δημιουργία και εισαγωγή 2ου στοιχείου στο ΤΕΛΟΣ
if ( list_append( &list, &tail, 62) == FALSE) { // αποτυχία
puts("*** out of memory, aborting program");
list_destroy( list) ; // καταστροφή προηγούμενων κόμβων
exit( EXIT_FAILURE );
}

// δημιουργία και εισαγωγή 3ου στοιχείου στην ΑΡΧΗ
if ( list_prepend( &list, &tail, 72) == FALSE) { // αποτυχία
puts("*** out of memory, aborting program");
list_destroy( list) ; // καταστροφή προηγούμενων κόμβων
exit( EXIT_FAILURE );
}

// δημιουργία και εισαγωγή 4ου στοιχείου στην ΑΡΧΗ
if ( list_prepend( &list, &tail, 82) == FALSE) { // αποτυχία
puts("*** out of memory, aborting program");
list_destroy( list) ; // καταστροφή προηγούμενων κόμβων
exit( EXIT_FAILURE );
}

// καταμέτρηση και τύπωμα του πλήθους των κόμβων στη λίστα
printf("The list consists of %d nodes\n", list_len( list) );

// τύπωμα των πεδίων value του 1ου και του τελευταίου κόμβου
printf("(head = %d, tail = %d)\n", list->value, tail->value);

// τύπωμα των στοιχείων της λίστας
list_print( list );

// αποδέσμευση όλων των κόμβων από τη μνήμη
list_destroy( list );

fflush(stdin); getchar();
exit( EXIT_SUCCESS );
}

Μπορείτε να δείτε τον κώδικα με syntax-highlighting, δείγμα εξόδου και σύνδεσμο κατεβάσματος εδώ: http://codepad.org/3WR5kyQR

Σε αυτό το σημείο δεν ξέρω αν είναι καλή ιδέα αν πρέπει να συνεχίσω με τα tutorials ή να σταματήσω μέχρι να μου δώσετε το ΟΚ για να πάμε παρακάτω.

Προσωπικά θέλω να αναλύσω και κώδικα που εισαγάγει τους νέους κόμβους απευθείας ταξινομημένους στη σωστή τους θέση, καθώς επίσης και μια λίγο πιο προχωρημένη υλοποίηση η οποία αντί να κρατάει χύμα στη main() τους δείκτες list και tail, να τους "συγκεντρώνει" σε μια ξεχωριστή δομή την οποία και θα περνάει κατόπιν στις διάφορες συναρτήσεις.

Όπως και την αντικατάσταση της συνάρτησης list_len() που ... σέρνεται, ειδικά όταν έχουμε τεράστιες λίστες (αφού για να μετρήσει το πλήθος των στοιχείων διατρέχει όλους τους κόμβους έναν προς έναν). Διατηρώντας ξεχωριστή μεταβλητή για το πλήθος των στοιχείων που θα την αυξάνουμε κατά 1 μέσα στις συναρτήσεις εισαγωγής κάθε φορά που προσθέτουμε έναν νέο κόμβο (κι αντίστοιχα μειώνοντάς την κατά 1 κάθε φορά που διαγράφουμε έναν κόμβο) ο χρόνος που θα χρειαζόμαστε για να βρούμε το πλήθος των στοιχείων της λίστας, από O(n) που είναι τώρα με την list_len() θα πέσει σε O(1)... που σημαίνει θα είναι απλά η εξέταση της τιμής μιας και μόνο μεταβλητής.

Μια τέτοια έξτρα δομή, θα μετατρέψει τους ορισμούς μας ως εξής...

Κώδικας: Επιλογή όλων

typedef struct node {
int data;
struct node *next;
} Node;

typedef struct {
Node *head, *tail;
int len;
} List;
και προφανώς χρειάζεται να τροποποιήσουμε και τον κώδικα των συναρτήσεων.

Π.χ. αντί για...

Κώδικας: Επιλογή όλων
Bool list_append( Node **list, Node **tail, int *len, int value);
(όπου len, η αυθεντική μεταβλητή της main() που θα κρατάει το πλήθος των στοιχείων)

θα έχουμε...

Κώδικας: Επιλογή όλων
Bool list_append( List *list, int data);
με τις ανάλογες φυσικά τροποποιήσεις μέσα στον κώδικα της συνάρτησης.

Το ερώτημα είναι, είμαστε έτοιμοι για αυτό ή χρειάζεται ένα διάλειμμα για να εμπεδωθούν όσα έχει καλύψει μέχρι στιγμής το tutorial; Ή να τα γράψω έτσι κι αλλιώς να υπάρχουν κι ο καθένας να τα ακολουθήσει με τους δικούς του ρυθμούς;

Για παράδειγμα, στο σημείο που έχουμε φτάσει μέχρι τώρα, δεν είναι ακόμα εύκολο να παρακολουθήσει κανείς tutorials από real-life εφαρμογή των λιστών, όπως για παράδειγμα αυτό εδώ που εξηγεί πως μπορεί να φτιάξει κανείς game objects με λίστες και να τα εμφανίσει γραφικώς και στην οθόνη: http://www.gamedev.net/page/resources/_ ... ects-r2041
Τελευταία επεξεργασία από migf1 και 22 Ιούλ 2011, 10:24, έχει επεξεργασθεί 3 φορά/ες συνολικά
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό Star_Light » 02 Ιούλ 2011, 00:58

migf1 έγραψε:Καλημέρα παιδιά, 1000 ευχαριστώ για τα καλά σας λόγια. Είναι χαρά μου να βοηθάω όπου μπορώ.

Φίλε Strarlight, ο κώδικάς σου έχει προβληματάκια:

1. στον ορισμό του struct customer πρέπει να βάλεις και τη λέξη customer στην 1η γραμμή, ώστε να ξέρει μέσα στο σώμα του σε τι τύπο αναφέρεται ο δείκτης next. Έτσι όπως το έχεις τώρα:

Κώδικας: Επιλογή όλων

typedef struct { // <--- λάθος, πρέπει: typedef struct customer {
int id;
struct customer *next;
} customer;

ουσιαστικά ο τύπος του *next είναι άγνωστος (άμα δεις ο gcc σου βγάζει warnings όταν πας να εκχωρήσεις στον δείκτη ->next ένα άλλον δείκτη τύπου customer). Επίσης, είναι καλή πρακτική τους πρόσθετους τύπους να τους γράφεις με κεφαλαίο το 1ο τους γράμμα, οπότε το παραπάνω γίνεται:

Κώδικας: Επιλογή όλων

typedef struct customer {
int id;
struct customer *next;
} Customer;


2. Όταν πας να τυπώσεις την λίστα σου, την διατρέχεις σωστά με έναν βοηθητικό δείκτη: helper, αλλά μέσα στο loop αντί να τυπώνεις μόνο το id του τρέχοντος κόμβου (helper->id), εσύ το βάζεις να τυπώσει τα id και των 3 κόμβων της λίστας, σε ένα printf. Και μάλιστα δεν τυπώνεις καν το id του εκάστοτε helper, αλλά κάνεις απευθείας πρόσβαση στους: head, one και two...

Κώδικας: Επιλογή όλων

while(helper!=NULL)
{
printf("%d %d %d",head->id , one->id , two->id); // <-- λάθος, πρέπει: printf("%d , helper->id);
helper=helper->next;
}

Οπότε θα πρέπει είτε να διατρέξεις τη λίστα με loop και να τυπώνεις το id του εκάστοτε τρέχοντος κόμβου μέσα στο loop...

Κώδικας: Επιλογή όλων

// τύπωμα της λίστας με loop
Customer *helper = head;
while ( helper != NULL )
{
printf("%d ", helper->id);
helper = helper->next;
}

... είτε να τυπώσεις απευθείας σε ένα printf τα id των head, one και two χωρίς loop (αν και αυτό δεν έχει νόημα, γιατί π.χ. αν έχουμε 100 στοιχεία στη λίστα δεν υπάρχει περίπτωση να τα τυπώσουμε με αυτό τον τρόπο)...
Κώδικας: Επιλογή όλων
printf("\n%d %d %d\n", head->id, one->id , two->id); //<--- μη χρήσιμος τρόπος (απλά για δοκιμή)


3. Όταν απελευθερώνεις τη μνήμη για τους κόμβους...
Κώδικας: Επιλογή όλων

free(head);
free(head->next); // <--- λάθος, το head είναι ήδη κατεστραμμένο
free(one->next);

κάνεις το σφάλμα να απελευθερώνεις πρώτα τον κόμβο στον οποίον δείχνει ο head και κατόπιν να τον χρησιμοποιείς ξανά (ενώ έχει ήδη απελευθερωθεί) για να απελευθερώσεις το head->next. Ο λόγος που ο compiler δεν σου βγάζει error είναι επειδή η free() δεν κάνει έλεγχο εγκυρότητας για το όρισμα που της περνάς (θεωρητικά αν είναι άκυρο ή NULL δεν κάνει τίποτα, αλλά η υλοποίησή της εξαρτάται από τον compiler). Όταν τρέχεις το παραπάνω εκτελέσιμο αρχείο δεν σου βγάζει segmentation-fault επειδή ο gcc έχει έναν ας τον πούμε εσωτερικό μηχανισμό προστασίας για κάποιες περιπτώσεις. Αν αυτό τον κώδικα τον κάνεις compile σε άλλον compiler το εκτελέσιμο αρχείο πιθανότατα θα βαρέσει seg-fault όταν θα πάει να εκτελέσει το: free( head->next ). Όπως και να χει πάντως είναι λανθασμένο!

Η σωστή σειρά για να αποδεσμεύσεις τους κόμβους είναι η ανάποδη από αυτή που έχεις βάλει εσύ...

Κώδικας: Επιλογή όλων

free(one->next);
free(head->next);
free(head);

ή ακόμα καλύτερα...

Κώδικας: Επιλογή όλων

free( head->next->next );
free( head->next );
free( head );


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

4. Μετά την απελευθέρωση των κόμβων έχεις μια συνθήκη if-else προκειμένου να ελέγξεις αν πέτυχε η απελευθέρωση μνήμης....

Κώδικας: Επιλογή όλων

if (head->id) // <-- λάθος
printf("H free dn ekane kala tin douleia tis \n");
else
printf("H mnimi eleutherwthike!");

Εδώ υπάρχουν 2 προβλήματα, το σοβαρότερο εκ των οποίων είναι πως και πάλι προσπαθείς να χρησιμοποιήσεις έναν κατεστραμμένο κόμβο (από το προηγούμενο free) και συγκεκριμένα προσπαθείς να χρησιμοποιήσεις το πεδίο: id του κατεστραμμένου κόμβου (head->id). Το επόμενο πρόβλημα είναι πως το πεδίο id είναι τύπου int, οπότε ακόμα και αν ο κόμβος head υπήρχε, η συνθήκη: if ( head->id ) συγκρίνει την int τιμή του id με το 0 για να αποφασίσει αν απελευθερώθηκε ή όχι η μνήμη;

Υποθέτω πως εννοούσες: if (head == NULL). Πάντως δεν είναι καλή ιδέα να συγκρίνεις τον δείκτη ενός κόμβου που έχεις ήδη καταστρέψει με το free(). Προφανώς ούτε να τον χρησιμοποιείς. Για να τον ξαναχρησιμοποιήσει ως κόμβο θα πρέπει να κάνεις εκ νέου malloc()/calloc().

Να πω και κάτι τελευταίο, που έχω την εντύπωση πως δεν έχεις ξεκαθαρίσει στο μυαλό σου. Μπορεί να κάνω και λάθος, αλλά θα το πω καλού-κακού μιας και το φόρουμ το διαβάζει πολύς κόσμος.

Έχει να κάνει με τον συμβολισμό -> για πρόσβαση στα πεδία μιας μεταβλητής που είναι τύπου struct. Ο κανονικός συμβολισμός για να αναφερθούμε σε ένα πεδίο μιας μεταβλητής τύπου struct είναι η τελεία (.).

Έστω...
Κώδικας: Επιλογή όλων

struct coordinates {
int x;
int y;
};

struct coordinates pos; // position

Για να βάλουμε τιμές στα πεδία x και y της μεταβλητής pos, γράφουμε...
Κώδικας: Επιλογή όλων

pos.x = 100;
pos.y = 200;

Αν όμως την μεταβλητή την ορίσουμε να είναι δείκτης, τότε αντί για τελεία χρησιμοποιούμε -> ...
Κώδικας: Επιλογή όλων

struct coordinates {
int x;
int y;
};

struct coordinates *pos; // position

pos = calloc(1, sizeof( struct coordinates) );
// εδώ κανονικά θέλει κι έλεγχο αν πέτυχε το calloc()... if (pos != NULL)

pos->x = 100;
pos->y = 200;

...
free( pos );

Άρα λοιπόν, στις λίστες (και οπουδήποτε) το σύμβολο -> δεν δείχνει στον επόμενο κόμβο, αλλά αναφέρεται σε ένα πεδίο του τρέχοντος κόμβου ;)

Το παραπάνω παράδειγμα είναι ιδανικό για να ξεκαθαρίσει κανείς κι ένα ακόμα σημείο: πως όταν δήλωσα τη μεταβλητή pos ως δείκτη ΔΕΝ επιχείρησα να περάσω απευθείας τιμές στα πεδία της (κάτι τέτοιο θα μου έδινε segmentation fault). Πρώτα έφτιαξα χώρο στη μνήμη, βάζοντας τον δείκτη pos να δείχνει σε εκείνο το χώρο (pos = calloc( ... ) )
και ΜΕΤΑ έβαλα τιμές στα πεδία του, με τον συμβολισμό των δεικτών ( -> ).

Το ίδιο συμβαίνει και με τις λίστες (και παντού). Ο δείκτης head ΔΕΝ είναι κόμβος ο ίδιος, είναι ένας δείκτης που δείχνει σε έναν κόμβο (στον 1ο της λίστας εν προκειμένω) που πρέπει πρώτα να έχει δημιουργηθεί στη μνήμη. Αυτό το κάνει πιο ξεκάθαρο η 2η από τις εικόνες που πόσταρα στο προηγούμενο post.

Είναι σημαντικό να είναι ξεκάθαρο αυτό το σημείο!


Εκτος και αν εχουμε ορισει εμεις στον κομβο να δειχνει στο επομενο στοιχειο της λιστας ετσι δεν ειναι?????
ΑΝτε τελειωσα τωρα αυτο το κομματι σου! Τα πηγαινω σιγα σιγα... μπραβο migf1 ευχαριστουμε και παλι για ολα.
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: ΚΕΦΑΛΑΙΟ 6 - ΔΕΙΚΤΕΣ

Δημοσίευσηαπό migf1 » 02 Ιούλ 2011, 10:41

Star_Light έγραψε:
Εκτος και αν εχουμε ορισει εμεις στον κομβο να δειχνει στο επομενο στοιχειο της λιστας ετσι δεν ειναι?????

Καλημέρα,
αναφέρεσαι στο κομμάτι για το ότι είναι άλλο πράγμα ο δείκτης κι άλλο ο κόμβος στον οποίον δείχνει; Δεν έχω καταλάβει ακριβώς την ερώτηση.

Γενικώς πάντως οι δείκτες δεν είναι κόμβοι, δείχνουν σε κόμβους (όπως δείχνουν και σε απλές μεταβλητές σε άλλες περιπτώσεις). Στην περίπτωσή μας, ο κάθε κόμβος περιέχει ως ένα από τα (δυο) πεδία του έναν ακόμα δείκτη (τον next) που τον βάζουμε να δείχνει στον επόμενο κόμβο. Δηλαδή ούτε ο next είναι κόμβος, είναι κι αυτός δείκτης που δείχνει σε κόμβο.

Π.χ. στο παρακάτω σχήμα...
Κώδικας: Επιλογή όλων

head
|
v
1 -> 2 -> NULL
ο head δείχνει στον κόμβο του 1, και ο head->next δείχνει στον κόμβο του 2.

Αν τώρα γράψουμε: head = head->next, το σχήμα αλλάζει σε...
Κώδικας: Επιλογή όλων

head
|
v
1 -> 2 -> NULL
ο head δείχνει στο κόμβο του 2 και ο head->next δείχνει στο NULL.

Αν ξαναγράψουμε head = head->next, ο head θα δείξει στο NULL (τέλος της λίστας)...
Κώδικας: Επιλογή όλων

head
|
v
1 -> 2 -> NULL

Αν σε αυτό το σημείο που είμαστε τώρα, γράψεις ή αναφερθείς στο: head->next θα φας segmentation fault στο runtime.

έγραψε:ΑΝτε τελειωσα τωρα αυτο το κομματι σου! Τα πηγαινω σιγα σιγα... μπραβο migf1 ευχαριστουμε και παλι για ολα.

Πολύ χαίρομαι :)
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

ΠροηγούμενηΕπόμενο

Επιστροφή στο Μαθήματα C