imitheos Δημοσ. 14 Οκτωβρίου 2012 Δημοσ. 14 Οκτωβρίου 2012 Γενικά μπόρεσα να λύσω σε μένα το μπέρδεμα με τους pointer οργανώνοντας τις εξής σκέψεις (τις παραθέτω να υπάρχουν): 2) Ο pointer είναι μια απλή μεταβλητή - ένας απλός τύπος. Ο pointer είναι ένας απλός integer (δεν είναι, αλλά και είναι). Απλά η γλώσσα σε αφήνει το περιεχόμενό του να το χρησιμοποιήσεις σα διεύθυνση μνήμης και να έχεις πρόσβαση στη μνήμη μέσω αυτού. Ο ίδιος δηλαδή δεν είναι τίποτα παραπάνω από έναν integer. Η "γλώσσα" σε αφήνει να τον χρησιμοποιήσεις σαν εισιτήριο στη μνήμη. Ο προορισμός είναι το περιεχόμενο του - τα data του και ο "χειρισμός" που μπορεί να γίνει σε αυτό τον προορισμό καθορίζεται από την "υπογραφή" του pointer (μπορεί δηλαδή ο προορισμός να είναι μια struct ένας απλό τύπος ή ένας άλλος pointer). 3) Έτσι κάθε pointer - μιας και είναι απλός τύπος - "μπαίνοντας" σε μια συνάρτηση ή "βγαίνοντας" από αυτή, αντιγράφεται. Το ότι τα δεδομένα που "δείχνει" είναι ίδια μέσα και έξω από τη συνάρτηση είναι γιατί τα data που αντιγράφονται, είναι διεύθυνση μνήμης. Αλλά αυτό είναι κάτι αυτόνομο και συμβαίνει γιατί ο μηχανισμός της γλώσσας σου επιτρέπει να παίξεις εύκολα με αυτά και να έχεις πρόσβαση όπου είναι να έχεις πρόσβαση. Πολύς κόσμος έχει διαβάσει/ακούσει ότι οι δείκτες είναι δύσκολοι και πάει με αυτό το σκεπτικό και μπερδεύεται ενώ οι δείκτες είναι όντως απλές μεταβλητές που έχουν μια τιμή όπως όλες οι μεταβλητές. Χωρίς βλάβη της γενικότητας, μπορούμε να πούμε πως η μνήμη είναι ενιαία και κάθε της διεύθυνση αντιπροσωπεύεται από ένα αριθμό όπως 1, 2, κτλ. Έτσι, όπως είπες, ο δείκτης περιέχει απλά μια αριθμητική τιμή. Όλη η δουλειά γίνεται με τον τελεστή * που σου επιτρέπει να χρησιμοποιήσεις την τιμή του δείκτη σαν να ήταν διεύθυνση και να προσπελάσεις την τιμή εκείνης της διεύθυνσης. > #include <stdio.h> int main(void) { int i = 5; printf("%d\n", * (int *)i); return 0; } Ο παραπάνω κώδικας είναι κακογραμμένος και δεν πρέπει να χρησιμοποιείται αλλά τον αναφέρω επίτηδες για να δούμε ότι οι δείκτες δεν διαφέρουν από μια απλή μεταβλητή. Ορίζουμε μια απλή μεταβλητή και της δίνουμε την τιμή 5. Έπειτα, μέσα στην printf, η πρόταση "(int *)" λέει "προσποιήσου πως ο i είναι δείκτης σε ακέραιο και χρησιμοποίησε τον έτσι" και με τον τελεστή * παίρνουμε την τιμή 5 και την χρησιμοποιούμε σαν να ήταν διεύθυνση. Έτσι ο παραπάνω κώδικας εμφανίζει στην οθόνη τον ακέραιο που ξεκινά από τη διεύθυνση 5. Επειδή όμως αυτή η περιοχή μνήμης δεν ανήκει στο πρόγραμμά μας, θα κρασάρει. Παλαιότερα σε DOS που δεν υπήρχαν τέτοιες προστασίες, πολλά προγράμματα χρησιμοποιούσαν τη παραπάνω συμπεριφορά για να γράψουν σε μια καθορισμένη θέση. Όταν άρχισαν να χρησιμοποιούνται 64bit πλατφόρμες, βγήκε ακόμη ένα κακό στοιχείο αυτής της συμπεριφοράς που είναι πως σε 64bit το μέγεθος του int και του δείκτη δεν είναι ίδιο για αυτό και πολλά κακογραμμένα προγράμματα που έπαιζαν τζάμι σε 32bit, κράσαραν σε 64bit. Για αυτό ας ξεχάσουμε τον κώδικα και ας θυμόμαστε απλά πως ένας δείκτης είναι μια απλή μεταβλητή και η μαγεία είναι στον τελεστή *. Ο τελεστής * μας επιτρέπει να χρησιμοποιήσουμε την τιμή οποιασδήποτε μεταβλητής σαν να ήταν διεύθυνση και να προσπελάσουμε τα περιεχόμενα της. Όσον αφορά το "by value", έχουμε πει ότι οι παράμετροι των συναρτήσεων στην C είναι διαφορετικά αντικείμενα που έχουν το ίδιο περιεχόμενο με τις αρχικές. Ας δούμε κάποια παραδείγματα. > #include <stdio.h> void foo(int x); int main(void) { int i = 5; printf("i exei dieu8insi %p\n", (void *)&i); printf("i prin tin ektelesi tis foo einai %d\n", i); foo(i); printf("i meta tin ektelesi tis foo einai %d\n", i); return 0; } void foo(int x) { x = 1; printf("x exei dieu8insi %p\n", (void *)&x); printf("x mesa stin foo einai %d\n", x); } Έξοδος: i exei dieu8insi 0x5c i prin tin ektelesi tis foo einai 5 x exei dieu8insi 0x40 x mesa stin foo einai 1 i meta tin ektelesi tis foo einai 5 Ο παραπάνω κώδικας καλεί μια συνάρτηση που αλλάζει τιμή στην μεταβλητή και δείχνει ότι η τιμή της μεταβλητής i δεν αλλάζει. Αυτό το παράδειγμα το καταλαβαίνει όλος ο κόσμος και χωρίς να εμφανίσουμε την διεύθυνση των δύο μεταβλητών επειδή έχουν διαφορετικό όνομα. Το πρόβλημα για πολύ κόσμο εμφανίζεται όταν έχουμε ίδια ονόματα. > #include <stdio.h> void foo(int i); int main(void) { int i = 5; printf("i exei dieu8insi %p\n", (void *)&i); printf("i prin tin ektelesi tis foo einai %d\n", i); foo(i); printf("i meta tin ektelesi tis foo einai %d\n", i); return 0; } void foo(int i) { i = 1; printf("i tis foo exei dieu8insi %p\n", (void *)&i); printf("i mesa stin foo einai %d\n", i); } Έξοδος: i exei dieu8insi 0x3c i prin tin ektelesi tis foo einai 5 i tis foo exei dieu8insi 0x20 i mesa stin foo einai 1 i meta tin ektelesi tis foo einai 5 Εδώ έχουμε τον ίδιο ακριβώς κώδικα απλά η τοπική μεταβλητή της συνάρτησης foo έχει ίδιο όνομα με την μεταβλητή i που της περνάμε καλώντας την από την main. Εδώ γίνεται συνήθως η σύγχυση. Όπως βλέπουμε, η αρχική μεταβλητή i έχει διεύθυνση 60 (0x3C) ενώ η i που βρίσκεται στην foo έχει διεύθυνση 32 (0x20) δηλαδή είναι δύο εντελώς διαφορετικά αντικείμενα. Όταν εμείς καλέσαμε την συνάρτηση foo, δεσμεύτηκε μνήμη για ένα νέο αντικείμενο (που συμπτωματικά έχει ίδιο όνομα) και αντιγράφηκε σε αυτό η τιμή 5. Όταν αλλάζουμε την τιμή μέσα στην foo, αλλάζουμε την τιμή αυτού του 2ου αντικειμένου με το αρχικό να μένει απείραχτο για αυτό και όταν επιστρέφουμε από την συνάρτηση βλέπουμε την μεταβλητή να ξαναέχει την τιμή 5. Μπορεί δηλαδή και στις δύο περιπτώσεις να έχουμε εκφράσεις "i = τιμή" αλλά εκτελούν διαφορετικά πράγματα. Στην main το "i = 5" σημαίνει πάνε στη διεύθυνση 60 και γράψε την τιμή 5. Μέσα στην foo, το "i = 1" σημαίνει πάνε στην διεύθυνση 32 και γράψε την τιμή 1. Μπακάλικα μπορούμε να σκεφτούμε δύο άτομα με όνομα Γιώργος Παπαδόπουλος που έχουν και οι δύο καστανά μαλλιά και ο ένας ζει στην Αθήνα και ο άλλος στην Θεσσαλονίκη. Ενώ έχουν ίδιο όνομα είναι δύο διαφορετικά άτομα. Αν ο Θεσσαλονικιός αποφασίσει να βάψει τα μαλλιά του ξανθά, θα αλλάξει μόνο εκείνος. Ο Αθηναίος θα παραμείνει καστανός. > #include <stdio.h> void foo(int *p); int main(void) { int i = 5; int *p = &i; printf("i prin exei dieu8insi %p, timi %d\n", (void *)&i, i); printf("p exei dieu8insi %p, timi %p\n", (void *)&p, (void *)p); foo(p); printf("i meta exei dieu8insi %p, timi %d\n", (void *)&i, i); return 0; } void foo(int *p) { *p = 1; printf("p tis foo exei dieu8insi %p, timi %p\n", (void *)&p, (void *)p); } Έξοδος: i prin exei dieu8insi 0xec, timi 5 p exei dieu8insi 0xe8, timi 0xec p tis foo exei dieu8insi 0xd0, timi 0xec i meta exei dieu8insi 0xec, timi 1 Ας δούμε τώρα αυτό που λέγαμε για τους δείκτες. Όταν μέσα στη main ορίσαμε το i, δεσμεύτηκε η διεύθυνση 236 (0xEC) και γράφτηκε εκεί η τιμή 5. Έπειτα ορίσαμε το δείκτη p για τον οποίο δεσμεύτηκε η διεύθυνση 232 (0xE8). Ως τιμή στον p δώσαμε την διεύθυνση της μεταβλητής i δηλαδή πήγαμε στη διεύθυνση 232 και γράψαμε την τιμή 236. Ερχόμαστε τώρα στην συνάρτηση foo. Ο δείκτης p εκεί έχει διεύθυνση 208 (0xD0) δηλαδή είναι διαφορετικό αντικείμενο από τον p της main, όπως ακριβώς γίνεται και σε μια απλή μεταβλητή. Όταν λοιπόν μπήκαμε μέσα στη foo, έγινε η ίδια λειτουργία που γίνεται όταν έχουμε απλές μεταβλητές δηλαδή αντιγράφηκε η τιμή του αρχικού p στον νέο p, έτσι η διεύθυνση 208 περιέχει και αυτή ως τιμή τον αριθμό 236. Ερχόμαστε τώρα και χρησιμοποιούμε τον μαγικό τελεστή * και λέμε *p = 1. Τι σημαίνει αυτή η έκφραση ? Το p για το λειτουργικό είναι ισοδύναμο της διεύθυνσης 208 οπότε αρχικά θα διαβάσει τη διεύθυνση 208 και θα βρει την τιμή της που είναι ο αριθμός 236. Ο τελεστής * τώρα λέει στο λειτουργικό "αυτή τη τιμή που διάβασες προσποιήσου ότι δεν είναι ένας οποιοσδήποτε αριθμός αλλά μια διεύθυνση μνήμης οπότε θα πας εκεί και θα γράψεις την τιμή 1". Το σύστημα λοιπόν πάει στη διεύθυνση 236 και γράφει την τιμή 1. Το 236 όμως αντικατοπτρίζει τη μεταβλητή i και έτσι με έμμεσο τρόπο αλλάξαμε την τιμή της μεταβλητής i από 5 σε 1. Ελπίζω να μην τα έγραψα ακαταλαβίστικα και να γίνει κατανοητή η λειτουργία των δεικτών.
migf1 Δημοσ. 14 Οκτωβρίου 2012 Δημοσ. 14 Οκτωβρίου 2012 Η αλήθεια είναι πως χωρίς τριβή δεν μαθαίνονται οι δείκτες, ακόμα κι όταν έχει εμπεδωθεί πλήρως η έννοια τους σε θεωρητικό επίπεδο (όπως πολύ όμορφα εξηγήσατε ο καθένας με τον δικό του τρόπο). Κι αυτό διότι ως μεμονωμένη γνώση φαντάζει άχρηστη. IMHO, θα πρέπει να συνδυαστεί (συμπληρωθεί) κατ' ελάχιστο και με εμπέδωση της έννοιας by-reference (κλήση αναφοράς) και κατόπιν με παραδείγματα κλήσης by-refernece ενός δείκτη (π.χ. ενός string-literal) αντί μιας απλής μεταβήτής. Για παράδειγμα, η εναλλαγή δυο string-literals (δηλαδή 2 δεικτών που ο καθένας δείχνει στον αρχικό χαρακτήρα μιας ακολουθίας από χαρακτήρες) μπορεί να γίνει efficiently απλώς εναλλάσοντας τις διευθύνσεις των 2 αυτών δεικτών. Με άλλα λόγια, περνάμε τους 2 αυτούς δείκτες by-reference (διπλοί δείκτες) σε μια συνάρτηση που εναλλάσει τις διευθύνσεις τους (τις διευθύνσεις των δεικτών δηλαδή... διπλοί δείκτες). > #include <stdio.h> /* ------------------------------------------------------------------------------- * int s_swap_addr( char **s, char **t ) * Εναλλάσσει τα περιεχόμενα των string literals s και t (επιστρέφει 1 σε επιτυχία, αλλιώς 0). * ------------------------------------------------------------------------------- */ int s_swap_addr( char **s, char **t ) { char *dummy = NULL; /* sanity checks */ if ( !s || !t || !*s || !*t) { return 0; } /* do the swapping */ dummy = *s; *s = *t; *t = dummy; return 1; } /* --------------------------------------------- */ int main( void ) { char *s1 = "hello"; char *s2 = "cruel world"; puts( s1 ); puts( s2 ); s_swap_addr( &s1, &s2 ); puts( s1 ); puts( s2 ); system( "pause" ); exit(0); } Πιστεύω πως όποιος εμπεδώσει με δικά του παραδείγματα by-reference κλήσεις δεικτών (και όχι απλώς απλών μεταβλητών) κάνει ένα καθοριστκό βήμα για την κατανόηση των δεικτών. Προφανώς υπάρχουν πολλά ακόμα, αλλά αυτή είναι μια καλή βάση πιστεύω.
Star_Light Δημοσ. 14 Οκτωβρίου 2012 Δημοσ. 14 Οκτωβρίου 2012 Ε ναι αμα δεν λύνεις και ασκησεις δεν πας πουθενα. Πχ οσο καλους δεικτες και να χεις διαβάσει μπορει να πέσεις σε κάνα τέτοιο -> *p++; θεωρεις σωστα οτι το *p ειναι μια έμμεση αναφορα για την μεταβλητή οποτε μπορεις να δώσειςμια αύξηση οπως θα έδινες και στην κανονικη μεταβλητη... πχ int x ..... x++ . Οποτε πεφτεις σε λουμπιτσα επειδη δεν ειναι το ιδιο γιατι ο ++ εχει υψηλοτερη προτεραιότητα απο τον (*) οποτε αν θες να αυξήσεις απλα την μεταβλητή στην οποία δειχνει ο δεικτης δινεις ένα *(p++) ή ++ *p ; κοιταζα μια άσκηση πριν και το βρήκα.
migf1 Δημοσ. 15 Οκτωβρίου 2012 Δημοσ. 15 Οκτωβρίου 2012 Αν έχει κανείς όρεξη και χρόνο να την τεστάρει για bugs, ας ρίξει μια ματιά ... > /*********************************************************//** * @brief Glue into a single token any quoted token inside s. ************************************************************* */ char *s_glue_quoted_tokens( char *s, char chQuote, const char *innerDelims, char chGlue ) { int i = 0; int inQuoted = 0; /* reset to false */ if ( !s || !innerDelims ) return NULL; if ( chQuote == chGlue || !*s || !*innerDelims || strchr(innerDelims, chQuote) || strchr(innerDelims, chGlue) ) return s; for (i=0; s[i]; i++) { if ( chQuote == s[i] ) inQuoted = !inQuoted; if ( inQuoted && strchr(innerDelims, s[i]) ) s[i] = chGlue; } return s; } Θεωρητικά ενώνει σε μια λέξη οποια tokens βρίσκονται quoted μέσα σε ένα c-string. Π.χ. αν έχουμε ας πούμε... > char s[] = "here is 'a quoted token' and here is 'another one'"; που αποτελείται από 10 λεξεις (χωρισμένες με κενό δηλαδή) τότε καλώντας... > s_glue_quoted_tokens( s, '\'', " \t", '_' ) πρέπει να μετατρέψει το s σε "here is 'a_quoted_token' and here is 'another_one'" Επιστρέφει NULL σε περίπτωση σφάλματος, ή ανέπαφο το αρχικό s στις εξής περιπτώσεις: α) αν το s ή το innerDelims είναι κενά β) αν ο χαρακτήρας του quote (chQuote) δοθεί ίδιος με τον χαρακτήρα-συγκόλλησης (chGlue) γ) αν οποιοσδήποτε από τους χαρακτήρες του β) βρίσκεται ήδη μέσα στο innerDelims. innerDelims είναι οι χαρακτήρες που ξεχωρίζουν σε λέξεις οτιδήποτε βρίσκεται μέσα σε quotes στο αρχικό s.
imitheos Δημοσ. 15 Οκτωβρίου 2012 Δημοσ. 15 Οκτωβρίου 2012 > void foo(float a, float ; int main(void) { int a = 1, b = 3; foo(a, ; return 0; } Διαβάζοντας ένα βιβλίο, βλέπω στην παράγραφο που μιλάει για casting τον παραπάνω κώδικα και αναφέρει πως αν καλέσουμε την συνάρτηση έτσι, ενδέχεται να έχουμε λάθος αποτελέσματα ανάλογα με τον compiler. Αυτό γίνεται λέει επειδή η foo περιμένει floats και εμείς δίνουμε ints και συνεχίζει καλώντας την συνάρτηση με casts "foo ((float) a, (float) B );". Μου φάνηκε πολύ παράξενο και είπα να δω τι λέει το πρότυπο. 6. If the function is defined with a type that includes a prototype, and the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. 7. If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. 10. On entry to the function, the size expressions of each variably modified parameter are evaluated and the value of each argument expression is converted to the type of the corresponding parameter as if by assignment. Δεν έκατσα να ασχοληθώ και πολύ γιατί βιάζομαι αλλά με μια γρήγορη ματιά, ερμηνεύω τα δύο παραπάνω τμήματα πως γίνεται πάντα αυτόματη μετατροπή στον σωστό τύπο εκτός και αν μιλάμε για μη συμβατούς τύπους (πχ int με char *). Υπό άλλες συνθήκες θα έλεγα ότι γράφει χαζομάρες αλλά το μυαλό μου είναι λίγο κουρκούτι σήμερα οπότε είπα να δώσω benefit of doubt και να ρωτήσω εδώ. Ισχύει αυτό που λέει για implementation-defined συμπεριφορά ?
migf1 Δημοσ. 15 Οκτωβρίου 2012 Δημοσ. 15 Οκτωβρίου 2012 Θα σε γελάσω, έχω κι εγώ την εντύπωση πως πάντα γίνονται implicitly converted τα function arguments. Δεν κόβω και το κεφάλι μου όμως πως ισχύει για όλες τις πλατφόρμες, κι επίσης δεν θυμάμαι καθόλου τι έκανε η K&R C σε αυτή την περίπτωση.
Star_Light Δημοσ. 15 Οκτωβρίου 2012 Δημοσ. 15 Οκτωβρίου 2012 Τσεκαρε και το βιβλιο του Κινγκ Σελ. 194 - 195. Το βιβλιο που διαβασες κάνει ενα explicit cast για να ξεμπερδευει απο τον πονοκεφαλο Τα implicit conversions ειναι λιγο παίδεμα.. μια φορα εκατσα και τα διαβασα...ελπιζω να ηταν και η τελευταια.
imitheos Δημοσ. 16 Οκτωβρίου 2012 Δημοσ. 16 Οκτωβρίου 2012 Τσεκαρε και το βιβλιο του Κινγκ Σελ. 194 - 195. Το βιβλιο που διαβασες κάνει ενα explicit cast για να ξεμπερδευει απο τον πονοκεφαλο Τα implicit conversions ειναι λιγο παίδεμα.. μια φορα εκατσα και τα διαβασα...ελπιζω να ηταν και η τελευταια. Όντως ο King εξηγεί με απλά λόγια αυτά που λέει η παράγραφος 6.5.2.2 στα προηγούμενα τμήματα από αυτά που παρέθεσα. Όπως λέει και ο King όμως, όταν υπάρχει prototype όπως στον κώδικα που έδωσα δεν υπάρχει κανένα πρόβλημα. Τα default argument promotions που αναφέρει η υποπαράγραφος 6 και ο King, μιλάνε για μη-ύπαρξη prototype. Στη περίπτωση που η συνάρτηση υφίσταται μετά την main και δεν έχω βάλει prototypes, 3 compilers όλοι βαράνε error "conflicting δηλώσεις συνάρτησης" σε c89 κατάσταση ενώ σε c99 εμφανίζουν και το warning ότι η c99 έχει σταματήσει την implicit δήλωση int, οπότε ακόμη και σε αυτή τη περίπτωση δεν δημιουργείται πρόβλημα. Παρόλα αυτά, επειδή δεν είδα το πρότυπο να λέει "βάρα error" και επειδή δεν εμπιστεύομαι το standard enforcing του gcc θα δοκιμάσω πρώτα κανένα c89-only compiler. Ο gcc πολλά πράγματα που πρέπει να κάνει enforce σε c89 δεν τα κάνει ούτε με -pedantic.
migf1 Δημοσ. 16 Οκτωβρίου 2012 Δημοσ. 16 Οκτωβρίου 2012 Μια που τις γράφω που τις γράφω generic τις ρουτίνες αυτές, δινω άλλη μια... > #include <stdarg.h> /*********************************************************//** * ************************************************************* */ char *s_nappendf( char *s, size_t maxlen, const char *format, ... ) { va_list ap; // int nchars = 0; size_t slen = 0, rlen = 0; // rlen = remaining length in s if ( !s || !format) return NULL; if ( !*format ) return s; slen = strlen(s); rlen = maxlen > slen ? maxlen - slen : maxlen; va_start(ap, format); /*nchars =*/ vsnprintf( &s[slen], rlen, format, ap ); va_end(ap); return s; } Αυτή μπορεί να αποδειχθεί χρήσιμη π.χ. σε γραφικά περιβάλλοντα, όταν θέλουμε να βγάλουμε πολλές γραμμές σε MessageBox. Π.χ... (παρμένο από έναν parser που φτιαχνω αυτές τις μέρες) ... > /*********************************************************//** * ************************************************************* */ bool ui_print_import_stats( ImportStats *stats ) { extern char g_outtext[MAXLEN_OUTTEXT]; if ( !stats ) return false; memset( g_outtext, 0, MAXLEN_OUTTEXT ); s_nappendf( g_outtext, MAXLEN_OUTTEXT, "Rounds imported : %lu (including %lu duplicated)\n", stats->nParsed - stats->nNoHole - stats->nNoFlop, stats->nDups ); s_nappendf( g_outtext, MAXLEN_OUTTEXT, "\tParsed : %lu\n", stats->nParsed ); s_nappendf( g_outtext, MAXLEN_OUTTEXT, "\tRejected: %lu\n", stats->nNoHole + stats->nNoFlop ); s_nappendf( g_outtext, MAXLEN_OUTTEXT, "\t\tas \"no Hole-Cards\": %lu\n", stats->nNoHole ); s_nappendf( g_outtext, MAXLEN_OUTTEXT, "\t\tas \"no Flop-Cards\": %lu\n", stats->nNoFlop ); ui_puts_boxed( g_outtext ); return true; } Λειτουργεί περίπου όπως η snprintf() με την διαφορά πως σε κάθε νέα κλήση προσθέτει τα νέα πράγματα στο τέλος του string (αντί να το κάνει overwrite, όπως κάνει η snprintf() ). To nchars το έχω commented-out γιατί θα μπορούσε να χρησιμοποιηθεί για να δούμε αν πέτυχε ή όχι η εσωτερική κλήση της vsnprintf()... ίσως το προσθέσω άλλη στιγμή.
Star_Light Δημοσ. 17 Οκτωβρίου 2012 Δημοσ. 17 Οκτωβρίου 2012 (επεξεργασμένο) Λοιπόν έχω 2 κώδικες που θέλω να συζητήσουμε θα παραθέσω αρχικα τον πρώτο.... τις προάλλες έπεσε στο μάτι μου ένας κώδικας που είχε δοθει παλιότερα απο τον migf1 και έλυνε την άσκηση 14 στην Σελ. 180 οπου εκτυπωνε ουσιαστικα μια πρόταση που θα δώσει ο χρήστης με αντιστροφη σειρά (οχι των γραμματων φυσικα αλλα των λέξεων) . Επειδή συμφωνα με τον τοτε κώδικα το προγραμμα λάμβανε υποψιν του και σημεια στίξης άν κάποιος έδινε είσοδο της μορφής > man hello^^^ θα έπαιρνε έξοδο της μορφής : > hello^^^ man Tα σημεία στίξης με χαλάγανε λιγο... οποτε θέλησα να το fixarw. Ο νέος κώδικας ειναι ο εξής : http://ideone.com/nrJ2L Oι διορθώσεις ειναι στις γραμμές 26-33 . Θέλω να μου πείτε την γνώμη σας κατα ποσο θεωρείται καλή ή κακη η σκέψη. Επειδή ο πίνακας ειναι αρχικοποιημένος στο 0 σε ολα του τα στοιχεια αν δεν μειώσω το i κάθε φορά που πέφτω πανω σε σημειο στίξης τοτε υπάρχει θέμα επειδη αυτες οι θέσεις μένουν μηδενικές. Το i ουτως η άλλως αυξάνεται μετα λογω του βρόχου ακριβως όπως το επιθυμώ. Και πανω σε αυτο θέλω γνώμες... Ο κώδικας δουλεύει οπως τον θέλω. > Input : he**llo m&an Output : man hello p.s Παρεπιπτόντως συμφωνα με τον King η isspace(input) που έχει πρώτυπο απο το cctype.h κάνει implicit cast τον char input σε int. Το πρώτο πράγμα που κοιταξε ο compiler ήταν οι οδηγιες του προεπεξεργαστη και εκει έχω κανει include την καταλληλη βιβλιοθηκη αρα βρήκε ένα πρώτυπο πριν την κλήση της οποτε ισχυεί το 1 στην Σελ. 194 . p.s2 @imithee μηπως ψάχνεις κατι τετοιο ? http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_067.HTM @6.10.3 Επεξ/σία 17 Οκτωβρίου 2012 από Star_Light
migf1 Δημοσ. 18 Οκτωβρίου 2012 Δημοσ. 18 Οκτωβρίου 2012 @StarLight: Δεν έχω καταλάβει για ποιο πράγμα ρωτάς αν το θεωρούμε καλή ή κακή σκέψη. Επίσης, πως ήταν και τι (δεν) έκανε ο αρχικός κώδικας; Δεν μας τον έχεις παραθέσει, οπότε δεν ξέρουμε τι ακριβώς άλλαξες και γιατί.
ipduh Δημοσ. 18 Οκτωβρίου 2012 Δημοσ. 18 Οκτωβρίου 2012 [offtopic] Αυτό τι νόημα έχει ? [/offtopic] o τύπος char είναι συνήθως 1B αλλα μπορεί σε καμιά παράξενη αρχιτεκτονικη να μην είναι , για αυτό το κάνει ουπς απαντήθηκε ... πως σβήνω? ... τεσπά
Star_Light Δημοσ. 18 Οκτωβρίου 2012 Δημοσ. 18 Οκτωβρίου 2012 @StarLight: Δεν έχω καταλάβει για ποιο πράγμα ρωτάς αν το θεωρούμε καλή ή κακή σκέψη. Επίσης, πως ήταν και τι (δεν) έκανε ο αρχικός κώδικας; Δεν μας τον έχεις παραθέσει, οπότε δεν ξέρουμε τι ακριβώς άλλαξες και γιατί. Τι ακριβως θες να σου πω? να δωσω σχολια με τον κωδικα επειδη δεν τον θυμάσαι? Ο κώδικας δεχοταν μια προταση και έκανε αντιστροφη των λεξεων της. Αλλα ετσι οπως ηταν φτιαγμενος δεχοταν και χαρακτηρες οι οποιοι ήταν σημεια στίξης πχ ο ^ και τους εκτυπωνε και αυτους. Πχ αν έδινες hello man^^^ θα σου έδινε man^^^ hello τα ^^^ δεν τα ηθελα και ειπα να κανω κάποιες διορθώσεις. ΟΙ διορθώσεις αυτες βρισκονται στις γραμμές 27 - 33 . Κατα ποσο θεωρείται εντάξει να μειώνεις τον μετρητη μέσα στο loop? Οπως κανω εγω και μετα να αυξάνεται παλι.
vinso Δημοσ. 18 Οκτωβρίου 2012 Δημοσ. 18 Οκτωβρίου 2012 1.Δε Ξέρω κατα πόσο είναι σωστό αυτό που έχεις γράψει μέσα στην for() αλλά με το να μειώνεις το i (που δε ξέρω και αυτο αν στέκει) μπορεί να χάσεις θέσεις στο πίνακα αν βρείς πολλά σημεία στίξης π.χ. make'''''''''''^^^^&&&&*&& a copy. τοτε το i θα μειωνεται συνεχώς και θα χάσεις τα αρχικά που έχεις αποθηκεύσει.(Νομίζω) 2. && c != '.' && c != '?'; κάνεις αυτο στη for ενώ έχεις μέσα στο if παρακάτω την ispunct. Αυτό που σου προτείνω είναι πολύ απλώ. Άν θές να διαβάζεις απο την αρχή με ispunct: i=0; while((c=getchar()) !=EOF) { if(ispunct© !=0) { input=c; i++; } } 1
Star_Light Δημοσ. 18 Οκτωβρίου 2012 Δημοσ. 18 Οκτωβρίου 2012 (επεξεργασμένο) Όχι . Επειδη τεστάρισα το πρόγραμμα με την εισοδο που δινεις και δεν κρασάρει. > Give a sentence: make'''''''''''^^^^&&&&*&& a copy Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ' ignored Character ^ ignored Character ^ ignored Character ^ ignored Character ^ ignored Character & ignored Character & ignored Character & ignored Character & ignored Character * ignored Character & ignored Character & ignored The reversal of the sentence: copy a make Επισης αυτο που προτεινεις ως λύση δεν το καταλαβαινω. Η ispunct() τσεκάρει αν ένας χαρακτηρας ειναι σημειο στιξης αλλιως επιστρέφει 0. Δηλαδη οσο ενας χαρακτηρας ειναι σημειο στιξης (ispunct(input( c ))!=0 ) να τον αποθηκευω στον πινακα? Αφου θελω να τους θέσω εκτος.... Παω να το ξαναδώ και επανερχομαι . Ισως εννοεις το αντιθετο και το εβαλες λογω βιασυνης... δεν μας ενοχλει τοσο αυτο οκ. EDIT : Οκ. > while (i < MAXLEN-1 && '\n' != (c = getchar()) && c != '.' && c != '?') { if( ( !ispunct(c) )) { input[i] = c; ++i; } else printf("\n punctuation character %c is ignored " , c); input[i] = '\0'; } O προηγούμενος κώδικας μπορει να μην κρασάρει αλλα ειναι στα ορια νομιζω αυτου που ονομάζουμε "spaghetti code" και χωρις να χρειαζεται πολυπλοκος... οποτε η παραπανω βελτιωση ειναι σιγουρα πιο κομψη και απλουστερη στην προσεγγιση. > void phrase_fill( char input[]) { int c ; int i=0 ; bool count = false ; /* Διάβασμα της φράσης και αποθήκευση στον input */ while (i < MAXLEN-1 && '\n' != (c = getchar()) && c != '.' && c != '?') { if( ( !ispunct(c) )) { input[i] = c; ++i; count = true; } else printf("\n punctuation character %c is ignored " , c); input[i] = '\0'; } if( !count ) exit(EXIT_FAILURE); return ; } Επεξ/σία 18 Οκτωβρίου 2012 από Star_Light
Προτεινόμενες αναρτήσεις