Προς το περιεχόμενο

Διατάξεις αλφαριθμητικών στη C


capoelo

Προτεινόμενες αναρτήσεις

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

 

 

"bad-habits" ειναι να μην διαβαζουν μια χ τεκμηριωση της χ συναρτησης. Οπως και το να μην διδασκουν μετα το hello world το hello debuger. Ειδικα το τελευταιο Ε-Λ-Ε-Ο-Σ, το 99% των αποριων εδω μεσα θα ειχαν αποτραπει μοναχα με ενα step by step mad.png αλλα οχι, εκει μανια να τους λενε να κανουν compile με σκετο compiler για να τρωνε τα errors απο το λειτουργικο

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 63
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Συχνή συμμετοχή στο θέμα

"bad-habits" ειναι να μην διαβαζουν μια χ τεκμηριωση της χ συναρτησης. Οπως και το να μην διδασκουν μετα το hello world το hello debuger. Ειδικα το τελευταιο Ε-Λ-Ε-Ο-Σ, το 99% των αποριων εδω μεσα θα ειχαν αποτραπει μοναχα με ενα step by step mad.png αλλα οχι, εκει μανια να τους λενε να κανουν compile με σκετο compiler για να τρωνε τα errors απο το λειτουργικο

 

Τι εννοείς με το "step by step";

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

το buffer δεν μπορεί να καθαριστεί με μια fflush(stdin) ;

Όπως και με μία printf. ;)

 

Απλώς είναι λίγο κάπως να καλείς fflush ή printf μετά από κάθε scanf.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Βασικά το fflush(stdin) λειτουργεί μονάχα στα Windows. Το στάνταρ επιβάλει να εφαρμόζεται μονάχα σε output streams (το stdin είναι input stream) αλλιώς ορίζει τη συμπεριφορά της ως μη αξιόπιστη (undefined).

 

ΥΓ. Η printf() καθαρίζει το stdout (και όχι το stdin) αλλά και πάλι δεν είναι υποχρεωμένη να το κάνει σύμφωνα με το στάνταρ, και δεν το κάνει σε όλες τις πλατφόρμες. Ο επίσημος & συμβατός παντού τρόπος καθαρισμού ενός output stream είναι με fflush( output_stream );

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@gon1332

 

Βασικά,όσο είμαι σε θέση να το τσεκάρω,χρησιμοποίησα την μορφή της scanf που μου πρότεινες και παρατήρησα ότι εάν συναντήσει κενό στο string,δεν εμφανίζει τίποτα απο'κει και πέρα(π.χ. αν έχω γράψει σε ένα πρόγραμμα scanf("%9s",x) και του δώσω ως είσοδο το "inso mnia foroum" θα εμφανίσει μόνο το inso).

 

Υ.Γ.Το κενό θεωρείται χαρακτήρας έτσι;Αν όχι,έγραψα μια τεράστια αρλούμπα.

 

 

Έστω

 

> printf(" Give a character: ");  
scanf("%c" , &c1);  

 

Άν ο χρήστης δώσει ειτε κενό ειτε χαρακτήρα αλλαγής γραμμής (πατήσει ENTER) δηλαδή δεν θα γινει skip όπως με το άν περίμενε ακέραιο ή κινητης υποδιαστολής. Με αυτο εννοείται οτι η μεταβλητή c1 θα έχει την τιμή '\n' (τυπικά 10 σε ASCII) αν δοκιμάσετε να το εκτυπώσετε μεσω μιας printf σαν %d. (Σελ.139 King)

 

Ο χρήστης μπορεί να χρησιμοποιήσει ένα κενό μεσα στο format string της scanf για να διορθώσει το παραπάνω ζήτημα . Το κενό αυτο σημαινει για την scanf να παραλείψει έναν ή περισσότερους κενούς χαρακτήρες. H σύνταξη της scanf λέει ότι βάζοντας το κενό αγνοείς όλες τις εμφανίσεις των "white-space" χαρακτήρων οπότε εκτός από το κενό αυτό καθεαυτό, αγνοείς και το tab και το newline.

 

