Δημοσιεύτηκε: 10 Ιουν 2011, 13:05
από migf1
Καλημέρα, αν του βάλεις πάνω από 255 χαρακτήρες (που είναι το όριο που έβαλα για το line buffer) θα κάνει πάλι το ίδιο πρόβλημα. Αλλά από το ολότελα, καλή κι η... Παναγιώταινα :lol:

Το όλο θέμα βρίσκεται στο πως θα επιλέξει κανείς να διαβάζει το user input στο πρόγραμμά του. Οι περισσότερες από τις στάνταρ συναρτήσεις διαβάσματος στη C είναι line buffered, που σημαίνει πως ότι γράφει ο χρήστης δεν επεξεργάζεται μέχρι να πατήσει ENTER. Μόλις το πατήσει γίνονται οι καταχωρήσεις στις μεταβλητές που περιμένει η συνάρτηση διαβάσματος. Αν μάλιστα ο χρήστης έχει πληκτρολογήσει περισσότερα πράγματα από ότι περιμένει η συνάρτηση διαβάσματος, τότε τα έξτρα παραμένουν στο input buffer, οπότε δημιουργούν πρόβλημα στην επόμενη συνάρτηση διαβάσματος του προγράμματος (δηλαδή αντί να διαβάσει φρέσκο input από τον χρήστη, ξεκινάει διαβάζοντας τα απομεινάρια από το προηγούμενο input).

Αυτό ακριβώς το πρόβλημα αντιμετωπίζει η fflush(stdin) η οποία καθαρίζει τη stdin (που είναι το standard input buffer). Για αυτό και την καλώ πριν από κάθε συνάρτηση διαβάσματος στον κώδικά μου. Το πρόβλημα είναι πως σύμφωνα με το στάνταρτ της γλώσσας η fflush() πρέπει να χρησιμοποιείται για output buffers/files, ενώ αν χρησιμοποιηθεί για input buffers/files τότε τα αποτελέσματά της είναι undefined.

Οι περισσότεροι compilers υποστηρίζουν ως extension τη fflush() και με input buffers, αλλά από ότι φαίνεται οι πρόσφατες εκδόσεις του gcc σταμάτησαν (αυτό υποψιάζομαι τουλάχιστον).

Αφού λοιπόν η fflush() δεν καθαρίζει το input buffer, η λύση για να έχουμε πλήρη έλεγχο στο input του χρήστη είναι να διαβάζουμε μόνοι μας ότι μας γράφει, σε ένα δικό μας string (που το λέω inbuf στον κώδικα) και να το έχουμε ορίσει με μήκος μεγαλύτερο από το μέγιστο πλήθος χαρακτήρων που υπολογίζουμε να πληκτρολογήσει ο χρήστης, Aκόμα κι αν είναι κακόβουλος και προσπαθεί επί τούτου να δημιουργήσει πρόβλημα στο πρόγραμμά μας. Το μήκος αυτό εγώ το έβαλα 255 χαρακτήρες στον κώδικα, με την σταθερά: MAXSLEN_LINEBUF (η 256η θέση είναι για το '\0') την οποία τη χρησιμοποιώ ως όριο χαρακτήρων που διαβάζω από τον χρήστη, μέσω της συνάρτησης: s_get() (η οποία με τη σειρά της χρησιμοποιεί τη στάνταρ συνάρτηση getchar() ).

Αν λοιπόν ο χρήστης πληκτρολογήσει παραπάνω από 255 χαρακτήρες, το πρόβλημα επανέρχεται. Αλλά είναι λίγο απίθανο να είναι και κακόβουλος και να πληκτρολογήσει 3.5 γραμμές μόνο και μόνο για να δημιουργήσει πρόβλημα στο πρόγραμμα μας. Ή τουλάχιστον έτσι ελπίζουμε :lol:

Ένας ακόμα λόγος για να διαβάζουμε σε δικό μας string το input του χρήστη είναι πως έχουμε πλέον πλήρη έλεγχο σε αυτά που μας έγραψε, ελέγχοντας και κρατώντας ότι μας χρειάζεται και αγνοώντας ότι δεν μας χρειάζεται.

Μπορούμε για παράδειγμα να κρατήσουμε μονάχα την 1η λέξη (αν έχει γράψει περισσότερες) ή να κρατήσουμε μονάχα τον 1ο χαρακτήρα. Αυτό το τελευταίο κάνω εγώ στη τελευταία διόρθωση στο switch της main() με το * που έχω βάλει μπροστά από το κάλεσμα της s_get().
Κώδικας: Επιλογή όλων

