Τα πάντα για την C

...του ubuntu και έργων ΕΛ/ΛΑΚ (Έργα-Οδηγοί-Προτάσεις)

Συντονιστής: konnn

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 16:21

Στο μεταξύ θα σου φτιάξω μια re-usable, bullet proof συνάρτηση να διαβάζεις έναν int από την κύρια είσοδο.

Εναλλακτική δηλαδή του...
Μορφοποιημένος Κώδικας: Επιλογή όλων
scanf("%d", &n);

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

Re: Τα πάντα για την C

Δημοσίευσηαπό Ilias95 » 29 Μαρ 2012, 16:58

Ορίστε:
Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>

#define ABSOLUTE_VALUE(x) ((x) >= 0 ? (x) : (-(x)))

int number_digits(int x)
{
int i;

if (!x) return 0;

x = ABSOLUTE_VALUE(x);
for (i = 0; x; x /= 10, i++);

return i;
}

int main(void)
{
int i, y, digits;

for (i = 1; i <= 30000; i++) {
digits = number_digits(i-1);
for (y = 0; y < digits; y++)
putchar('\b');
printf("%d", i);
}
return 0;
}


Την number_digits() την έγραψα αμέσως, ένα χαζό λάθος είχα στη λογική του loop που μου πήρε κάποια ώρα να το βρω.
Επίσης, νομίζω η number_digits() είναι re-usable. :P
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 17:07

Λοιπόν, η εν λόγω συνάρτηση είναι η readInt() η οποία χρησιμοποιεί την s_getsflushed() για να διαβάσει την κύρια είσοδο μονοκόμματα ως string, το οποίο κατόπιν μετατρέπει σε int μέσω της strtol().

Η υλοποίηση της s_getsflushed() είναι μια "υποχόνδρια" παραλλαγή εκείνης που έχω στην LIBS, με την έννοια πως δεν θεωρεί αυτονόητο πως το sizeof(char) ισούται με 1 byte (αρχικά μην αναλωθείς στον κώδικά αυτηνής, επικεντρώσου πρώτα στον κώδικα της readInt() και θεώρησε πως αντί της s_getsflushed() είχες π.χ. την fgets() ... απλώς η s_getsflushed() καθαρίζει και το stdin από τυχόν έξτρα χαρακτήρες ;) ).

Σε ότι αφορά την readInt() την έβαλα να επιστρέφει 2 διαφορετικές τιμές σε περίπτωση σφάλματος, INT_MIN και INT_MAX, επισημαίνοντας έτσι 2 διαφορετικούς λόγους αποτυχίας (τους γράφω σε σχόλια). Επίσης, επειδή δεν υπάρχει στάνταρ συνάρτηση stroi() που να δουλεύει απευθείας πάνω σε int, αναγκαστικά χρησιμοποίησα την strtol() σε έναν τοπικό long int (ret) τον οποίο και κάνω cast σε int πριν τον επιστρέψω.

Το βασικό error-checking το κάνει ουσιαστικά η strtol() κι εγώ απλώς "προσαρμόζω" τις δικές της τιμές αποτυχίας στις 2 δικές μου (INT_MIN και INT_MAX) έχοντας μια πιο "συμπαγή προς χρήση" συνάρτηση. Προφανώς, οι τιμές INT_MIN και INT_MAX παύουν πλέον να θεωρούνται έγκυρες τιμές για τους int που διαβάζουμε με την readInt(), αλλά είναι ελάχιστο τίμημα μπροστά στην αυτοματοποίηση που μας προσφέρει :)

Στην main() δείχνω έναν πιθανό τρόπο χρήση της readInt() με αντίστοιχα μηνύματα σφάλματος. Προφανώς δεν είναι υποχρεωτικό να τσεκάρει κανείς συνεχώς την τιμή επιστροφής της readInt() σε κάθε της κλήση, καλό είναι να το κάνει όμως. Το ζουμί είναι πως είτε την τσεκάρει είτε όχι, δεν υπάρχει περίπτωση να κρασάρει το πρόγραμμα αν του δοθεί bad input.

Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>
#include <stdlib.h> /* strtol() */
#include <limits.h> /* INT_MIN, INT_MAX */

