ΠΕΡΙΕΧΟΜΕΝΑ
4.
ΠΕΡΝΩΝΤΑΣ ΔΟΜΕΣ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ
6.
ΠΙΝΑΚΕΣ ΚΑΙ ΔΟΜΕΣ ΜΕΣΑ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ
III. ΕΝΩΣΕΙΣ (UNIONS)
V. ΤΥΠΟΙ ΟΡΙΖΟΜΕΝΟΙ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ (U.D.T. - USER DEFINED TYPES)
ΚΕΦΑΛΑΙΟ 8ο
ΔΟΜΕΣ, ΕΝΩΣΕΙΣ, ΤΥΠΟΙ ΟΡΙΖΟΜΕΝΟΙ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ
ΚΑΙ ΑΠΑΡΙΘΜΗΤΕΣ
Στη C, μια
δομή είναι μία συλλογή από μεταβλητές οι οποίες αναφέρονται με το ίδιο όνομα,
πράγμα το οποίο σημαίνει ότι έχουν στενή σχέση μεταξύ τους. Οι μεταβλητές οι
οποίες αποτελούν τη δομή καλούνται στοιχεία της δομής. Όλα τα στοιχεία μιας
δομής σχετίζονται μεταξύ τους λογικά. Για παράδειγμα, το όνομα και η διεύθυνση
σε έναν τηλεφωνικό κατάλογο φυσιολογικά αναπαρίστανται σα μία δομή.
Π.χ.: struct addr
{
char
name[30];
char
street[40];
char
city[20];
char
state[2];
unigned
long int zip;
};
Η
δεσμευμένη λέξη struct λέει στον
compiler ότι μία
δομή πρόκειται να δηλωθεί. Η δήλωση τελειώνει με ερωτηματικό επειδή στην ουσία
αποτελεί εντολή. Το addr δίνει
ταυτότητα στην ιδιαίτερη αυτή δομή δεδομένων και τον καθοριστή του τύπου της.
Μέχρι στιγμής, στον κώδικα δεν έχει δηλωθεί στην πραγματικότητα καμία
μεταβλητή. Μόνο η φόρμα των δεδομένων έχει οριστεί. Για να δηλώσουμε πραγματικά
μεταβλητή με αυτή τη δομή πρέπει να γράψουμε:
struct addr addr_info;
Αυτό
δηλώνει μία μεταβλητή structure, του
τύπου addr η οποία λέγεται addr_info. Όταν
ορίζουμε μία δομή ουσιαστικά δηλώνουμε μία σύνθετη μεταβλητή. Η Turbo C αυτομάτως δεσμεύει αρκετή μνήμη για να τοποθετήσει όλες τις
μεταβλητές που αποτελούν δομή.
Name 30 Bytes
Street 40 //
City 20 // addr_info
State
3 //
Zip
4 //
(Η δομή της addr_info στη
μνήμη)
Επίσης, μπορούμε
να δηλώσουμε περισσότερες από μία μεταβλητές της ίδια στιγμή που ορίζουμε μία
δομή.
Π.χ.:
struct addr {
char name[30];
char street[40];
char city[20];
char state[2];
unigned long int zip;
} addr_info, binfo, cinfo;
ορίζει
έναν τύπο δομής, ο οποίος καλείται addr και
δηλώνει τις μεταβλητές addr_info, binfo, cinfo να είναι αυτού του τύπου. Αν χρειαζόμασταν μόνο μία
μεταβλητή, το όνομα της δομής δε θα χρειαζόταν. Αυτό σημαίνει:
struct {
char name[30];
char street[40];
char city[20];
char state[2];
unigned long int zip;
}addr_info;
το παραπάνω δηλώνεται σαν ένα
όνομα μεταβλητής addr_info, και ορίζεται από τη δομή η οποία προηγείται.
Η γενική
μορφή δήλωσης ενός structure είναι:
struct structure_name {
type var_name;
type var_name;
. .
. .
. .
} structure_variables;
To structure_name είναι το όνομα της δομής και όχι ένα όνομα μεταβλητής. Τα structure_variables είναι τα ονόματα μεταβλητών τοποθετημένα σε μία λίστα η
οποία χωρίζεται με , .
Τα διάφορα
στοιχεία των δομών αναφέρονται κάνοντας χρήση του dot ( . ) operator. Για
παράδειγμα, ο παρακάτω κώδικας αποτελεί την τιμή 12131 στο πεδίο zip της structure
μεταβλητής addr_info η οποία δηλώθηκε προηγουμένως:
addr_info.zip = 12131;
Όλα τα στοιχεία δομών προσπελαύνονται με τον ίδιο τρόπο. Η γενική μορφή σύνταξης είναι:
structure_name.element_name;
Έτσι για
να εμφανίσουμε τον ταχυδρομικό κώδικα στην οθόνη θα πρέπει να γράψουμε:
printf( “%d”, addr_info.zip);
Με την
ίδια λογική θα μπορούσαμε να χρησιμοποιήσουμε τη συνάρτηση gets( ) για να εισάγουμε στοιχεία στον πίνακα χαρακτήρων addr_info.name:
gets(addr_info.name);
Αυτό περνά
έναν character pointer στην αρχή του στοιχείου name. Για να προσπελάσουμε τα διάφορα στοιχεία του addr_info.name, θα πρέπει να τοποθετήσουμε δείκτες στο name. Για παράδειγμα, για να εμφανίσουμε τα περιεχόμενα
του addr_info.name ένα – ένα
χαρακτήρα θα πρέπει:
register int t;
for (t = 0; addr_info.name [t], ++t)
putchar(addr_info.name [t]);
Η πιο
κοινή χρήση των δομών είναι σε πίνακες από δομές. Για να δηλώσουμε ένα array of structures, πρέπει
πρώτα να δηλώσουμε τη δομή και μετά να δηλώσουμε έναν πίνακα αυτού του τύπου.
Π.χ. για να δηλώσουμε έναν πίνακα 100 στοιχείων του τύπου addr, ο οποίος έχει δηλωθεί στην προηγούμενη παράγραφο,
θα πρέπει να γράψουμε:
struct addr_info[100];
Αυτό
δημιουργεί 100 σετ μεταβλητών οργανωμένων όπως είναι δηλωμένη η δομή adrr. Για την προσπέλαση του συγκεκριμένου στοιχείου του
πίνακα (μιας συγκεκριμένης δομής μέσα στον πίνακα) θα πρέπει να κάνουμε χρήση
δεικτών με το όνομα της δομής. Π.χ. για να εμφανίσουμε τον ταχυδρομικό κώδικα
της 3ης δομής θα γράψουμε:
printf( “%d”, addr_info[2].zip);
Όπως σε όλες
τις μεταβλητές με δείκτες (arrays) σαν
πρώτο στοιχείο θεωρείται το 0.
Για την
κατανόηση των δομών και των πινάκων από δομές, θα αναλύσουμε ένα απλό
πρόγραμμα, το οποίο χρησιμοποιεί ένα array
of structures για να κρατήσει τις πληροφορίες.
Οι συναρτήσεις αυτού του προγράμματος παρουσιάζονται σε σχέση με τις δομές και
τα στοιχεία τους με διάφορους τρόπους ώστε να φανεί η χρήση των δομών. Οι
πληροφορίες που θα διαχειριστούμε σ’ αυτό το πρόγραμμα είναι οι εξής:
item name
cost
number on hand
Ο πλήρης
κώδικας παρουσιάζεται παρακάτω:
#define MAX 100
struct inv {
char
item[30];
float
cost;
int
on_hand;
}
inv_info[MAX];
void init_list( ), list( ), delete( ), enter( );
main( )
{
char
choice;
init_list(
); / * Αρχικοποίηση Structure Array * /
for (;
;) {
choice
= menu_select( );
switch
(choice) {
case
1:
enter(
);
break;
case
2:
delete(
);
break;
case
3:
list(
);
break;
case
4:
exit(0);
} /
* Τέλος της case * /
} / * Tέλος της for * /
} /
* Τέλος του main * /
void init_list( ) / * Αρχικοποίηση του Structure Array * /
{
register
int t;
for (t = 0;
t<MAX; ++t)
inv_info
[t]. Item[0] = ‘ \ 0 ‘ ;
}
menu_select ( ) /
* Επιλογή του Χρήστη * /
{
char
s[80];
int c;
printf
(“\n”);
printf(“1. Enter An Item \n”);
printf(“2. Delete An Item \n”);
printf(“3. List The Inventory \n”);
printf(“4. Quit
\n”);
do {
printf(“\n Enter Your Choice: “);
gets
(s);
c
= atoi (s);
} while (c<0
c> 4);
return
c;
}
void enter( ) /
* Είσοδος Πληροφοριών * /
{
int
slot;
slot =
find_free( );
if (slot
= = - 1) {
printf(“\n
List Full”);
return;
}
printf(“Enter
Item: “);
gets(inv_info[slot].item);
printf(“Enter
Cost”);
scanf(“%f”,
&inv_info[slot].cost);
printf(“Enter
Number on Hand: “);
scanf(“%d%*c”,
&inv_info[slot].on_hand);
}
find_free( ) /
* Έλεγχος για Ελεύθερες Θέσεις στον Πίνακα * /
{
register int t;
for (t = 0;
inv_info[t].item[0] && t<MAX; ++t);
if (t ==MAX) return –1;
return
t;
}
void delete( ) /
* Διαγραφή Στοιχείου */
{
register int slot;
char s
[80];
printf(“Enter
Record #: “);
gets(s);
slot =
atoi(s);
if
(slot>=0 && slot < MAX)
inv_info[slot].item[0]
= ‘ \ 0 ’;
}
void list( ) / * Εμφάνιση των Στοιχείων * /
{
register int t;
for (t = 0;
t<MAX; ++t)
{
if (inv_info[t].item[0])
{
printf(“Item:
%s \n”, inv_info[t].item);
printf(“Cost:
%f \n”, inv_info[t].cost);
printf(“On
Hand: %d\n\n”, inv_info[t].on_hand);
}
}
printf(“\ n \ n “);
}
iv)
Περνώντας
Δομές σε Συναρτήσεις
Έως εδώ οι
δομές και οι πίνακες δομών που συναντήσαμε στα παραδείγματά μας έχουν δηλωθεί
σαν global ή σαν local μέσα στις συναρτήσεις που τις
χρησιμοποιούν. Εδώ θα ερευνήσουμε επακριβώς τον τρόπο με τον οποίο μπορούμε να
περάσουμε δομές σαν παραμέτρους συναρτήσεων.
α) Πέρασμα Στοιχείων Δομών σε
Συναρτήσεις
Όταν
περνάμε ένα στοιχείο μιας δομής σε μία συνάρτηση, ουσιαστικά περνάμε την τιμή αυτού
του στοιχείου στη συνάρτηση. Έτσι, είναι σα να περνάμε μία απλή μεταβλητή
(εκτός φυσικά αν το στοιχείο αυτό είναι σύνθετη μεταβλητή, όπως ένας πίνακας
χαρακτήρων – string). Π.χ.
έστω ότι έχουμε την εξής δομή:
struct fred {
char x;
int y;
float z;
char s[10];
}
mike;
Τα
στοιχεία αυτά θα μπορούσαν να περαστούν σε συναρτήσεις ως εξής:
func1(mike.x); / * Πέρασμα του χαρακτήρα x */
func2(mike.y); / * Πέρασμα του ακεραίου y * /
func3 (mike.z); / * Πέρασμα του δεκαδικού z * /
func4(mike.s[2]); / * Πέρασμα του 2ου στοιχείου του
πίνακα s*/
Σε
περίπτωση πάντως που ο προγραμματιστής επιθυμεί να περάσει τη δ/νση ενός
συγκεκριμένου στοιχείου της δομής κάνοντας χρήση του περάσματος call-by-value, θα πρέπει να χρησιμοποιήσει τον τελεστή & πριν
το όνομα της δομής. Π.χ. στη δομή mike του
προηγούμενου παραδείγματος, για να περάσουμε σε μία συνάρτηση τις διευθύνσεις
κάποιων στοιχείων θα πρέπει να γράψουμε:
func1(&mike.x); / * Πέρασμα της δ/νσης του χαρακτήρα x */
func2(&mike.y); / * Πέρασμα της δ/νσης του ακεραίου y * /
func3 (&mike.z); / * Πέρασμα της δ/νσης του float z * /
func4(mike.s); / * Πέρασμα της δ/νσης του string s
ΔΕΝ χρησιμοποιείται ο τελεστής &
*/
func5(mike.s[2]); / * Πέρασμα του 3ου στοιχείου του
πίνακα s*/
Σημειώνεται
ότι ο τελεστής & χρησιμοποιείται μπροστά από το όνομα της δομής και όχι από
το όνομα του στοιχείου. Επίσης, σημειώνεται ότι το στοιχείο s του προηγούμενου παραδείγματος, αποτελεί ένα string οπότε ο τελεστής & δεν απαιτείται αφού όταν
χρησιμοποιούμε έναν πίνακα χωρίς indexing
αναφερόμαστε στη δ/νση της μνήμης του στοιχείου 0 του πίνακα.
β) Πέρασμα Ολόκληρων Δομών σε
Συναρτήσεις
Όταν μία
δομή χρησιμοποιείται σαν όρισμα σε συνάρτηση, ολόκληρη η δομή περνά στη
συνάρτηση κάνοντας χρήση της standard
call-by-value μεθόδου.
Αυτό σημαίνει ότι οποιεσδήποτε αλλαγές θα συμβούν στα περιεχόμενα της δομής
μέσα στη συνάρτηση, δε θα έχουν αντίκτυπο στη δομή έξω από τη συνάρτηση, η
οποία χρησιμοποιείται σαν όρισμα.
Όταν
χρησιμοποιούμε μία δομή σαν παράμετρο σε συνάρτηση, το πιο σημαντικό πράγμα που
πρέπει να θυμόμαστε είναι ότι ο τύπος του ορίσματος θα πρέπει να ταιριάζει με
τον τύπο της παραμέτρου. Για παράδειγμα το όρισμα arg και η παράμετρος parm θα πρέπει
να έχουν δηλωθεί να είναι του ίδιου τύπου δομές.
main( )
{
struct {
int a,b;
char ch;
}
arg;
arg.a = 1000;
f1 (arg);
}
f1(parm)
struct {
int x,y;
char ch ;
} parm ;
{
printf(“%d”,
parm.x);
}
Το
πρόγραμμα αυτό εμφανίζει τον αριθμό 1000 στην οθόνη. Σε γενικές γραμμές η
μέθοδος αυτή, τεχνικά, δεν είναι εσφαλμένη. Παρ’ όλ’ αυτά, δεν χρησιμοποιείται
και πολύ από τους περισσότερους προγραμματιστές. Μια καλύτερη προσέγγιση του
ζητήματος θα ήταν να δηλώσουμε τη δομή σαν global και στη συνέχεια
απλά να χρησιμοποιήσουμε το όνομά της για τη δήλωση structure variables και
παραμέτρων. Κάνοντας χρήση αυτής της μεθόδου το παραπάνω πρόγραμμα θα γραφτεί
ως εξής:
#include <stdio.h>
struct struct_type { / * Ορισμός της δομής * /
int a,b;
char ch;
} ;
main( )
{
struct
struct_type arg; / * Δήλωση της arg * /
arg.a = 1000;
f1(arg);
}
f1(parm)
struct struct_type parm ;
{
printf(“%d”, parm.a);
}
Η μέθοδος αυτή όχι μόνο γλιτώνει τον προγραμματιστή από περιττές έγνοιες αλλά τον βοηθά να σιγουρέψει ότι τα ορίσματα ταιριάζουν με τις παραμέτρους. Εκτός αυτών φυσικά μειώνει και τον κώδικα του προγράμματος.
Η Turbo C επιτρέπει pointers σε δομές με τον ίδιο τρόπο όπως στους άλλους τύπους
μεταβλητών. Παρ’ όλ’ αυτά, υπάρχουν κάποια σημεία στα οποία θα πρέπει να δοθεί
ιδιαίτερη έμφαση.
α) Δηλώνοντας ένα Structure Pointer
Οι structure pointers δηλώνονται με την τοποθέτηση ενός * μπροστά από το όνομα
της δομής. Για παράδειγμα, στη δομή του προηγούμενου παραδείγματος, την addr, το παρακάτω δηλώνει τον addr_pointer να είναι pointer σε δεδομένα αυτού του τύπου:
struct addr *addr_pointer;
β) Χρησιμοποιώντας Structure Pointers
Υπάρχει
ένα βασικό μειονέκτημα στο πέρασμα, ακόμη και των πιο απλών δομών σε
συναρτήσεις: Πρέπει να γίνει push (και pop) όλων των στοιχείων της δομής στο stack. Σε απλές δομές με λίγα στοιχεία το πρόβλημα αυτό
δεν είναι σοβαρό, αλλά αν χρησιμοποιούνται αρκετά στοιχεία ή αν μερικά στοιχεία
από τα στοιχεία είναι πίνακες ή δομές, τότε η διαδικασία εκτέλεσης ενδέχεται να
εκφυλιστεί σε άγνωστο επίπεδο. Η λύση σ’ αυτό το πρόβλημα είναι να περάσουμε
στη συνάρτηση ένα pointer στη δομή.
Όταν περνά
ένας pointer σε structure σε μία συνάρτηση, μόνο η δ/νση
του structure γίνεται push και pop στο stack. Αυτό σημαίνει ότι έτσι μπορούμε να εκτελέσουμε μία
πολύ γρήγορη κλήση συνάρτησης. Επίσης, επειδή στη συνάρτηση αναφέρεται η ίδια η
δομή και όχι ένα αντίγραφό της, αυτή μπορεί να τροποποιηθεί. Για να βρούμε τη
δ/νση μιας structure
variable
χρησιμοποιούμε τον τελεστή & πριν το όνομα της δομής. Π.χ. έστω το παρακάτω
τμήμα κώδικα.
struct bal {
float balance;
char name [80];
} person;
struct bal*; / * Δήλωση ενός structure pointer * /
p = &person;
τοποθετεί τη δ/νση της δομής person στον pointer
p. Για να αναφερθούμε τώρα σε
κάποιο από τα επιμέρους στοιχεία της δομής μπορούμε να γράψουμε:
(*p).balance
Η
παρένθεση είναι απαραίτητη γιατί ο dot
operator (. )
είναι ιεραρχημένος υψηλότερα από τον pointer *. Στην
πραγματικότητα μπορούν να χρησιμοποιηθούν δύο μέθοδοι για να αναφερθούμε σε ένα
στοιχείο δομής στο οποίο έχει δοθεί ένας pointer.
Η πρώτη
μέθοδος χρησιμοποιεί ακριβή αναφορά pointer, αλλά
μπροστά στις σημερινές μεθόδους φαντάζει αρχαϊκή. Παρ’ όλ’ αυτά, πολλές από τις
παλιές εκδόσεις της C τη χρησιμοποιούν, καθώς επίσης και οι μοντέρνες
προσεγγίσεις στηρίζονται σ’ αυτή.
Η δεύτερη
μέθοδος προσπέλασης στοιχείων δομών που έχουν δοθεί σαν pointers είναι o arrow
operator (à), ο
οποίος στην ουσία αποτελεί συντομογραφία της πρώτης μεθόδου.
Για την
καλύτερη κατανόηση της χρήσης των structure
pointers, ας δούμε
το παρακάτω πρόγραμμα το οποίο εμφανίζει την ώρα, τα λεπτά και τα δευτερόλεπτα
στην οθόνη, κάνοντας χρήση ενός software
delay timer.
#include <stdio.h>
struct tm {
int
hours;
int
minutes;
int seconds;
} ;
main( )
{
struct
tm time;
time.hours
= 0;
time.minutes
= 0;
time.seconds
= 0;
for (;
;) {
update
(&time);
display(&time);
}
}
update (struct tm *t)
{
(*t).seconds
++ ;
if ( (*
t).seconds==60) {
(*t).seconds
= 0;
(*t).minutes++;
}
if (
(*t).minutes ==60) {
(*t).minutes
= 0 ;
(*t).hours++ ;
}
if ( ( * t).hours ==24) (*t).hours=0 ;
delay(
);
}
display (struct tm *t)
{
printf(“%d:”,
(*t).hours);
printf(“%d:”,
(*t). minutes);
pirntf(“%d:”,
(*t).seconds);
}
delay( )
{
long int
t ;
for
(t=1 ; t<128000 ; ++t)
}
Στο
πρόγραμμα αυτό καλείται μία καθολική δομή, η tm. Μέσα στη main( )
δηλώνεται η structure variable time, να είναι
τύπου tm, και αρχικοποιείται στο
00:00:00. αυτό σημαίνει ότι η time είναι
απευθείας γνωστή μόνο στη συνάρτηση main( ). Οι
συναρτήσεις update( ), που
αλλάζει την ώρα, και display( ), που
εμφανίζει την ώρα, δέχονται σαν παράμετρο τη δ/νση μνήμης της δομής. Και στις
δύο συναρτήσεις το όρισμα έχει δηλωθεί να είναι ένας pointer σε μία δομή του τύπου m έτσι ώστε ο compiler να
γνωρίζει πώς να αναφερθεί στα επιμέρους στοιχεία.
Κάθε
στοιχείο της δομής στην πραγματικότητα αναφέρεται από έναν pointer. Για παράδειγμα, θέλουμε να
θέσουμε τις ώρες στο 0 όταν συναντηθεί το 24:00:00, θα πρέπει να γράψουμε:
if ( (*t).hours == 24) (*t).hours
= 0;
Αυτή η
γραμμή κώδικα λέει στον compiler να πάρει
τη δ/νση του t (που
είναι η ώρα στη main( ) ) και
να της καταχωρήσει την τιμή 0 στο στοιχείο που λέγεται hours (Υπενθυμίζεται ότι η παρένθεση είναι απαραίτητη γύρω από
τον pointer (*t) γιατί ο τελεστής ( . ) βρίσκεται πιο ψηλά στην
ιεραρχία από τον pointer τελεστή *
(at adress) ).
Σήμερα
σπάνια συναντάμε αναφορές σε στοιχεία δομών, με την ακριβή χρήση του τελεστή *.
Επειδή η διαδικασία αυτή είναι συχνή, η Turbo C διαθέτει έναν ειδικό τελεστή για την εκτέλεσή της. Αυτός
είναι ο τελεστής -> (à), ή
αλλιώς arrow operator. Ο ->
χρησιμοποιείται στη θέση του ( . ) όταν προσπελαύνουμε στοιχεία δομών τα οποία
έχουν δοθεί σαν pointers στη δομή.
Π.χ.:
(* t).hours
είναι το ίδιο με
t -> hours
Έτσι
λοιπόν η συνάρτηση update( ) θα
μπορούσε να ξαναγραφτεί ως εξής:
update( struct tm *t)
{
t -> seconds ++;
if (t ->seconds ==60) {
t
-> seconds = 0;
t
-> minutes ++;
}
if (t->
minutes ==60) {
t->
minutes = 0;
t-> hours++;
}
if
(t-> hours ==24) t->hours = 0;
delay( );
}
Υπενθυμίζεται
ότι χρησιμοποιούμε τον dot
operator για να προσπελάσουμε στοιχεία δομών όταν ενεργούμε πάνω
στην ίδια τη δομή. Όταν όμως έχουμε έναν pointer σε μία δομή τότε θα πρέπει να χρησιμοποιήσουμε τον arrow operator. Π.χ.:
#include <stdio.h>
struct xyinput
{
int x, y;
char
message [80] ;
int i ;
} ;
void input_xy( ) ;
main( )
{
struct
xyinput mess;
mess.x
= 10;
mess.y
= 10;
strcpy (mess.message, “Enter An Integer : “);
clrscr(
);
input_+xy (&mess);
printf(“Your
Number Squared Is : %d”, mess.i * mess.i);
}
void input_xy
(struct xyinput *info)
{
gotoxy(info->x,
info->y);
printf(info->message);
scanf(“%d”,
&info->i);
}
Στο
παράδειγμα αυτό η συνάρτηση input_xy( ) μας επιτρέπει να ορίζουμε τις συντεταγμένες x και y στις
οποίες θα εμφανίζουμε ένα μήνυμα και ύστερα θα εισάγουμε μία ακέραια τιμή. Γι’
αυτό το σκοπό έχουμε δημιουργήσει τη δομή xyinput.
Το
πρόγραμμα αυτό χρησιμοποιεί τις συναρτήσεις clrscr( ) και gotoxy( ), οι
οποίες ανήκουν στις standard συναρτήσεις
βιβλιοθήκης.
vi)
Πίνακες
και Δομές μέσα σε Συναρτήσεις
Ένα
στοιχείο μίας δομής μπορεί να είναι σύνθετο ή απλό. Ένα απλό στοιχείο μπορεί να
είναι ένα στοιχείο οποιουδήποτε από τους ενσωματωμένους τύπους δεδομένων της Turbo C, όπως ακέραιος ή χαρακτήρας. Σύνθετα δεδομένα
μπορεί να είναι strings, πίνακες
ή δομές. Π.χ.:
struct x {
int a
[10] [10]; / * Πίνακας 10 x 10 */
float b;
}
Για να
αναφερθούμε στις θέσεις 3, 7 του πίνακα a της δομής y θα πρέπει
να γράψουμε: y.a [3] [7]
Όταν μία
δομή αποτελεί στοιχείο άλλης δομής, αυτό καλείται nested structure (φωλιασμένη δομή). Π.χ.:
struct emp {
struct
addr address;
float
wage;
} worker;
Εδώ το
στοιχείο address είναι
φωλιασμένη δομή μέσα στην emp. Φυσικά η
addr σαν δομή θα πρέπει να έχει
δηλωθεί προηγουμένως. Αν υποθέσουμε ότι έχει ως εξής:
struct addr
{
char name[30];
char street[40];
char
city[20];
char
state[2];
unsigned
long int zip;
}
struct emp {
struct
addr address;
float
wage;
} worker;
Η δομή emp περιλαμβάνει δύο στοιχεία το wage και τη δομή addr. Ο
παρακάτω κώδικας καταχωρεί $35.000 στο στοιχείο wage του worker και 12131
στο στοιχείο zip του address.
worker.wage = 35000;
worker.address.zip
= 12131;
Τα
στοιχεία αναφέρονται από το εσωτερικό και από αριστερά προς τα δεξιά.
IΙ. BIT FIELDS (ΔΥΑΔΙΚΑ ΠΕΔΙΑ)
Αντίθετα
με τις περισσότερες γλώσσες προγραμματισμού, η C διαθέτει ένα ενσωματωμένο τρόπο
προσπέλασης μεμονωμένων bit μέσα σε bytes. Αυτό αποδεικνύεται χρήσιμο για τρεις λόγους:
i)
Αν ο χώρος αποθήκευσης είναι περιορισμένος μπoρούμε να αποθηκεύσουμε αρκετές boolean (True – False)
μεταβλητές μέσα σε ένα byte.
ii)
Αρκετές συσκευές μεταφέρουν πληροφορίες
κωδικοποιημένες σαν bits μέσα σε bytes.
iii)
Οι περισσότερες ρουτίνες κρυπτογράφησης χρειάζονται
να προσπελάσουν bites μέσα σε bytes.
Παρ’ όλο
που όλα αυτά μπορούν να επιτευχθούν με τη χρήση bytes και δυαδικών τελεστών, ένα bit-field προσδίδει
μεγαλύτερη αποτελεσματικότητα στον κώδικά μας. Επίσης τον κάνει περισσότερο
μεταφέρσιμο.
Ο τρόπος
που χρησιμοποιεί η C για να προσπελάσει bits βασίζεται στη λειτουργία των δομών. Ένα δυαδικό πεδίο είναι
ένας ειδικός τύπος δομής ο οποίος ορίζει το μέγεθός του σε bits. Η γενική σύνταξη είναι:
struct struc_name
{
type
name1:length;
type
name2:length;
. .
. .
. .
type nameN:length;
}
Ένα
δυαδικό πεδίο μπορεί να είναι δηλωμένο σαν int, signed ή unsigned. Τα δυαδικά πεδία μεγέθους 1 bit πρέπει να δηλώνονται αναγκαστικά σαν unsigned γιατί ένα bit δε μπορεί να έχει πρόσημο. Π.χ.:
struct device
{
unsigned
active:1;
unsigned
ready:1;
unsigned
xmt_error:1;
} dev_code;
Η παραπάνω
δομή ορίζει τρεις μεταβλητές του 1 bit. Η structure variable dev_code θα χρησιμοποιηθεί για την αποκωδικοποίηση πληροφοριών που
θα παρθούν από κάποια θύρα. Αν υποθέσουμε ότι αυτή η θύρα αφορά ένα tape streamer, το παρακάτω τμήμα κώδικα γράφει
ένα byte πληροφοριών στο φανταστικό
tape drive και
παράλληλα ελέγχει για σφάλματα κάνοντας χρήση της dev_code από πάνω.
wr_tape (char c)
{
while (! dev_code.ready) rd (&dev_code); / *
Αναμονή * /
wr_to_tape( c ); / * Εγγραφή του Out
Byte * /
while (dev_code.active) rd
(&dev_code); / * Αναμονή για την εγγραφή */
if (dev_code.xmt_error0
printf(“Write Error…”);
}
Η δομή dev_code δεσμεύει
μόνο τα τρία χαμηλότερα bit ενός byte, ενώ τα υπόλοιπα μένουν αχρησιμοποίητα. Στον
παραπάνω κώδικα, η συνάρτηση rd( )
επιστρέφει την κατάσταση του streamer ενώ η wr_to_tape( ) γράφει τα δεδομένα. Όπως φαίνεται και στο
παράδειγμα, κάθε στοιχείο της δομής προσπελαύνεται με τον τελεστή dot( . ) . Πάντως, αν η δομή αναφέρεται μέσω ενός pointer μπορούμε να χρησιμοποιήσουμε τον
τελεστή -> .
Δεν είναι
υποχρεωτικό να ονομάσουμε κάθε bit field. Αυτό μας
επιτρέπει να φτάνουμε στο bit που
θέλουμε, προσπερνώντας τα αχρησιμοποίητα. Έτσι, ας υποθέσουμε για παράδειγμα,
ότι στην προηγούμενη άσκηση το tape drive
επιστρέφει, μία σημαία τέλους του streamer στο 5ο
bit, θα πρέπει να αλλάξουμε τη
δομή η οποία θα γίνει:
struct device
{
unsigned
active : 1;
unsigned
ready : 1;
unsigned
xmt_error : 1;
unsignded : 2;
unsigned
EOT :
1;
} dev_code;
Οι
μεταβλητές τύπου bit field περιέχουν
σοβαρές απαγορεύσεις. Δε μπορούμε να πάρουμε τη δ/νση μνήμης ενός bit field. Οι μεταβλητές τύπου bit field δε μπορούν να μπουν σε πίνακες.
Δεν επιτρέπεται να ξεπεράσουμε τα όρια ενός ακεραίου. Δε μπορούμε να ξέρουμε
από μηχάνημα σε μηχάνημα πότε τα πεδία λειτουργούν από δεξιά προς αριστερά και
πότε αντίστροφα. Πάντως επιτρέπεται να αναμίξουμε κανονικά στοιχεία δομών με bit fields. Π.χ.:
struct emp {
struct
addr address;
float
pay;
unsigned
lay_off : 1;
/ * Συνταξιούχος */
unsigned
hourly : 1; / * Ωρομίσθιος * /
} ;
Στη C μία ένωση είναι μία τοποθεσία στη μνήμη η οποία χρησιμοποιείται από πολλές μεταβλητές, οι οποίες μάλιστα μπορεί να είναι διαφορετικού τύπου. Η δήλωση μιας ένωσης είναι παρεμφερής με τη δήλωση μιας δομής. Π.χ.:
union union)type {
int i;
char ch;
} ;
Όπως και
στις δομές, έτσι και στις ενώσεις, δεν είναι απαραίτητη η δήλωση μεταβλητών.
Μπορούμε να δηλώσουμε μία μεταβλητή είτε τοποθετώντας το όνομά της στο τέλος,
είτε κάνοντας χρήση συνεχόμενων δηλώσεων. Αν θέλουμε να δηλώσουμε μια union variable του τύπου union_type
χρησιμοποιώντας τη δήλωση που μόλις δόθηκε, θα πρέπει να γράψουμε:
union union_type cnvt;
Στην ένωση
cnvt και ο ακέραιο i, και ο χαρακτήρας ch μοιράζονται την ίδια θέση μνήμης (φυσικά ο i δεσμεύει 2 bytes και ο ch μόνο 1).
Όταν μία
δομή δηλώνεται, ο compiler αυτόματα
δημιουργεί μία μεταβλητή αρκετά μεγάλη ώστε να χωρέσει τη μεγαλύτερη μεταβλητή
μέσα στην ένωση.
Η
προσπέλαση ενός στοιχείου ένωσης γίνεται με τον ίδιο τρόπο που γίνεται και στις
δομές. Αν λειτουργούμε απευθείας με μία δομή χρησιμοποιούμε τον τελεστή dot ( . ), ενώ αν αναφερόμαστε σε αυτή μέσω ενός pointer χρησιμοποιούμε τον arrow (->). Για παράδειγμα για να αποδώσουμε τον
ακέραιο 10 στο στοιχείο i του cnvt θα πρέπει να γράψουμε: cnvt.i = 10;
Κάνοντας
χρήση δομών μπορούμε να παράγουμε μεταφέρσιμο κώδικα. Κυρίως οι δομές
χρησιμοποιούνται όταν χρειάζεται να γίνει αλλαγή τύπου σε μία μεταβλητή. Για
παράδειγμα, η συνάρτηση βιβλιοθήκης της C putw( ), γράφει τη δυαδική αναπαράσταση μιας μεταβλητής σε ένα
αρχείο δίσκου. Αν και υπάρχουν αρκετοί τρόποι κωδικοποίησης αυτής της
συνάρτησης, αυτός που ακολουθεί στο παράδειγμα χρησιμοποιεί ένα union. Πρώτα δημιουργούμε ένα union που αποτελείται από έναν ακέραιο και από έναν πίνακα δύο
χαρακτήρων (bytes).
union pw {
int
i;
char
ch [z];
}
Τώρα η putw( ) κάνει χρήση της ένωσης.
putw(word, fp);
union pw word;
FILE *fp;
{
putc
(word -> ch [0]); / * Πρώτο Μισό * /
putc (word -> ch [1]); / * Δεύτερο
Μισό * /
}
Παρ’ όλο
που καλείται με έναν ακέραιο η putw( ) μπορεί
να χρησιμοποιεί τη συνάρτηση βιβλιοθήκης putc( ) για να κάνει εγγραφή ενός ακεραίου στο δίσκο. Επειδή η
συνάρτηση δέχεται ένα union, άνετα
μπορεί να γράψει τα δύο μισά ενός ακεραίου στο δίσκο.
ΙV. ENUMERATIONS (ΑΠΑΡΙΘΜΗΣΕΙΣ)
Απαριθμητής είναι ένα σύνολο από ακέραιες σταθερές οι οποίες ορίζουν όλες τις δυνατές τιμές που μπορεί να πάρει μία μεταβλητή αυτού του τύπου. Οι απαριθμήσεις είναι κοινές και στην καθημερινή ζωή. Για παράδειγμα, η απαρίθμηση των νομισμάτων που χρησιμοποιούνται στις ΗΠΑ είναι:
Penny,
Nickel, Dime, Quarter, Half_Dollar, Dollar
Οι απαριθμήσεις δηλώνονται όπως και οι δομές, χρησιμοποιώντας τη λέξη enum για να σηματοδοτήσουν την αρχή ενός απαριθμητή. Ο τρόπος σύνταξης είναι:
enum enum_type_name {enumeration list} var_list;
Και το enum_type_name και το var_list είναι προαιρετικά. Ο τύπος του απαριθμητή χρησιμοποιείται για να δηλώνουμε μεταβλητές αυτού του τύπου. Το παρακάτω τμήμα κώδικα ορίζει τον απαριθμητή coin και θέτει τη μεταβλητή money να είναι αυτού του τύπου:
enum
coin { penny, nickel, quarter,
half_dollar, dollar};
enum coin money;
Δεδομένης της ανωτέρω δήλωσης οι παρακάτω εντολές είναι αποδεκτές:
money
= dime;
if
(money == quarter) printf(“Is a Quarter \n”);
Το κλειδί για την κατανόηση των απαριθμητών είναι
ότι κάθε ένα από τα σύμβολα αντιστοιχεί σε μία ακέραια τιμή και μπορεί να
χρησιμοποιηθεί σε κάθε ακέραια έκφραση. Π.χ.:
printf(“The Number Of Nickels In a
Quarter Is %d”, quarter +2);
η ανωτέρω έκφραση είναι απόλυτα σωστή. Η τιμή του πρώτου συμβόλου της απαρίθμησης είναι 0, του δεύτερου 1, του τρίτου 2, κλπ. Έτσι η έκφραση:
printf (“%d %d”, penny, dime);
θα εμφανίσει 0 2 στην οθόνη.
Είναι δυνατό να καθορίσουμε την τιμή ενός ή περισσοτέρων
συμβόλων κάνοντας χρήση αρχικών τιμών. Αυτό επιτυγχάνεται τοποθετώντας ένα =
δίπλα από το σύμβολο, ακολουθούμενο από την επιθυμητή τιμή. Όταν δίνουμε αρχική
τιμή σε ένα από τα στοιχεία ενός απαριθμητή, τα στοιχεία που ακολουθούν έχουν
τιμή κατά ένα μεγαλύτερη. Π.χ.:
enum
coin { penny, nickel, dime, quarter =
100 , half_dollar } ;
Οι τιμές των στοιχείων θα είναι:
penny 0
nickel 1
dime 2
quarter 100
half_dollar 101
dollar 102
Υπενθυμίζεται ότι το σύμβολο dollar είναι απλά το όνομα ενός ακεραίου και όχι ένα string. Έτσι δε μπορούμε να εμφανίσουμε το string “dollar” μέσω μίας συνάρτησης εξόδου. Επίσης δε μπορούμε να δώσουμε τιμή τύπου string σε κάποιο από τα στοιχεία ενός απαριθμητή. Έτσι το παρακάτω τμήμα κώδικα είναι εσφαλμένο:
/ * ΛΑΘΟΣ ΚΩΔΙΚΑΣ */
money = dollar;
printf(“%s”, money);
Επίσης το παρακάτω παράδειγμα είναι εσφαλμένο:
/ * ΛΑΘΟΣ ΚΩΔΙΚΑΣ * /
money = “penny”;
Στην πραγματικότητα η δημιουργία κώδικα ο οποίος
εισάγει και εξάγει σύμβολα απαριθμητών είναι λιγάκι κουραστική. Έτσι ο παρακάτω
κώδικας εμφανίζει με λέξεις το είδος του νομίσματος που περιέχει η μεταβλητή money. Π.χ.:
Switch (money)
{
Case
penny : printf (“penny”);
:
break;
case nickel : printf (“nickel”);
:
break;
case dime : printf (“dime”);
:
break;
case
quarter : printf (“Quarter”);
:
break;
case
half_dollar : printf
(“Half_Dollar”);
:
break;
case
dollar : printf (“Dollar”);
}
Πολλές φορές είναι πιθανό να δηλώσουμε έναν πίνακα
από strings ναι να
χρησιμοποιήσουμε την τιμή του απαριθμητή σαν δείκτη για να μεταφράσουμε την
τιμή του απαριθμητή στο αντίστοιχο string. Έτσι το παρακάτω παράδειγμα κάνει την ίδια έξοδο με το
προηγούμενο switch. Π.χ.:
char
name [ ] = {
“penny”,
“nickel”,
“dime”,
“quarter”,
“half_dollar”,
“dollar”
};
Φυσικά αυτό το παράδειγμα λειτουργεί όταν δεν έχει αποδοθεί καμία αρχική τιμή στα στοιχεία του απαριθμητή, και αυτό γιατί ο πίνακας, εξ’ ορισμού, αρχίζει από το στοιχείο 0. το cast (int) μπροστά από το money χρησιμοποιείται για να αποσβέσει τα warnings, επειδή η μεταβλητή money τεχνικά δεν είναι ακέραια μεταβλητή, αλλά enumeration variable.
Εφόσον λοιπόν οι τιμές των απαριθμήσεων πρέπει να μετατρέπονται στις αντίστοιχες, αναγνώσιμες από τους ανθρώπους, τιμές τους με αυτοματοποιημένο τρόπο, για είσοδο – έξοδο κονσόλας, κυρίως χρησιμοποιούνται σε ρουτίνες που δεν είναι απαραίτητη τέτοια μετατροπή. Για παράδειγμα, ένα enumeration, συχνά χρησιμοποιείται για να ορίσει τον πίνακα συμβόλων ενός compiler.
V. USER DEFINED TYPES (ΤΥΠΟΙ ΟΡΙΖΟΜΕΝΟΙ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ)
Η Turbo C επιτρέπει στον προγραμματιστή να ορίσει νέα ονόματα τύπων κάνοντας χρήση της δεσμευμένης λέξης typedef. Δε δημιουργούνται νέοι τύποι δεδομένων, απλά ορίζονται νέα ονόματα για τους υπάρχοντες τύπους. Η διαδικασία αυτή βοηθά στη δημιουργία πιο μεταφέρσιμου κώδικα, εφόσον μόνο οι δηλώσεις typedef πρέπει να αλλαχθούν. Η γενική μορφή σύνταξης της typedef είναι:
typedef type name;
όπου type είναι κάθε επιτρεπτός τύπος δεδομένων και name είναι το νέο όνομα γι’ αυτόν τον τύπο. Το νέο όνομα το οποίο ορίζεται είναι μία προσθήκη και όχι αντικατάσταση του παλιού τύπου.
Για παράδειγμα μπορούμε να δημιουργήσουμε νέο όνομα για τον τύπο float ως εξής:
typedef float balance;
Η ανωτέρω δήλωση λέει στον compiler να αναγνωρίσει το balance σαν ένα άλλο όνομα για το float. Έτσι μπορούμε να δημιουργήσουμε μια float μεταβλητή:
balance over_due;
Η μεταβλητή είναι μία floating point μεταβλητή του τύπου balance όπου είναι ένα άλλο
όνομα για το float.
Μπορούμε επίσης να χρησιμοποιήσουμε την typedef για να ορίσουμε πιο σύνθετους τύπους. Π.χ.:
typedef struct
{
float
due;
int
over_due;
char
name [40];
}
client
client
list [NUM_CLIENTS];
#include <bios.h>
#include
<stdio.h>
#include<stdlib>
main(
)
{
struct
equip {
unsigned
Must_Boot_From_FDD : 1;
unsigned
Math_Co_80x87 : 1;
unsigned
Motherboard_RAM : 2;
unsigned
Initial_Video_Mode : 2;
unsigned
No_Of_FDDs : 2;
unsigned
DMA_Chip : 1;
unsigned
No_Of_Ser_Ports : 3;
unsigned
Joystic : 1;
unsigned
Serial_Printer : 1;
unsigned
No_of_Printers : 2;
}
;
union {
struct
equip status;
unsigned
eq;
} Mach_Equip;
Mach_Equip.eq
= biosequip( );
clrscr(
);
if
(Mach_Equip.status.Must_Boot_From_FDD);
printf(“Η εκκίνηση γίνεται από το drive \n”);
else
printf(“Η εκκίνηση γίνεται από το σκληρό \n”);
if (Mach_Equip.status.Math_Co_80x87)
pirntf(“Βρέθηκε Μαθηματικός Συνεπεξεργαστής \n”);
else
printf(“Δε Βρέθηκε Μαθηματικός Συνεπεξεργαστής \n”);
switch (Mach_Eqiup.status.Motherboard_RAM) {
case
0 :
printf
(“16 KB Οργάνωση \n”);
break;
case
1 :
printf
(“32 KB Οργάνωση \n”);
break;
case
2 :
printf
(“48 KB Οργάνωση \n”);
break;
case
3 :
printf (“64 KB Οργάνωση \n”);
}
switch (Mach_Equip.status.Initial_Video_Mode) {
case 0 :
/ * Η τιμή αυτή δε χρησιμοποιείται * /
break;
case
1 :
printf
(“ 40 x 25, Color Adapter”);
break;
case
2 :
printf
(“ 80 x 25, Color Adapter”);
break;
case
3 :
printf
(“80 x 25, Monocrome Adapter \n”);
}
printf(“ Ανιχνεύθηκαν %d Οδηγοί Δισκετών \ n”,
Mach_Equip.status.No_of_FDDs+1);
if
(Mach_Equip.status.DMA_chip)
printf (“Ανιχνεύθηκε DMA Chip \n”);
else
printf (“Δεν Ανιχνεύθηκε DMA Chip
\n”);
printf (“Ανιχνεύθηκαν %d Σειριακές Θύρες \n”, Mach_Equip.status.No_Of_Ser_Ports+1);
if
(Mach_Equip.status.Joystick)
printf (“Ανιχνεύθηκε Game Port
\n”);
if (Mach_Equip.status.Serial_Printer)
printf (“Ανιχνεύθηκε Σειριακός Εκτυπωτής \n”);
Printf (“ Ανιχνεύθηκαν %d Εκτυπωτές συνδεδεμένοι στο Σύστημα \n”, Mach_Equip.status.No_of_Printers+1);
} / * Τέλος Του Προγράμματος * /