>
printf(" Give a character: ");  
scanf(" %c" , &c1);

 

Eδω η scanf θα κάνει discard κάθε κενό χαρακτήρα ή αλλαγής γραμμής μέχρι να δοθεί ένας κανονικός και να τοποθετηθεί στην c1 . Ένας χαρακτήρας αλλαγής γραμμής ανήκει στην κατηγορία των white-space characters ενω το αντίστροφο δεν ισχύει δηλαδη υπάρχουν white-space characters οι οποίοι ΔΕΝ ειναι χαρακτήρες αλλαγής γραμμής (όπως ο κενός χαρακτήρας απο μονος του , ο χαρακτήρας tab κτλπ). Υπάρχουν whitespace characters που δεν ειναι αλλαγής γραμμής. ( Πχ ο κενός , tab characters κτλπ) .

 

Τι πρόβλημα θα παρουσιαστεί όταν πάει να γίνει αντιστοίχηση απο την scanf ενος κενού με τον ordinary '/' για παράδειγμα?

Έστω ο ακόλουθος κώδικας :

 

>
#include<stdio.h>

int main(void)
{
int x,y;
printf(" Give x and y as x/y : ");
scanf("%d/%d",&x , &y);
printf("%d, %d" x,y);
return 0;
}

 

Σε input 2/ 4

ειναι εντάξει! Γιατι? γιατι η scanf αγνοεί τους κενούς ενω ψάχνει για έναν integer!!!!

 

Όμως σε input 2 /4 δεν θα είναι γιατι πάει να κάνει αντιστοίχιση τον κενό με τον '/' . Θές μια

 

>
scanf("%d /%d" , &x , &y);

 

ένας κενός χαρακτήρας πάνω ειναι αρκετός για όσους δώσεις στην είσοδο!

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Eδω η scanf θα κάνει discard κάθε κενό χαρακτήρα ή αλλαγής γραμμής μέχρι να δοθεί ένας κανονικός και να τοποθετηθεί στην c1 .

Η scanf θα κάνει discard κάθε πρώτο κενό χαρακτήρα που θα δοθεί και στη συνέχεια θα διαβάσει τον επόμενο χαρακτήρα ο οποίος θα τοποθετηθεί στην c1.

 

Σε input 2/ 4

ειναι εντάξει! Γιατι? γιατι η scanf αγνοεί τους κενούς ενω ψάχνει για έναν integer!!!!

 

Όμως σε input 2 /4 δεν θα είναι γιατι πάει να κάνει αντιστοίχιση τον κενό με τον '/' .

Επιπρόσθετα, αν δώσεις σαν input το '2/4\n', επίσης θα δουλέψει.

 

Υπάρχει ο χαρακτήρας καταστολής εκχώρησης %*c που μπορεί να βοηθήσει σε κάποιες περιπτώσεις, αν και δε μου έχει τύχει ποτέ.

π.χ.

>printf(" Give a character: ");
scanf("%*c%c" , &c1);

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

Επίσης θα μπορούσες να το χρησιμοποιήσεις και κάπως έτσι:

>printf(" Give month and year with any token between them: ");
scanf("%d%*c%d", &month, &year);

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@gon1332

 

Οχι. Γιατι μπορει και ο 2ος να ειναι κενος. Μπορει να θελω να δωσω 10 κενους στη σειρά.

Ολους αυτους θα τους κανει discard . Αρα καθε κενο χαρακτηρα μεχρι να δωσεις πχ τον 'a'

ή τον 'Α' ή οποιον άλλον θες πχ απο αυτους. Μολις τον δωσεις τον διαβαζει και τον τοποθετει

οπως λες και εσυ στην θεση μνήμης που έχει δεσμευθει για την μεταβλητη κατα τη δηλωση της.

 

Και το '2/4\n θα δουλεψει οπως λες αλλα ο αλλαγης γραμμης μένει για εισοδο

σε επομενη κλήση της scanf. Επειδη ηθελα να το κρατησω απλο δεν έβαλα δευτερη κληση της.

