Μεταβλητές τύπου χαρακτήρα 31 Μαρτίου 014 1 Μεταβλητές τύπου char Για χειρισμό χαρακτήρων η C διαθέτει τον τύπο char. Ο τύπος είναι βαθμωτός δηλαδή ακέραιης αναπαράστασης. Τυπικά έχει μέγεθος ενός byte καθώς ο αρχικός σχεδιασμός ήταν να μπορεί μία μεταβλητή τέτοιου τύπου να αποθηκεύσει ένα χαρακτήρα του πίνακα ASCII¹. Πρέπει να έχουμε στο μυαλό μας ότι ο τύπος char ουσιαστικά αποθηκεύει έναν ακέραιο μεγέθους ενός byte. Απλώς η C μας δίνει εργαλεία που βοηθούν το χειρισμό αυτών των αριθμών ως εκτυπώσιμων χαρακτήρων. Στο παράδειγμα 1 αναθέτουμε μία ακέραια τιμή, το 5, σε μία char μεταβλητή και στη συνέχεια την τυπώνουμε δύο φορές, τη μία χρησιμοποιώντας τον προσδιοριστή %d δηλαδή ως ακέραιο και την άλλη με τον προσδιοριστή %c δηλαδή ως χαρακτήρα. Listing 1: Mεταβλητή char 5 char c = 5; 7 printf("h metablntn ws akeraios: %d\n", c); 8 printf("h metablntn ws xaraktnras: %c\n", c); 9 10 return 0; 11 } Η έξοδος του παραπάνω προγράμματος θα είναι: H metablntn ws akeraios: 5 H metablntn ws xaraktnras: A Είναι σημαντικό να παρατηρήσει κανείς ότι τα περιεχόμενα της μεταβλητής ήταν και στις δύο περιπτώσεις τα ίδια, δηλαδή η ακέραια τιμή 5. Η διαφορά είναι ότι στην πρώτη περίπτωση ¹Αυτός ο τρόπος αναπαράστασης κειμένου τείνει να εγκαταλειφθεί καθώς τα περισσότερα περιβάλλοντα εργασίας έχουν πολυγλωσσικές απαιτήσεις. Για τέτοιες εφαρμογές χρειάζονται άλλου είδους εργαλεία και τύποι στους οποίους δε θα επεκταθούμε. 1
ζητήσαμε από τη C να τυπώσει αυτήν την τιμή ως αριθμό στο δεκαδικό σύστημα, οπότε μετά από αρκετές πράξεις τύπωσε το χαρακτήρα και στη συνέχεια το χαρακτήρα 5. Στη δεύτερη περίπτωση, η τιμή 5 θεωρείται ASCII κωδικός κάποιου χαρακτήρα, συγκεκριμένα του A οπότε και τυπώνεται στην οθόνη η συγκεκριμένη εικόνα. Όσο έχουμε υπόψη μας αυτήν τη λογική μπορούμε να χρησιμοποιήσουμε και άλλου τύπου μεταβλητές για την αποθήκευση χαρακτήρων. Το συγκεκριμένο παράδειγμα θα είχε ακριβώς την ίδια συμπεριφορά ακόμα και αν δηλώναμε τη μεταβλητή c ως int. Προφανώς δεν είναι βολικό όποτε θέλουμε ένα χαρακτήρα να ανατρέχουμε στον πίνακα ASCII για να βρούμε τον κωδικό του. Η C μας δίνει τη δυνατότητα να χρησιμοποιήσουμε σταθερές τύπου χαρακτήρα δηλαδή τους ίδιους τους χαρακτήρες μέσα σε μονά εισαγωγικά. Στο συγκεκριμένο παράδειγμα θα μπορούσαμε στη γραμμή 5 να γράψουμε 1 char c = 'A'; και το αποτέλεσμα θα ήταν το ίδιο, η μεταβλητή c θα είχε την τιμή 5. Η στανταρ βιβλιοθήκη της C δίνει κάποιες συναρτήσεις για είσοδο χαρακτήρων μία από τις οποίες είναι η getchar() η οποία διαβάζει ένα χαρακτήρα από το πληκτρολόγιο και τον επιστρέφει ως ακέραιο. Στο παρακάτω παράδειγμα χρησιμοποιούμε την getchar() για να διαβάσουμε ένα χαρακτήρα από το πληκτρολόγιο. Στη συνέχεια τυπώνουμε τον ίδιο το χαρακτήρα και τον ASCI κωδικό του. 5 char c; 7 printf(" Patnste eva plnktro: "); 8 c = getchar(); Listing : Εισαγωγή χαρακτήρων με τη getchar 9 printf(" Patnsate to xaraktnra %c me ASCII kwdiko %d\n", c, c); 10 11 return 0; 1 } Φυσικά μπορούμε να καλέσουμε την getchar() όσες φορές θέλουμε για να διαβάσουμε περισσότρους από έναν χαρακτήρες. Στο παράδειγμα χρησιμοποιούμε την getchar() μέσα σε ένα do-while βρόχο για να διαβάσουμε περισσότερους από έναν χαρακτήρες. 5 char c; 7 do { Listing 3: getchar σε βρόχο
8 printf(" Patnste eva plnktro: "); 9 c = getchar(); 10 printf(" Patnsate to xaraktnra %c me ASCII kwdiko %d\n", c, c); 11 } while (c!= '.'); 1 13 return 0; 14 } Στο συγκεκριμένο παράδειγμα παρατηρήστε ότι στη γραμμή 11 χρησιμοποιούμε τον συσχετιστικό τελεστή!= σε μία μεταβλητή τύπου χαρακτήρα και στη σταθερά τύπου χαρακτήρα '.' (τον ASCII κωδικό της τελείας). Μπορούμε να χρησιμοποιήσουμε όλους τους γνωστούς συσχετιστικούς τελεστές για να ελέγξουμε αν ένας χαρακτήρας έχει μεγαλύτερο, μικρότερο, μεγαλύτερο ή ίσο κτλ. κωδικό από κάποιον άλλο χαρακτήρα ή από κάποια σταθερά τύπου χαρακτήρα. Θα δούμε ανάλογα παραδείγματα στη συνέχεια. Ένα άλλο χαρακτηριστικό της getchar() που θα πρέπει να τρέξετε το πρόγραμμα για να το δείτε είναι ότι η getchar() πραγματοποιεί ασύγχρονη είσοδο δηλαδη δεν μας επιστρέφει τους χαρακτήρες τη στιγμή που γράφονται στο πληκτρολόγιο αλλά αργότερα. Αν, για παράδειγμα, γράψετε τη φράση Inside a broken clock το πρόγραμμα θα τυπώσει: Patnste eva plnktro: Inside a Patnsate to xaraktnra I me ASCII kwdiko 73 Patnste eva plnktro: Patnsate to xaraktnra n me ASCII kwdiko 110 Patnste eva plnktro: Patnsate to xaraktnra s me ASCII kwdiko 115 Patnste eva plnktro: Patnsate to xaraktnra i me ASCII kwdiko 105 Patnste eva plnktro: Patnsate to xaraktnra d me ASCII kwdiko 100 Patnste eva plnktro: Patnsate to xaraktnra e me ASCII kwdiko 101 Patnste eva plnktro: Patnsate to xaraktnra me ASCII kwdiko 3 Patnste eva plnktro: Patnsate to xaraktnra a me ASCII kwdiko 97 Patnste eva plnktro: Patnsate to xaraktnra me ASCII kwdiko 10 Patnste eva plnktro: broken clock Patnste eva plnktro: Patnsate to xaraktnra b me ASCII kwdiko 98 Patnste eva plnktro: Patnsate to xaraktnra r me ASCII kwdiko 114 Patnste eva plnktro: Patnsate to xaraktnra o me ASCII kwdiko 111 Patnste eva plnktro: Patnsate to xaraktnra k me ASCII kwdiko 107 Patnste eva plnktro: Patnsate to xaraktnra e me ASCII kwdiko 101 Patnste eva plnktro: Patnsate to xaraktnra n me ASCII kwdiko 110 Patnste eva plnktro: Patnsate to xaraktnra me ASCII kwdiko 3 Patnste eva plnktro: Patnsate to xaraktnra c me ASCII kwdiko 99 Patnste eva plnktro: Patnsate to xaraktnra l me ASCII kwdiko 108 3
Patnste eva plnktro: Patnsate to xaraktnra o me ASCII kwdiko 111 Patnste eva plnktro: Patnsate to xaraktnra c me ASCII kwdiko 99 Patnste eva plnktro: Patnsate to xaraktnra k me ASCII kwdiko 107 Patnste eva plnktro: Patnsate to xaraktnra me ASCII kwdiko 10 Δηλαδή, η getchar() δεν δίνει τους χαρακτήρες στο πρόγραμμα τη στιγμή που γράφονται στο πληκτρολόγιο αλλά σε σειρές μετά από το πάτημα του return στο πληκτρολόγιο². Αυτό που συμβαίνει, περίπου, είναι ότι το λειτουργικό παίρνει τους χαρακτήρες τη στιγμή που γράφονται στο πληκτρολόγιο και τους βάζει σε έναν ενδιάμεσο χώρο, ένα buffer. Όταν πατηθεί το return, το λειτουργικό ενημερώνει το πρόγραμμα ότι υπάρχουν χαρακτήρες στον buffer τους οποίους μπορεί να επεξεργαστεί. Το πρόγραμμα στη συνέχεια, κάθε φορά που ζητάει να διαβάσει έναν χαρακτήρα από το πληκτρολόγιο, στην πραγματικότητα διαβάζει τον επόμενο διαθέσιμο χαρακτήρα από τον buffer. Παρατηρήστε επίσης ότι το ίδιο το πλήκτρο return έρχεται στον buffer με τον ASCII κωδικό 10. Όπως αναφέρθηκε μπορούμε να χρησιμοποιήσουμε τους συνήθεις συσχετιστικούς τελεστές για να επαληθεύσουμε συνθήκες πάνω σε char μεταβλητές. Σε αυτές τις περιπτώσεις είναι χρήσιμο να θυμόμαστε κάποιες ιδιότητες του πίνακα ASCII, όπως για παράδειγμα ότι οι χαρακτήρες από το 0 έως το 9, από το A έως το Z και από το a έως το z έχουν συνεχόμενους αριθμητικούς κωδικούς. Έτσι θα μπορούσαμε τροποποιώντας το παραπάνω παράδειγμα να μετρήσουμε πόσοι χαρακτήρες από εκείνους που έγραψε ο χρήστης είναι πεζοί αλλάζοντας τις γραμμές 7 έως 11 όπως παρακάτω: Listing 4: Μέτρηση πεζών χαρακτήρων 5 char c, count_lower = 0; 7 printf(" Grapse eva keimeno kai patnste tnv teleia:\ n"); 8 do { 9 c = getchar(); 10 if (c >= 'a' && c <= 'z') 11 count_lower ++; 1 } while (c!= '.'); 13 printf(" Egrapses %d pezoys xaraktnres\n", count_lower); 14 15 return 0; 1 } Αντίστοιχα μπορούμε να χρησιμοποιήσουμε αριθμητικούς τελεστές για να κάνουμε μετατροπές σε χαρακτήρες. Με δεδομένο ότι οι πεζοί και κεφαλαίοι χαρακτήρες είναι συνεχόμενοι μπορούμε να ²Υπάρχουν βιβλιοθήκες για τη C που δίνουν τη δυνατότητα για σύγχρονη είσοδο χαρακτήρων. Δυστυχώς αυτές είναι είτε πολύ χαμηλού επιπέδου όπως για παράδειγμα η συνάρτηση select είτε ανήκουν σε βιβλιοθήκες που δεν είναι μέρος του προτύπου και έτσι δεν διατίθενται σε όλα τα περιβάλλοντα όπως για παράδειγμα η βιβλιοθήκη curses στο UNIX και η βιβλιοθήκη conio στα Windows. 4
προσθέσουμε έναν αριθμό για να μετατρέψουμε έναν κεφαλαίο σε πεζό. Αν τροποποιήσουμε τις γραμμές 8 έως 1 του προηγούμενου παραδείγματος όπως παρακάτω, προκύπτει ένα πρόγραμμα που μετατρέπει τους πεζούς χαρακτήρες που γράφει ο χρήστης σε κεφαλαίους: 1 do { c = getchar(); 3 if (c >= 'a' && c <= 'z') 4 c -= ('a' - 'A'); 5 printf("%c", c); } while (c!= '.'); Listing 5: Μέτατροπή πεζών σε κεφαλαία Εκτός από τους αριθμητικούς και συσχετιστικούς τελεστές, μπορούμε να εφαρμόσουμε και τους bit τελεστές σε μεταβλητές τύπου char, αφού πρόκειται για μεταβλητές ακέραιου τύπου. Παρατηρώντας ότι στον κώδικα ASCII, οι πεζοί χαρακτήρες έχουν κωδικούς από 97 και πάνω ενώ οι κεφαλαίοι από 5 και πάνω, μπορούμε να συμπεράνουμε ότι για να μετατρέψουμε έναν πεζό σε κεφαλαίο, αρκεί να σβήσουμε το bit με τιμή 3, το οποίο είναι ισοδύναμο με το να εφαρμόσουμε τη λογική πράξη XOR. Οπότε αλλάζουμε την εντολή c -= ('a'- 'A'); σε c ^= 3;³. Με αντίστοιχο τρόπο, μπορούμε να υλοποιήσουμε λειτουργίες που συχνά χρειάζονται στο χειρισμό κειμένων όπως για παράδειγμα συναρτήσεις που μας λένε αν ένας χαρακτήρας είναι πεζός ή κεφαλαίος, αριθμητικός ή αλφαβητικός, κενό ή σημείο στίξης κοκ. Η στάνταρ βιβλιοθήκη της C μας βγάζει από τον κόπο δίνοντας αρκετές τέτοιες συναρτήσεις, μερικές από τις οποίες αναφέρουμε παρακάτω: islower(c): Ελέγχει αν ο c είναι πεζός χαρακτήρας. isupper(c): Ελέγχει αν ο c είναι κεφαλαίος χαρακτήρας. isalpha(c): Ελέγχει αν ο c είναι αλφαβητικός. isalnum(c): Ελέγχει αν ο c είναι αλφαριθμητικός, δηλαδή αλφαβητικός ή αριθμητικός. Ισοδύναμη με την έκφραση isalpha(c) isdigit(c). isdigit(c): Ελέγχει αν ο c είναι αριθμητικό ψηφίο, δηλαδή από το 0 μέχρι το 9. isxdigit(c): Ελέγχει αν ο c είναι αριθμητικό ψηφίο του δεκαεξαδικού συστήματος, δηλαδή από το 0 μέχρι το 9 ή από το a μέχρι το z ή από το A μέχρι το Z. isblank(c): Ελέγχει αν ο c είναι κάποιο κενό, δηλαδή space ή tab. isspace(c): Ελέγχει αν ο c είναι κάποιο διάστημα, δηλαδή space, οριζόντιο ή κάθετο tab (\t, \v) ή κάποιος από τους χαρακτήρες feed (\f), αλλαγής γραμμής (\n), επαναφοράς κεφαλής (\r). isascii(c): Ελέγχει αν ο c είναι ASCII χαρακτήρας⁴. ³Αυτή η τακτική δε θα έπρεπε να εφαρμόζεται σε πραγματικά προγράμματα γιατί υποθέτει ότι ο κώδικας ASCII θα συνεχίσει να χρησιμοποιείται από τη C. Αν και αυτό είναι απίθανο να αλλάξει, καλό είναι να μη χρησιμοποιούμε συγκεκριμένες, γραμμένες με το χέρι τιμές όταν χειριζόμαστε χαρακτήρες. ⁴Ο πίνακας ASCII δεν αντιστοιχίζει όλες τις τιμές από το 0 μέχρι το 55 που μπορεί να αποθηκευτούν σε ένα byte σε χαρακτήρες. Σε πολλές κωδικοποιήσεις (encodings) οι τιμές στο εύρος 0-55 που δεν αναφέρονταν στον πίνακα ASCII αντιστοιχίζονταν σε χαρακτήρες, συνήθως για την αναπαράσταση μη-αγγλικών αλφαβήτων. 5
isgraph(c): Ελέγχει αν ο c είναι εκτυπώσιμος, εκτός από το κενό. isprint(c): Ελέγχει αν ο c είναι εκτυπώσιμος, συμπεριλαμβανομένου του κενού. ispunct(c): Ελέγχει αν ο c είναι εκτυπώσιμος χαρακτήρας και δεν είναι διάστημα ή αλφαριθμητικός χαρακτήρας.