switch ( tolower( *s_get(inbuf, MAXSLEN_LINEBUF) ) ) // GET USER CHOICE

Δεν γνωρίζω σε ποιο σημείο έχεις φτάσει στην εκμάθηση της γλώσσας, αλλά ας το εξηγήσω καλού-κακού. Τα strings στη C είναι arrays of char (πίνακες χαρακτήρων) αλλά στην ουσία το όνομά τους είναι pointer (δείκτης) στην 1η θέση του πίνακα. Οπότε όταν ορίζουμε το string: char inbuf[ whatever_length ] τότε είτε inbuf[0] γράψουμε είτε *inbuf, αναφερόμαστε στον 1ο χαρακτήρα του string inbuf.

Τώρα, τη συνάρτηση s_get() την έχω ορίσει να επιστρέφει το string που διαβάζει, το οποίο το περνάμε έτσι κι αλλιώς και σαν πρώτο όρισμα της συνάρτησης. Οπότε το string Που διαβάζει επιστρέφεται και μέσα στο πρώτο όρισμά της αλλά και ως ξεχωριστή τιμή επιστροφής της συνάρτησης. Στο παραπάνω switch χρησιμοποιώ την τιμή επιστροφής της συνάρτησης. Θα μπορούσα να χρησιμοποιήσω την συνάρτηση έξω από το switch και κατόπιν μέσα στο switch να ελέγξω τον 1ο χαρακτήρα του inbuf[] που έχει διαβαστεί μέσα στην συνάρτηση, ως εξής:
Κώδικας: Επιλογή όλων

s_get(inbuf, MAXSLEN_LINEBUF); // GET USER CHOICE
switch ( tolower( *inbuf ) ) // CHECK USER CHOICE
ίδιο αποτέλεσμα και με πιο ευανάγνωστο κώδικα ;)

Την συνάρτηση s_get() την χρησιμοποιώ και μέσα στην num_askuser() όταν διαβάζω τους όρους της εκάστοτε πράξης. Αμέσως μετά το διάβασμα περνάω το διαβασμένο string inbuf ως όρισμα στη στάνταρ συνάρτηση: strtod() η οποία μετατρέπει ένα string σε double. Η strtod() κάνει τους δικούς της εσωτερικούς ελέγχους για την εγκυρότητα του string, αν αντιστοιχεί δηλαδή σε έγκυρο double. Αν δεν αντιστοιχεί ή αν προκύψει κάποιο άλλο σφάλμα, τότε μας το επισημαίνει μέσω του 2ου ορίσματός της και μέσω της καθολικής μεταβλητής errno (η οποία ορίζεται στη βιβλιοθήκη <errno.h> και τη χρησιμοποιούν όλες οι στάνταρ συναρτήσεις της C). Κατόπιν ελέγχω αυτά τα 2 για να δω αν προέκυψε κάποιο σφάλμα, και τι είδους σφάλμα, και τυπώνω ανάλογα μηνύματα στην οθόνη. Για πλήρη κατανόηση της strtod() είναι καλύτερα να διαβάσεις το manual της (π.χ.: http://pubs.opengroup.org/onlinepubs/00 ... trtod.html).

Η strtod() ανήκει στην στάνταρ βιβλιοθήκη <stdlib.h> η οποία μεταξύ πολλών άλλων παρέχει κι άλλες συναρτήσεις μετατροπής ενός string σε αριθμό (integer, float, double, κλπ, κλπ).

Αν είσαι ακόμα "εξαρτημένος" από την scanf() (πράγμα καθόλου υποτιμητικό και πολύ σύνηθες σε όλους όσους πρωτο-ξεκινούν να μαθαίνουν τη γλώσσα) μπορείς να χρησιμοποιήσεις την επίσης στάνταρ συνάρτηση sscanf() αφού πρώτα έχεις διαβάσει το input του χρήστη σε ένα δικό σου string (για να καταλάβεις, άμα διαβάσεις το link, ο gcc θεωρεί την scanf() depreciated... σημείωση: στο παράδειγμα του link διαβάζει το string με τη συνάρτηση getline() η οποία όμως είναι extension μονάχα του GCC και δεν υπάρχει στη στάνταρ C, ούτε σε άλλους compilers, για αυτό εγώ έχω γράψει τη δικιά μου: s_get() ;) ).

ΥΓ. Ναι αντικατέστησε την V2 με αυτή την τελευταία και σβήσε αν θες τη V1. Ακόμα καλύτερα, μπορείς να βάλεις το link του κώδικα που έχω στο Ideone.com, το οποίο θα έχει πάντα την πιο ενημερωμένη έκδοση (θα το κρατάω updated δλδ).