Αρα η "επιδραση" του δεν φαινεται καθολου εδω. :P

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Πάντως παιδιά εγώ θα επιμείνω πως από ένα (πολύ πρώιμο) σημείο και μετά το να ασχοληθεί κανείς με όλο το εύρος δυνατοτήτων της scanf() είναι χαμένος κόπος & χρόνος. Όχι μόνο επειδή κανείς δεν τη χρησιμοποιεί, αλλά και γιατί αποκλείεται να θυμάται κανείς απ' έξω όλες της τις ιδιαιτερότητες (κυρίως όμως γιατί δεν τη χρησιμοποιεί κανείς).

 

Αντίθετα, το να αφιερώσει κάποιος τον χρόνο του φτιάχνοντας έναν απλό, resusable tokenizer είναι πολύ πιο εποικοδομητικό και χρήσιμο. Και από εκπαιδευτικής πλευράς και από πρακτικής.

 

Έναν wrapper δηλαδή της strtok() που θα επιστρέφει έναν πίνακα με τα tokens.

 

Στην πιο χαλαρή εκδοχή του θα μπορούσε να έχει έναν πρότυπο ας πούμε σαν κι αυτό...

 

>char **tokenιze( char *input, char *delims, int *n );

δημιουργώντας δυναμικά κι επιστρέφοντας ένα πίνακα από tokens (c-string) με τον αρχικό τους διαχωρισμό να τον υπαγορεύουν οι χαρακτήρες του 2ου ορίσματος (delims). To n θα είναι χρήσιμο να επιστρέφει το πλήθος των tokens, ώστε να μπορεί να υλοποιηθεί πιο εύκολα ο destructor του πίνακα. Αλλιώς θα πρέπει η tokenize() να εγγυάται πως το τελευταίο string του πίνακα θα είναι NULL, και θα είναι το μόνο NULL string.

 

Οπότε με κάτι σαν κι αυτό...

 