/*********************************************************//**
* @brief Read into existing cstring s up to (ssize-1) chars from stdin or until
* ENTER is pressed, and null terminate it (removing ENTER if necessary).
* If more than (len-1) chars have been typed before the ENTER is pressed,
* they are ignored and they are removed from the stdin buffer.
*
* @return A pointer to the start of read s, or NULL on error.
*************************************************************
*/
char *s_getsflushed( char *s, const size_t ssize )
{
const size_t charsize = sizeof(char);/* don't assume 1 byte == sizeof(char) */
size_t i = 0;


/* sanity checks */
if ( !s )
return NULL;
if ( ssize < charsize )
return s;

/* read chars from stdin */
for (i=0; (s[i]=getc(stdin)) != '\n' && i < ssize-charsize; i += charsize)
; /* ... empty loop-body */

if ( s[i] != '\n' ) { /* ssize reached without '\n' */
s[i] = '\0'; /* ... null terminate s */
while (getchar() != '\n') /* ... flush remaining chars */
; /* ... ... empty loop-body */
}
else
s[i] = '\0';

return s;
}
/*********************************************************//**
* @brief Read safely an integer form stdin.
* @return The integer, or INT_MIN if input contained non-digits (including blanks),
* or INT_MAX in case of out of range integer value.
*************************************************************
*/
int readInt( void )
{
char input[ 255+1 ] = {'\0'};
char *tail = NULL;
long int ret = INT_MAX; /* INT_MAX & INT_MIN denote errors (see comments above) */

/* input failure? */
if ( !s_getsflushed(input, 255+1) )
return INT_MAX;

ret = strtol(input, &tail, 10 );

/* blank or non-digit chars in input? */
if ( '\0' != *tail || (tail == input && 0 == ret) )
return INT_MIN;

/* over/underflowed input? */
if ( ret > INT_MAX-1 || ret < INT_MIN+1 )
return INT_MAX;

/* everything ok */
return (int)ret;
}

/* ------------------------------------------------- */
int main( void )
{
int n;

printf( "Enter a valid integer: " );
n = readInt();

if ( INT_MAX == n )
puts("*** error: out of range integer!");
else if ( INT_MIN == n )
puts("*** error: blank or non-digit chars in input!");
else
printf( "%d was a valid input\n", n );

system("pause"); /* windows only */
return 0;
}

Όλες αυτές οι re-usable συναρτήσεις που θα φτιάχνεις μπορούν να προστίθενται σε μια βιβλιοθήκη την οποία θα την καλείς σε κάθε σου πρόγραμμα. Αν βαριέσαι τη διαδικασία της βιβλιοθήκης (που όντως είναι σπαστική) μπορείς να βάλεις τους ορισμούς των συναρτήσεων σου μέσα στο "myextras.h" και απλά να το κάνεις include σε κάθε σου πρόγραμμα (κι ας μη συνηθίζεται να μπαίνουν σε header files οι ορισμοί συναρτήσεων).

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

Re: Τα πάντα για την C

Δημοσίευσηαπό Ilias95 » 29 Μαρ 2012, 17:11

:bow:
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 17:20

Ilias95 έγραψε:Ορίστε:
Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>

#define ABSOLUTE_VALUE(x) ((x) >= 0 ? (x) : (-(x)))

int number_digits(int x)
{
int i;

if (!x) return 0;

x = ABSOLUTE_VALUE(x);
for (i = 0; x; x /= 10, i++);

return i;
}

int main(void)
{
int i, y, digits;

for (i = 1; i <= 30000; i++) {
digits = number_digits(i-1);
for (y = 0; y < digits; y++)
putchar('\b');
printf("%d", i);
}
return 0;
}


Την number_digits() την έγραψα αμέσως, ένα χαζό λάθος είχα στη λογική του loop που μου πήρε κάποια ώρα να το βρω.
Επίσης, νομίζω η number_digits() είναι re-usable. :P

Σε πάω ρε φίλε! Είσαι γάτος!

Δεν είναι η πιο efficient λύση, αλλά είναι μια ΑΞΙΟΠΡΕΠΕΣΤΑΤΗ λύση δεδομένης της απειρίας σου στη συγκεκριμένη γλώσσα (εννοώ για το ελάχιστο διάστημα που ασχολείσαι μαζί της)!

