ΠΕΡΙΕΧΟΜΕΝΑ

 

                        I.  ΔΟΜΕΣ (STRUCTURES)

                                    1. ΑΝΑΦΕΡΟΝΤΑΣ ΣΤΟΙΧΕΙΑ ΔΟΜΩΝ

                                    2. ΠΙΝΑΚΕΣ ΑΠΟ ΔΟΜΕΣ

                                    3. ΠΑΡΑΔΕΙΓΜΑ ΑΠΟΘΗΚΗΣ

                                    4. ΠΕΡΝΩΝΤΑΣ ΔΟΜΕΣ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ

                                                α. ΠΕΡΑΣΜΑ ΣΤΟΙΧΕΙΩΝ ΔΟΜΩΝ

                                                β. ΠΕΡΑΣΜΑ ΟΛΟΚΛΗΡΩΝ ΔΟΜΩΝ

                                    5. POINTERS ΣΕ ΔΟΜΕΣ

                                                α. ΔΗΛΩΣΗ STRUCTURE POINTER

                                                β. ΧΡΗΣΗ STRUCTURE POINTER

                                    6. ΠΙΝΑΚΕΣ ΚΑΙ ΔΟΜΕΣ ΜΕΣΑ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ

                        II.  ΔΥΑΔΙΚΑ ΠΕΔΙΑ (BITFIELDS)

                        III. ΕΝΩΣΕΙΣ (UNIONS)

                        IV. ΑΠΑΡΙΘΜΗΤΕΣ (ENUMERATORS)

                         V. ΤΥΠΟΙ ΟΡΙΖΟΜΕΝΟΙ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ (U.D.T. - USER DEFINED TYPES)

                          -  ΟΛΟΚΛΗΡΩΜΕΝΟ ΠΑΡΑΔΕΙΓΜΑ

 

 

 

ΚΕΦΑΛΑΙΟ 8ο

 

ΔΟΜΕΣ, ΕΝΩΣΕΙΣ, ΤΥΠΟΙ ΟΡΙΖΟΜΕΝΟΙ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ

ΚΑΙ ΑΠΑΡΙΘΜΗΤΕΣ

 

 

Ι.  STRUCTURES (ΔΟΜΕΣ)

 

Στη 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 είναι τα ονόματα μεταβλητών τοποθετημένα σε μία λίστα η οποία χωρίζεται με , .

 

 

i)                    Αναφέροντας Στοιχεία Δομών

 

Τα διάφορα στοιχεία των δομών αναφέρονται κάνοντας χρήση του 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]);

 

 

ii)                  Πίνακες από Δομές

 

Η πιο κοινή χρήση των δομών είναι σε πίνακες από δομές. Για να δηλώσουμε ένα array of structures, πρέπει πρώτα να δηλώσουμε τη δομή και μετά να δηλώσουμε έναν πίνακα αυτού του τύπου. Π.χ. για να δηλώσουμε έναν πίνακα 100 στοιχείων του τύπου addr, ο οποίος έχει δηλωθεί στην προηγούμενη παράγραφο, θα πρέπει να γράψουμε:

 

struct addr_info[100];

 

Αυτό δημιουργεί 100 σετ μεταβλητών οργανωμένων όπως είναι δηλωμένη η δομή adrr. Για την προσπέλαση του συγκεκριμένου στοιχείου του πίνακα (μιας συγκεκριμένης δομής μέσα στον πίνακα) θα πρέπει να κάνουμε χρήση δεικτών με το όνομα της δομής. Π.χ. για να εμφανίσουμε τον ταχυδρομικό κώδικα της 3ης δομής θα γράψουμε:

 

         printf( “%d”, addr_info[2].zip);

 

Όπως σε όλες τις μεταβλητές με δείκτες (arrays) σαν πρώτο στοιχείο θεωρείται το 0.

 

 

 

iii)                Παράδειγμα Αποθήκης

                  

Για την κατανόηση των δομών και των πινάκων από δομές, θα αναλύσουμε ένα απλό πρόγραμμα, το οποίο χρησιμοποιεί ένα 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);

}

 

Η μέθοδος αυτή όχι μόνο γλιτώνει τον προγραμματιστή από περιττές έγνοιες αλλά τον βοηθά να σιγουρέψει ότι τα ορίσματα ταιριάζουν με τις παραμέτρους. Εκτός αυτών φυσικά μειώνει και τον κώδικα του προγράμματος.

 

 

 

v)                 Pointers σε Δομές

 

Η 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 (TrueFalse) μεταβλητές μέσα σε ένα 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;    /  * Ωρομίσθιος   * /

}  ;

 


IIΙ.  UNIONS (ΕΝΩΣΕΙΣ)

 

Στη 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. Έτσι δε μπορούμε να εμφανίσουμε το stringdollar” μέσω μίας συνάρτησης εξόδου. Επίσης δε μπορούμε να δώσουμε τιμή τύπου 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];

 

 


d     ΟΛΟΚΛΗΡΩΜΕΝΟ ΠΑΡΑΔΕΙΓΜΑ    c

 

#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);

 

}  /  *  Τέλος Του Προγράμματος  * /