>
...
char input[ MAX_INPUT ] = {'\0};
char **tokens = NULL;
int  n = 0;
...
fgets( input, MAX_INPUT, stdin );
tokens = tokenize( input, "\n\t ", &n );
if ( NULL != tokens )	// ή ισοδύναμα: if ( n > 0 )
{
   // handle tokens here
 free_tokens( &tokens, n );
}
...

μπορούμε να κάνουμε οτιδήποτε θελήσουμε με το οποιοδήποτε token του πίνακα

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Οι regexes είναι για τελείως διαφορετική δουλειά (άσχετα που στην προκειμένη το παράδειγμα που συζητάμε είναι τόσο απλό που μπορεί να γίνει εύκολα και με regex).

 

H strtok (εκτός του ότι την απεχθάνομαι γιατί χρησιμοποιεί global state) είναι επίσης ανεπαρκής για να παρσάρεις λίγο πιο δύσκολα πράγματα (π.χ. δε μπορείς ούτε ένα μια shell command line να παρσάρεις σαν άνθρωπος γιατί υπάρχουν και quoted strings).

 

Για τέτοιες περιπτώσεις μια ικανοποιητική λύση η οποία είναι και ιδιαίτερα εκπαιδευτική αν δε σε πειράζει να τη γράψεις μόνος σου είναι καστομιά parser φτιαγμένος με finite state automaton (είμαι σίγουρος ότι κάπου έχω μερικά τέτοια αλλά με μια γρήγορη ματιά δε βρήκα αυτή τη στιγμή).

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για τέτοιες περιπτώσεις μια ικανοποιητική λύση η οποία είναι και ιδιαίτερα εκπαιδευτική αν δε σε πειράζει να τη γράψεις μόνος σου είναι καστομιά parser φτιαγμένος με finite state automaton (είμαι σίγουρος ότι κάπου έχω μερικά τέτοια αλλά με μια γρήγορη ματιά δε βρήκα αυτή τη στιγμή).

 

Αν βρει καποιος την κανονικη εκφραση που τον ενδιαφερει να αναγνωριζει ο parser , τοτε αυτο που προτεινεις ειναι μια πολυ καλη λυση

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για shell parsing τα regex είναι πολύ καλή λύση πάντως (κι εύχρηστη).

 

Αλλά οκ, νομίζω ξεφεύγουμε πολύ από τη κεντρική ιδέα του παρόντος νήματος.

 

Το global state της strtok() είναι όντως σπαστικό, αλλά δεν είναι και κάτι το τόσο το τρομερό πιστεύω. Βασικά αν του περνάς ένα αντίγραφο του αρχικού σου string αφήνεις ανέπαφο το αυθεντικό. Σε Posix υπάρχει ήδη έτοιμη συνάρτηση, η strdup(), και σε μη posix είναι trivial ο κώδικάς της...

 

>
char *s_clone( const char *s )
{
char *clone = NULL;

if ( !s || !*s
|| NULL == (clone = malloc( (1 + strlen(s)) * sizeof(char) )) )
	return NULL;

return strcpy(clone, s);
}

 

Οπότε π.χ. μπορείς να ξεκινάς τον κώδικα του tokenizer(input, ...) με ...

 

>char *temp = s_clone(input);

και να τον τελειώνεις με...

 

>free(temp)

Ομοίως μπορείς μέσα στο loop με την strotlk() να χρησιμοποιήσεις και πάλι την s_clone()...

 

>
...
for (i=0, cp=strtok(temp, delims); cp; i++, cp=strtok(NULL, delims) )
{
	if ( NULL == (tokens[i] = s_clone(cp)) ) {
		free_tokens( &tokens, i-1 );
		free( tokens );
		return NULL;
	}
}
...

ο **tokens κανονικά θέλει alloc και re-alloc as needed, αλλά το παραλείπω παραπάνω για να μη γίνει πολύ δυσνόητος ο κώδικας

 

Το ζουμί αυτού που θέλω να πω είναι πως με μια s_clone() "καθαρίζεις" από τυχόν παρενέργειες του global state της strtok().

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Το ανεπιθύμητο global state της strtok δεν είναι τόσο το string που παρσάρει (όπως είπες στη χειρότερη μπορείς να κάνεις τοπικά ένα αντίγραφο) αλλά το γεγονός ότι θυμάται πού βρίσκεται το parsing ανα πάσα στιγμή. Στην προκειμένη περίπτωση από πρακτική σκοπιά δεν παίζει ρόλο, αλλά τέτοιες προσεγγίσεις παραπέμπουν σε άλλες δεκαετίες όταν απλά δεν ξέραμε πώς να το κάνουμε καλύτερα.

 

Όσο για τη regex για shell parsing, δεσμεύομαι αν προσπαθήσεις να γράψεις μία να σου βρίσκω συνεχώς παραδείγματα όπου δε λειτουργεί σωστά μέχρι να τα παρατήσεις. :-D

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Το ανεπιθύμητο global state της strtok δεν είναι τόσο το string που παρσάρει (όπως είπες στη χειρότερη μπορείς να κάνεις τοπικά ένα αντίγραφο) αλλά το γεγονός ότι θυμάται πού βρίσκεται το parsing ανα πάσα στιγμή. Στην προκειμένη περίπτωση από πρακτική σκοπιά δεν παίζει ρόλο, αλλά τέτοιες προσεγγίσεις παραπέμπουν σε άλλες δεκαετίες όταν απλά δεν ξέραμε πώς να το κάνουμε καλύτερα.

Υποθέτω εννοείς το γεγονός πως δεν είναι re-entrant. Αν ναι, για αυτό ακριβώς έκανα το παραπάνω ποστ (η tokenize() είναι re-entrant :) )

 

Πάντως για να είμαστε δίκαιοι, η strtok() δεν είναι φτιαγμένη για να κάνει parsing, αλλά tokenizing... εννοώ είναι λίγο άδικο να τη κατηγορούμε για δουλειές που δεν είναι φτιαγμένη να κάνει.

 

Όσο για τη regex για shell parsing, δεσμεύομαι αν προσπαθήσεις να γράψεις μία να σου βρίσκω συνεχώς παραδείγματα όπου δε λειτουργεί σωστά μέχρι να τα παρατήσεις. :-D

Γιατί ρε συ; Για πες κάνα παράδειγμα.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...