Λοιπόν, η number_digits() θα ήταν re-usable αν δεν εξαρτιόταν από το macro ABSOLUTE_VALUE(x). Δηλαδή ή θα πρέπει να την δώσεις σε κάποιον τρίτο μαζί με το macro, ή καλύτερα αντί για το macro να χρησιμοποιήσεις την στάνταρ συνάρτηση abs() (#include <stdlib.h> ).

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

Μπορείς λοιπόν να φτιάξεις μια συνάρτηση ή ένα macro που θα τυπώνει όσα '\b' του περάσεις σαν όρισμα, και όταν το καλείς να του περνάς σαν όρισμα απευθείας την printf() που χρησιμοποιείς για να τυπώσεις το νούμερο μέσα στο loop.

Δες μια πιθανή υλοποίηση...

Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MAXINPUT (255+1)

/* console backspace ntimes */
#define NBS( ntimes ) \
do { \
int nTImeSTMp = (ntimes); \
while (nTImeSTMp--) \
putchar('\b'); \
} while(0)

/* ------------------------------------------------- */
int main( void )
{
char input[ MAXINPUT ] = {'\0'};
int uplim = 0;
short i = 0;

do {
printf( "Enter upper limit (1 - %hd): ", SHRT_MAX );
fgets( input, MAXINPUT, stdin);
uplim = atoi(input);
} while ( uplim < 1 || uplim > SHRT_MAX);

printf( "Counter: " );
for (i=0; i < uplim; i++)
NBS( printf("%hd", i) );
putchar('\n');

system("pause"); /* windows only */
return 0;
}
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό Ilias95 » 29 Μαρ 2012, 17:29

Απορία.

Γιατί;
Κώδικας: Επιλογή όλων
do { \
int nTImeSTMp = (ntimes); \
while (nTImeSTMp--) \
putchar('\b'); \
} while(0)

Και όχι:
Κώδικας: Επιλογή όλων
int nTImeSTMp = (ntimes); \
while (nTImeSTMp--) \
putchar('\b'); \
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 17:35

Έκανα μια διόρθωση στην readInt() παραπάνω. Εκεί που τσεκάρω για over/underflowed input, διόρθωσα τα όρια σε INT_MIN+1 και INT_MAX-1 για να αποκλείσω τις INT_MIN & INT_MAX από έγκυρες τιμές (πριν τις είχα σκέτες στον έλεγχο).

btw, οι τιμές αυτές ορίζονται στο <limits.h> όπως και τα όρια όλων των integer τύπων της C. Μια σημαντική σημείωση είναι πως για να μπορείτε να ελέγχετε αν μια δοθείσα τιμή είναι εκτός ορίων του τύπου που έχετε ορίσει, πρέπει ο έλεγχος να γίνει με μια (τοπική συνήθως) μεταβήτή που θα είναι μεγαλύτερου τύπου.

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

Μορφοποιημένος Κώδικας: Επιλογή όλων
short int n;

/* read n here */

if ( n > SHRT_MAX || n < SHRT_MIN ) ...

προφανώς δεν έχει νόημα!!! Δεν θυμάμαι μάλιστα πως αντιδράει ο compiler (ή το run-time, αν περάσει από τον compiler) σε αυτές τις περιπτώσεις. Ευτυχώς υπάρχουν οι συναρτήσεις strto?() οι οποίες κάνουν handle σε επίπεδο συστήματος πιθανά over/underflows, επιστρέφουν ενδεικτικές τιμές για αυτές τις περιπτώσεις (βασικά επιστρέφουν τα όρια του τύπου, όπως έκανα κι εγώ στην readInt() )... προτείνω μάλιστα ΑΝΕΠΙΦΥΛΑΚΤΑ να διαβάσετε την τεκμηρίωση αυτών των συναρτήσεων και να μάθετε να τις χρησιμοποιείτε ! :)
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 17:43

Ilias95 έγραψε:Απορία.

Γιατί;
...
Και όχι:
...

Αυτό είναι συνηθισμένο λάθος που κάνουν όσοι ξεκινούν με C. Ο λόγος είναι ότι πρόκειται για macro, και όχι για συνάρτηση. Τα macros είναι μια απλή αντικατάσταση (search & replace) των γραμμών του σώματός τους σε όσες γραμμές εμφανίζεται ο ορισμός τους στον υπόλοιπο κώδικα (αυτό το κάνει ο preprocessor πριν καν ξεκινήσει το compilation).

Αν λοιπόν το είχα όπως δείχνεις στην 2η περίπτωση, τότε αυτό εδώ...

Μορφοποιημένος Κώδικας: Επιλογή όλων
for (i=0; i < uplim; i++)
NBS( printf("%hd", i) );

θα γινόταν πριν καν ξεκινήσει το compilation, έτσι...
Μορφοποιημένος Κώδικας: Επιλογή όλων
for (i=0; i < uplim; i++)
int nTImeSTMp = (ntimes);
while (nTImeSTMp--)
putchar('\b');\

ενώ έτσι όπως το έχω, γίνεται έτσι...
Μορφοποιημένος Κώδικας: Επιλογή όλων
for (i=0; i < uplim; i++)
do {
int nTImeSTMp = (ntimes);
while (nTImeSTMp--)
putchar('\b');
} while( 0 );

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

Re: Τα πάντα για την C

Δημοσίευσηαπό Ilias95 » 29 Μαρ 2012, 17:45

Σωστά. Απλά δεν έχω δει ακόμα macro definitions.
Οπότε φαντάζομαι εκτός απ' αυτά που είναι μία γραμμή τα υπόλοιπα ορίζονται πάντα σε do-while.
Ilias95
saintTUX
saintTUX
 
Δημοσιεύσεις: 1548
Εγγραφή: 29 Απρ 2011, 23:26
Εκτύπωση

Re: Τα πάντα για την C

Δημοσίευσηαπό migf1 » 29 Μαρ 2012, 17:49

Ilias95 έγραψε:Σωστά. Απλά δεν έχω δει ακόμα macro definitions.
Οπότε φαντάζομαι εκτός απ' αυτά που είναι μία γραμμή τα υπόλοιπα ορίζονται πάντα σε do-while.

Ναι (για να έχεις το κεφάλι σου ήσυχο... και με 0 στο while ).

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

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

Επιστροφή στο Ανάπτυξη Λογισμικού / Αλγόριθμοι

cron