ΠΕΡΙΕΧΟΜΕΝΑ

 

i.   ΜΟΝΟΔΙΑΣΤΑΤΟΙ ΠΙΝΑΚΕΣ

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

iii.  STRINGS

iv. ΔΥΣΔΙΑΣΤΑΤΟΙ ΠΙΝΑΚΕΣ

v.  ΠΙΝΑΚΕΣ ΑΠΟ STRINGS

vi. ΠΟΛΥΔΙΑΣΤΑΤΟΙ ΠΙΝΑΚΕΣ

vii. ΠΙΝΑΚΕΣ ΚΑΙ POINTERS

viii. ALLOCATED ARRAYS

ix.  ΑΡΧΙΚΟΠΟΙΗΣΗ ΠΙΝΑΚΩΝ

x.  ΑΡΧΙΚΟΠΟΙΗΣΗ ΜΗ ΚΑΘΟΡΙΣΜΕΝΩΝ ΠΙΝΑΚΩΝ

xi. ΟΛΟΚΛΗΡΩΜΕΝΟ ΠΑΡΑΔΕΙΓΜΑ (ΤΡΙΛΙΖΑ)

 

 

 

 

ΚΕΦΑΛΑΙΟ 6ο

 

ΠΙΝΑΚΕΣ – ARRAYS

 

            Ένας πίνακας είναι μία συλλογή από μεταβλητές του ίδιου τύπου, οι οποίες αναφέρονται με το ίδιο όνομα. Ένα συγκεκριμένο στοιχείο στον πίνακα προσπελαύνεται από έναν δείκτη. Στην C όλοι οι πίνακες αποτελούνται από συνεχόμενες θέσεις μνήμης. Η χαμηλότερη διεύθυνση αναφέρεται στο πρώτο στοιχείο,  ενώ η μεγαλύτερη διεύθυνση ανταποκρίνεται στο τελευταίο στοιχείο. Οι πίνακες μπορούν να έχουν από μία ως πολλές διαστάσεις.

 

 

i)                    Μονοδιάστατοι Πίνακες:

 

Η γενική μορφή ενός μονοδιάστατου πίνακα είναι:

 

type var_name [size];

 

Στην C οι πίνακες πρέπει να δηλώνονται ακριβώς έτσι ώστε ο compiler να μπορεί να δεσμεύσει χώρο γι’ αυτούς στην μνήμη. Εδώ το type δηλώνει τον βασικό τύπο του πίνακα, που είναι και ο τύπος κάθε στοιχείου του πίνακα. Το size δηλώνει πόσα στοιχεία θα δεχτεί ο πίνακας. Για έναν μονοδιάστατο πίνακα, η συνολική χωρητικότητα ενός πίνακα σε bytes υπολογίζεται ως εξής:

 

total bytes = sizeof (type) * length of array

 

Όλοι οι πίνακες ξεκινούν από το 0 για το πρώτο στοιχείο τους. Έτσι αν γράψουμε char p [10];, δηλώνουμε έναν πίνακα χαρακτήρων 10 στοιχείων, όπου p[0] είναι το πρώτο και p[9] το τελευταίο.

 

Π.χ.:            main( )

                   {

                     int x [10]

                     int t;

                     for( t=0; t<10; ++t)   x [t] = t;

                   }

 

            Το παραπάνω παράδειγμα «φορτώνει» έναν πίνακα ακεραίων με τους αριθμούς από το 0 ως το 9.

 

            Στην C δε γίνεται έλεγχος ορίων στους πίνακες. Μπορούμε να γράψουμε ακόμη και μετά το τέλος του πίνακα, γράφοντας έτσι στα δεδομένα μιας άλλης μεταβλητής ή ακόμη και στον ίδιο τον κώδικα του προγράμματος. Είναι δουλειά του προγραμματιστή να εκτελέσει έλεγχο των ορίων, όταν χρειάζεται. Για παράδειγμα, σιγουρεύεται ότι οι πίνακες χαρακτήρων, που θα εισαχθούν με τη χρήση της gets( ), είναι αρκετά μεγάλοι ώστε να χωρέσουν τα δεδομένα που θα τους καταχωρήσουμε.

 

            Οι μονοδιάστατοι πίνακες ουσιαστικά είναι λίστες πληροφοριών του ίδιου τύπου.

 

Π.χ.:     Το παρακάτω σχήμα δείχνει πως απεικονίζεται ο πίνακας a στη μνήμη, αν έχει δηλωθεί ως εξής:

 

char a [7]

και κάνοντας την αποδοχή ότι ξεκινά από τη θέση μνήμης 1000.

 

Στοιχείο Πίνακα

0

1

2

3

4

5

6

Διεύθυνση Μνήμης

1000

1001

1002

1003

1004

1005

1006

 

            Έτσι απεικονίζεται στη μνήμη ένας μονοδιάστατος πίνακας επτά στοιχείων (από 0 ως 6).

 

 

i)                    Περνώντας Μονοδιάστατους Πίνακες σε Συναρτήσεις

 

Όταν περνάμε μονοδιάστατους πίνακες σε συναρτήσεις, καλούμε τη συνάρτηση με το όνομα του πίνακα χωρίς δείκτες. Αυτό περνά τη διεύθυνση του πρώτου στοιχείου του πίνακα στη συνάρτηση. Στη C δεν είναι δυνατό να περάσουμε ολόκληρο τον πίνακα σαν όρισμα. Αντί γι’ αυτό χρησιμοποιούμε έναν pointer.

 

Π.χ.:     Το παρακάτω τμήμα κώδικα περνά τη διεύθυνση του i στην func1( ) :

 

                        main( )

                        {

                          int i [10];

                          func1(i);

                       

                        }

 

            Αν μία συνάρτηση δέχεται μονοδιάστατο πίνακα, θα πρέπει η τυπική παράμετρος (formal parameter) να έχει δηλωθεί σαν pointer, σαν οριοθετημένος (sized) πίνακας ή μη οριοθετημένος (unsized) πίνακας.

 

Π.χ.:    Για να περάσουμε το ii στη συνάρτηση func1( ), θα πρέπει να ορίσουμε τη συνάρτηση ως εξής:

 

                        func1(str)

                        int *str;                         / * pointer * /

                        {

                        ……

                        }

                                                            ή

                        func1(str)

                        int str [10];                   / * sized array * /

                        {

                        ……

                        }

                                                            ή

                        func1(str)

                        int str[ ] ;                      / * unsized array * /

                        {

                        ……

                        }

 

            Όλες οι μέθοδοι δήλωσης είναι σωστές, επειδή κάθε μία από αυτές λέει στον compiler, ότι ένας character pointer πρόκειται να περαστεί. Στην πρώτη δήλωση χρησιμοποιείται ένας pointer, στη δεύτερη ορίζεται μία τυπική δήλωση πίνακα, ενώ στην τρίτη έχουμε μία τροποποιημένη δήλωση πίνακα, η οποία απλά ορίζει ότι θα περαστεί ένας πίνακας τύπου int αγνώστου μήκους.

 

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

 

                        func1(str)

                        int str[32] ;                   / * unsized array * /

                        {

                        ……

                        }

           

            Και αυτό θα δούλευε, επειδή ο compiler δημιουργεί κώδικα ο οποίος καθοδηγεί την func1( ) να δεχτεί έναν pointer. Βλέπουμε λοιπόν ότι στην πραγματικότητα δε δημιουργείται ένας πίνακας 32 θέσεων και δε δεσμεύεται χώρος γι’ αυτόν.

 

 

ii)                  Strings:

 

Ως τώρα η πιο κοινή χρήση των μονοδιάστατων πινάκων ήταν στα strings. Στην C ένα string έχει δηλωθεί  να αποτελεί έναν character array, ο οποίος τερματίζεται με ένα null. Το null ορίζεται από το ‘\0’ και γενικά είναι 0. Γι’ αυτό το λόγο είναι απαραίτητο να δηλώνουμε τους πίνακες χαρακτήρων να είναι ένα χαρακτήρα μεγαλύτεροι από το μεγαλύτερο string που πρόκειται να αποθηκεύσουν.

 

Παρ’ όλο που η C δε διαθέτει ένα δεσμευμένο τύπο string, επιτρέπει σταθερές τύπου string.  Μία σταθερά string είναι μία αλληλουχία χαρακτήρων κλεισμένων μέσα σε “ “ ( π.χ.: “Hello There”). Δεν είναι απαραίτητο να προσθέσουμε το null στο τέλος του string. Στην περίπτωση των σταθερών η C θα το κάνει αυτό αυτόματα.

 

Η Turbo C υποστηρίζει αρκετές συναρτήσεις διαχείρισης string. Οι πιο κοινές είναι:

 

strcpy( s1, s2)             Αντιγράφει το s2 στο s1.

strcat( s1, s2)              Ενώνει το s2 στο τέλος του s1.

strlen( s1)                    Επιστρέφει το μέγεθος του s1.

strcmp(s1, s2)            Επιστρέφει 0, <0, >0, ανάλογα με το αποτέλεσμα της σύγκρισης των δύο strings.

 

(Οι συναρτήσεις αυτές μαζί με άλλες έχουν ήδη αναλυθεί λεπτομερώς σε προηγούμενο κεφάλαιο)

 

Π.χ.:                main( )

                        {

                        char s1 [80], s2 [80];

                        gets(s1);   gets (s2);

                        printf(“Lengths: %d %d \n”, strlen(s1), strlen(s2));

                        if( !strcmp (s1, s2) )   printf( “The strings are equal \ n”);

                        strcat (s1, s2);

                        printf(“%s “, s1);

                        }

 


iii)                Δυσδιάστατοι Πίνακες:

 

Η Turbo C επιτρέπει πολυδιάστατους πίνακες. Η πιο απλή μορφή πολυδιάστατου πίνακα είναι ο πίνακας δύο διαστάσεων. Σε γενικές γραμμές ένας δισδιάστατος πίνακας είναι ένας πίνακας από μονοδιάστατους πίνακες. Η γενική μορφή δήλωσης δυσδιάστατων πινάκων είναι:

 

type array_name [2nd dimension size] [1st dimension size]

 

Έτσι για να δηλώσουμε έναν δυσδιάστατο πίνακα από ακέραιους, θα πρέπει να γράψουμε:

 

int pinakas [10] [20];

 

Παρατηρούμε ότι αντίθετα από άλλες γλώσσες προγραμματισμού οι οποίες χρησιμοποιούν το κόμμα για να διαχωρίσουν τις διαστάσεις των πινάκων, η C τοποθετεί κάθε διάσταση στις δικές της αγκύλες.

 

Έτσι για να προσπελάσουμε το στοιχείο (3, 5) του πίνακα, θα πρέπει να γράψουμε:

 

pinakas [3] [5]

 

Στο παρακάτω παράδειγμα ένας δυσδιάστατος πίνακας αρχικοποιείται με τους αριθμούς 1 ως 12.

 

Π.χ.:                main( )

                        {

                        int t, i,  num [3] [4];

                        for (t=0; t<3; ++t)

                               for (i=0; i<4; ++i)

                                                num[t] [i] = (t * 4) + i + 1;

                        }

 

            Σ’ αυτό το παράδειγμα το στοιχείο num[0] [0] έχει την τιμή 1, το num[0] [1] την τιμή2, το num[0] [2] την τιμή 3, κλπ. Το στοιχείο num[2] [3] έχει την τιμή 12.

 

            Οι δισδιάστατοι πίνακες αποθηκεύονται σε μία μήτρα με στήλες και γραμμές, όπου ο πρώτος δείκτης αναφέρεται στη γραμμή και ο δεύτερος στη στήλη. Αυτό σημαίνει, ότι οι δεξιοί δείκτες αλλάζουν πολύ πιο γρήγορα απ’ ότι οι αριστεροί, κατά τη διάρκεια της προσπέλασης των στοιχείων ενός πίνακα. Θα μπορούσαμε να φανταστούμε τον αριστερό δείκτη σαν έναν pointer στη σωστή γραμμή.

 

            Υπενθυμίζεται ότι η μνήμη στην περίπτωση δήλωσης πινάκων, δεσμεύεται μόνιμα. Στην περίπτωση δυσδιάστατων πινάκων ο παρακάτω τύπος χρησιμοποιείται για τον υπολογισμό του αριθμού των bytes μνήμης που θα δεσμευτούν:

 

bytes = row * column * number of bytes in data type

 

 

 

Δεύτερος Δείκτης

Π

ρ

ώ

τ

ο

ς

0, 0

0, 1

0, 2

0, 3

0, 4

0, 5

0, 6

0, 7

1, 0

1, 1

1, 2

1, 3

1, 4

1, 5

1, 6

1, 7

2, 0

2, 1

2, 2

2, 3

2, 4

2, 5

2, 6

2, 7

3, 0

3, 1

3, 2

3, 3

3, 4

3, 5

3, 6

3, 7

4, 0

4, 1

4, 2

4, 3

4, 4

4, 5

4, 6

4, 7


Απεικόνιση ενός δυσδιάστατου πίνακα στη μνήμη.

 

Με βάση τα παραπάνω, αν υποθέσουμε ότι έχουμε έναν πίνακα ακεραίων με διαστάσεις 10 x 5, τότε αυτός θα δεσμεύει 10 x 5 x 2 = 100 bytes μνήμης.

 

Όταν επιχειρούμε να «περάσουμε έναν πίνακα σαν όρισμα σε μία συνάρτηση, μόνο ένας pointer  στο πρώτο στοιχείο ( [0] [0] ) ουσιαστικά περνάει. Πάντως μία συνάρτηση η οποία δέχεται έναν δισδιάστατο πίνακα, θα πρέπει να γνωρίζει τουλάχιστον το μήκος της πρώτης διάστασης, γιατί ο compiler χρειάζεται να γνωρίζει το μέγεθος της κάθε γραμμής, αν είναι να αριθμήσει τον πίνακα σωστά. Για παράδειγμα, μία συνάρτηση, η οποία θα δεχτεί έναν δισδιάστατο πίνακα με διαστάσεις 10, 10, θα πρέπει να έχει δηλωθεί ως εξής:

 

              func1 (x)

              int x [ ] [10];

              {

             

              }

 

Φυσικά θα μπορούσαμε να είχαμε δηλώσει και τη δεύτερη διάσταση αλλά αυτό δεν είναι απαραίτητο. Ο compiler χρειάζεται να γνωρίζει την πρώτη διάσταση, έτσι ώστε να μπορεί να εργαστεί με δηλώσεις όπως: x [2] [4], μέσα σε μία συνάρτηση. Αν το μέγεθος των γραμμών ήταν άγνωστο, τότε ο compiler δε θα μπορούσε να βρει που αρχίζει η τρίτη γραμμή.

 

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


Π.χ.:        #include<stdio.h>

               #define CLASSES 3

               #define GRADES 30

               int grade [CLASSES] [GRADES];

               main( )

               {

                  char ch ;

                  for( ;; )   {

                        do  {

                              printf(“ (E)nter grades\n”);

                              printf(“ (R)eport grades\n”);

                              printf (“ (Q)uit \n”);

                              ch=toupper(getche( )) ;

                        }   while(ch != ‘E’  && ch != ‘R’  && ch!= ‘Q’);

                        switch (ch)  {

                                    case ‘E’ :

                                          enter_grades( );

                                          break;

                                    case ‘R’ :

                                          disp_grades(grade);

                                          break ;

                                    case ‘Q’ :

                                          exit (0) ;

                        }

                  }

               }

               enter_grades ( )

               {

                  int t, i ;

                  for (t=0 ; t<CLASSES ;t++)   {

                     printf(“Class  #%d:\n”, t+1);

                     for(i=0; i<GRADES; ++i);

                           grade[t] [i] = get_grade(i);

                  }

               }

               get_grade (int_num)

               {

                  char s[80];

                  printf (“Enter grade for student #%d:\n”, num+1);

                  gets(s);

                  return (atoi(s));

               }

               disp_grades(int g[ ] [GRADES])

               {

                  int t, i;

                  for(t=0 ; t<CLASSES ; ++t)   {

                     printf (“Class #%d:\n”, t+1);

                     for(i=0; i<GRADES; ++i)

                     printf(“grade for student #%d is %d\n”, i+1, g[t] [i]);

                  }

               }

 

 

 

 

iv)               Πίνακες από Strings:

 

Γενικά στον προγραμματισμό είναι ασυνήθιστο να χρησιμοποιούμε πίνακες από strings. Για να δημιουργήσουμε έναν πίνακα από strings θα πρέπει να χρησιμοποιήσουμε έναν δισδιάστατο πίνακα χαρακτήρων, όπου ο αριστερός δείκτης του καθορίζει τον αριθμό των strings και ο δεξιός δείκτης το μήκος του κάθε string. Για παράδειγμα, το παρακάτω ορίζει έναν πίνακα από 30 strings, όπου το καθένα έχει μέγιστο 80 χαρακτήρες:

 

char str_array[30] [80];

 

Η προσπέλαση ενός τέτοιου πίνακα είναι σχετικά απλή. Απλά αναφέρουμε μόνο τον αριστερό δείκτη:

 

gets(str_array[2]);

 

Δίνει τιμή στο 3ο string του πίνακα. Θα μπορούσαμε επίσης να γράψουμε:

 

gets(&str_array[2][0]);

 

Αλλά η προηγούμενη μορφή είναι πολύ πιο συνηθισμένη σε επαγγελματικά προγράμματα C.

 

Π.χ.:     #include<stdio.h>

            #define MAX 100

            #define LEN 80

            char text [MAX] [LEN];

            main( )

            {

               register int t, i, j;

               for (t=0; t<MAX; t++)   {

                     printf( “%d:”, t);

                     gets (text [t]);

                     if ( !*text[t]) break;

               }

               for (i=0; i<t; i++)  {

                     for(j=0; text[ i ] [ j ]; j++)  printf (“%c”, text [ i ] [ j ]);

                     printf(“%c”, ‘\n’);

               }

            }

 

            Το πρόγραμμα αυτό εισάγει γραμμές κειμένου έως ότου μία κενή γραμμή εισαχθεί. Τότε επανεμφανίζει κάθε γραμμή.

 

 

v)                 Πολυδιάστατοι Πίνακες:

 

Η Turbo C επιτρέπει επίσης πίνακες περισσοτέρων των δύο διαστάσεων. Η γενική φόρμα σύνταξης τέτοιων πινάκων είναι:

 

type name [size1] [size2]… [sizeN];

 

            Πίνακες με περισσότερες από τρεις διαστάσεις χρησιμοποιούνται σπάνια και αυτό εξαιτίας της μνήμης που απαιτεί η καταχώρησή τους. Όπως αναφέρθηκε πιο πριν η μνήμη για τους global πίνακες δεσμεύεται κατά τη δήλωσή τους. Για παράδειγμα ένας πίνακας τεσσάρων διαστάσεων 10, 6, 9, 4 απαιτεί 10x6x9x4=2160 bytes. Αν αυτός είναι πίνακας ακεραίων, τότε δεσμεύει 2160x2=4320 bytes. Αν είναι πίνακας double, δεσμεύει 2160x8=34560 bytes (κάθε double απαιτεί 8 bytes). Ο χώρος που απαιτείται αυξάνεται παράλληλα με τις διαστάσεις του πίνακα.

 

            Σε γενικές γραμμές θα πρέπει να θυμόμαστε ότι ο υπολογιστής απαιτεί χρόνο για να υπολογίσει τον κάθε δείκτη. Αυτό σημαίνει ότι η προσπέλαση ενός πολυδιάστατου πίνακα είναι πολύ πιο χρονοβόρα από αυτή ενός μονοδιάστατου. Γι’ αυτό (και για άλλους βέβαια λόγους), όταν χρειάζεται να χρησιμοποιήσουμε μεγάλους πολυδιάστατους πίνακες, δυναμικά, δεσμεύουμε ένα τμήμα της μνήμης κάθε φορά, χρησιμοποιώντας pointers ή τις συναρτήσεις δυναμικής δέσμευσης που προσφέρει η C. Αυτή η προσέγγιση ονομάζεται SPARSE ARRAY.

 

            Όταν περνάμε πολυδιάστατους πίνακες σε συναρτήσεις, είμαστε υποχρεωμένοι να δηλώσουμε όλες τις διαστάσεις εκτός από την αριστερή.

 

Π.χ.:     Αν ο πίνακας m έχει δηλωθεί ως εξής:

 

int m[4] [3] [6] [5];

 

            Η συνάρτηση func1( ), που τον χρησιμοποιεί, θα έχει την εξής μορφή:

 

                           func1(d)

                           int d[ ] [3] [6] [5];

                           {

                          

                           }

 

            Φυσικά μπορούμε να δηλώσουμε και την αριστερή διάσταση, αν θέλουμε.

 

 

vi)               Πίνακες και Pointers:

 

Στη C οι πίνακες και οι pointers σχετίζονται στενά. Για παράδειγμα, το όνομα ενός πίνακα χωρίς δείκτες είναι ένας pointer στο πρώτο στοιχείο του πίνακα.

            Π.χ.:     char p[10];

 

            Οι δύο παρακάτω δηλώσεις είναι ισοδύναμες:

 

p          και       &p[0]

 

ή με άλλα λόγια η σχέση p==&p[0] θα δώσει αληθές, επειδή η διεύθυνση μνήμης του πρώτου στοιχείου ενός πίνακα είναι το ίδιο με τη διεύθυνση του πίνακα.

 

            Κάθε μεταβλητή τύπου pointer μπορεί να κάνει χρήση δεικτών, σα να είχε δηλωθεί σαν πίνακας.

 

Π.χ.:     int  *p, i[10];

            p=i;

            p[5] = 100;      /* Απόδοση με χρήση index */

            (*p+5) = 100;  /* Απόδοση με χρήση αριθμητικής pointer*/

 

            Στο παραπάνω παράδειγμα και οι δύο εντολές τοποθετούν την τιμή 100 στο έκτο στοιχείο του i. Η πρώτη εντολή αποδίδει δείκτες στον pointer p. Πάντως και στις δύο περιπτώσεις το αποτέλεσμα είναι ίδιο (η αριθμητική των pointers εξετάζεται λεπτομερώς στο αντίστοιχο κεφάλαιο).

 

            Το ίδιο ισχύει και στους πίνακες δύο ή περισσότερων διαστάσεων.

 

Π.χ.:     a          και      &a[0][0]           είναι ισοδύναμα.

 

Με αυτή τη λογική μπορούμε να αναφερθούμε στο στοιχείο 0, 4 του πίνακα a, είτε χρησιμοποιώντας δείκτες πινάκων, a [0] [4], είτε κάνοντας χρήση pointers  *(a+4). Παρομοίως το στοιχείο 1, 2 είναι το a[1][2] ή το *(a+12).

 

Στους δυσδιάστατους πίνακες ισχύει ο παρακάτω τύπος:

 

a[ j] [k] is equivalent to (a+(j* row length) +k)

 

Στο προηγούμενο παράδειγμα υποθέτουμε ότι ο πίνακας είναι 10, 10, άρα a[1] [2] είναι ίσο με:

 

*(a+(1*10)+2) = * (a+10+2) = * (a+12)

 

            Οι pointers χρησιμοποιούνται πολλές φορές για την προσπέλαση πινάκων, επειδή η αριθμητική των pointers είναι μία διαδικασία κατά τι πιο γρήγορη από τους δείκτες των πινάκων. Το κύριο όφελος από τη χρήση pointers στην προσπέλαση πινάκων το έχουμε όταν προσπελαύνουμε σειριακά τον πίνακα. Σ’ αυτή την περίπτωση ο pointer μπορεί να αυξηθεί ή να μειωθεί χρησιμοποιώντας τους υψηλής αποδοτικότητας τελεστές αύξησης και μείωσης της Turbo C. Σε περίπτωση πάντως που ο πίνακας πρέπει να προσπελαστεί τυχαία, δεν υπάρχει σχεδόν καμία διαφορά από τη χρήση array-indexing.

 

            Με μία άλλη λογική οι δυσδιάστατοι πίνακες μοιάζουν με πίνακες από pointers που δείχνουν σε πίνακες γραμμών. Έτσι ένας τρόπος για να χρησιμοποιήσουμε pointers στην προσπέλαση δισδιάστατων πινάκων είναι κάνοντας χρήση διαχωριζομένων μεταβλητών pointer. Η παρακάτω συνάρτηση εμφανίζει τα περιεχόμενα των οριζόμενων γραμμών της global ακεραίας μεταβλητής (πίνακα) num:

 

int num [10] [10];

pr_row(int j)

{

            int *p, t;

            p=&num [ j ] [ 0 ];  /* Δνση του 1ου στοιχείου στη γραμμή j  */

            for(t=0; t<row_dimension; ++t)

                        printf(“%d”, *(p+t)); 

 }

 

            Μπορούμε να σκεφτούμε με την ίδια λογική και για πίνακες περισσοτέρων των δύο διαστάσεων. Π.χ. ένας τρισδιάστατος πίνακας μπορεί να μειωθεί σε έναν πίνακα από pointers που δείχνουν σε δισδιάστατους πίνακες, οι οποίοι μπορούν να μειωθούν σε πίνακες από pointers που δείχνουν σε πίνακες μίας διάστασης. Γενικά ένας πίνακας μπορεί να μειωθεί σε έναν pointer από (n-1) διαστάσεων πίνακες. Ο νέος αυτός πίνακας (ο n-1) μπορεί κι αυτός να αναλυθεί με την ίδια λογική. Η διαδικασία τερματίζεται όταν η ανάλυση καταλήξει σε έναν μονοδιάστατο πίνακα.

 

 

vii)             Allocated Arrays

 

Σε αρκετές προγραμματιστικές περιπτώσεις, είναι αδύνατο να γνωρίζουμε πόσο μεγάλος πρέπει να είναι ένας πίνακας. Επίσης, πολλά προγράμματα πρέπει να χρησιμοποιούν όσο περισσότερη μνήμη γίνεται ακόμη και σε μηχανήματα με πολύ λίγη μνήμη. Ένα τέτοιο παράδειγμα είναι ένας text editor. Σε αυτή την περίπτωση δεν είναι δυνατό να κάνουμε χρήση πίνακα επειδή οι διαστάσεις του ορίζονται την ώρα της μετάφρασης και δεν μπορούν να αλλάξουν την ώρα της εκτέλεσης. Η λύση είναι δημιουργία δυναμικών πινάκων. Ένας δυναμικός πίνακας χρησιμοποιεί το τμήμα της ελεύθερης μνήμης που λέγεται heap  και προσπελαύνεται με τη χρήση pointers σ’ αυτή τη μνήμη (υπενθυμίζουμε ότι σε κάθε pointer μπορούμε να χρησιμοποιήσουμε δείκτες σα να ήταν πίνακες).

 

Στην Turbo C μπορούμε δυναμικά να δεσμεύσουμε και να αποδεσμεύσουμε μνήμη χρησιμοποιώντας τις συναρτήσεις βιβλιοθήκης, όπως η malloc( ) (δεσμεύει μνήμη και επιστρέφει έναν void pointer στην αρχή της) ή την free( ) (επιστρέφει τη μνήμη που είχε προηγουμένως δεσμευτεί στο heap).

 

Η γενική μορφή των δύο αυτών συναρτήσεων είναι:

 

         void *p;

         p=malloc(num_bytes);

         free(p);

 

Στο παράδειγμα αυτό η μεταβλητή num_bytes είναι ο αριθμός των απαιτούμενων bytes. Αν δεν υπάρχει αρκετή ελεύθερη μνήμη για την ικανοποίηση της απαίτησης, η malloc( ), θα επιστρέψει null. Σημειώνεται ότι η free( ) καλείται μόνο με μία αποδεκτή (προηγούμενα δεσμευμένη) μεταβλητή pointer, αλλιώς η οργάνωση του heap ενδέχεται να υποστεί σοβαρή ζημιά, η οποία πιθανώς θα προκαλέσει κατάρρευση του προγράμματος ή ακόμη και του συστήματος.

 

Επίσης (τεχνικά δεν απαιτείται) είναι καλό να γίνεται χρήση του header file <stdlib.h> σε κάποιο πρόγραμμα που κάνει χρήση των προηγούμενων συναρτήσεων. Π.χ.:

 

            char *p;

            p=(char *) malloc(1000);

 

Το τμήμα κώδικα αυτό, δεσμεύει 1000 bytes μνήμης. Το p θα δείχνει στο πρώτο από τα 1000 bytes ελεύθερης μνήμης. Παρατηρούμε ότι ένα cast χρησιμοποιείται για να καλυφθεί ο void pointer που επιστρέφει η malloc( ) στον απαιτούμενο char pointer. Γενικά, χρησιμοποιούμε ένα cast για να μετατρέπουμε τον pointer που επιστρέφει η malloc( ) στον τύπο που απαιτούμε εμείς. Το παρακάτω παράδειγμα δείχνει τον τρόπο χρήσης ενός δυναμικά δεσμευμένου πίνακα.

 

      #include <stdlib.h>

      main( )

      {

            char *s;

            register int t;

            s=(char *) malloc (80);

            if (!s)     {

                  printf ( “Memmory Request Failed\n”);

                  exit(1);

            }

            gets(s);

            for (t=strlen(s)-1; t>=0; t--)

                  printf(“%c”. s[t]);

            free(s);

      }

 

Όπως δείχνει το πρόγραμμα ο s ελέγχεται πριν τη χρήση του (υπενθυμίζουμε ότι ο null pointer είναι false à0 και κάθε άλλη pointer τιμή true à 1), έτσι σιγουρευόμαστε ότι δε θα γίνει ατυχής χρήση ενός null pointer (η χρήση ενός null pointer σίγουρα θα επιφέρει κατάρρευση του συστήματος). Παρατηρήστε πως γίνεται χρήση δεικτών με τον pointer s σα να ήταν πίνακας.

 

Είναι δυνατή η χρήση πολυδιάστατων δυναμικών πινάκων αλλά σίγουρα θα χρειαστεί να γίνει χρήση μίας συνάρτησης για την προσπέλασή τους, επειδή με κάποιο τρόπο θα πρέπει να έχουν δηλωθεί όλες οι διαστάσεις του πίνακα εκτός από την αριστερή. Για να επιτευχθεί αυτό ένας pointer περνά στην συνάρτηση όπου δηλώνει στις παραμέτρους της τα απαιτούμενα όρια του πίνακα.

 

Π.χ.:

         #include <stdlib.h>

         main( )

         {

                     int *p;

                     p=(int ) malloc (40*sizeof(int));

                     if (!p)     {

                           printf(“Memmory Request Falied\n”);

                           exit(1);

                     }

                     table(p);

                     show(p);

         }

         table  (int p[4] [10])

         {

                     register int i, j;

                     for (j=1; j<11; j++)

                           for (i=1; i<5; i++)

                                 p[ i ] [ j ] = pwr(i, j);

         }

         show(int p[4] [10])

         {

                     register int i, j;

                     printf(“%10s  %10s  %10s  %10s\n”, “N”, “N^2”, “N^3”, “N^4”);

                     for ( j=1; j<11; j++)   {

                           for( i=1;  i<5;  i++)   printf(“%10d”, p[ i ] [ j ]);

                           printf(“\n”);

                     }

         }

         pwr(int a, int b)

         {

                     register int t=1;

                     for( ;b;b--)

                           t=t*a;

                     return t ;

         }

 

Τα εξαγόμενα αυτού του προγράμματος είναι:


 

Ν

 

1

2

3

4

5

6

7

8

9

10

Ν^2

 

1

4

9

16

25

36

49

64

81

100

Ν^3

 

1

8

27

64

125

216

343

512

729

1000

Ν^4

 

1

16

81

256

625

1296

2401

4096

6561

10000

 

 

Στο παράδειγμα αυτό εμφανίζονται η 1η, 2η, 3η, και 4η δύναμη των αριθμών από 1 ως 10. Βλέπουμε πως μπορούμε να περάσουμε πολυδιάστατους πίνακες σε παραμέτρους συναρτήσεων. Εδώ περνάμε έναν πίνακα 4, 10 ακεραίων στοιχείων στις συναρτήσεις show( ) και table( ). Η διαφορά είναι ότι από μόνοι μας δεσμεύουμε μνήμη με την malloc( ) για τις διαστάσεις του πίνακα.

 

Σημειώστε επίσης τον τρόπο χρήσης του τελεστή sizeof για τον υπολογισμό της μνήμης που θα χρειαστούμε. Η έκφραση sizeof(int) θα μας επιστρέψει 2 γιατί ένας int χρειάζεται 2 bytes. Αυτό εγγυάται και τη μεταφερσιμότητα του κώδικα από μηχάνημα σε μηχάνημα.

 

 

viii)           Αρχικοποίηση Πινάκων

 

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

 

type_specifier array_name[size1]…[sizeN]={value_list};

 

Το value_list είναι μία λίστα σταθερών, διαχωριζόμενων με κόμμα, οι οποίες είναι συμβατές με τον type_specifier. Η πρώτη σταθερά τοποθετείται στην πρώτη θέση του πίνακα, η δεύτερη στη δεύτερη, κλπ. Η τελευταία είσοδος στον πίνακα δεν ακολουθείται από κόμμα. Σημειώνεται ότι το ερωτηματικό ακολουθεί την αγκύλη. Στο παρακάτω παράδειγμα ένας 10θέσιος πίνακας ακεραίων αρχικοποιείται με τιμές από 1 ως 10.

 

            int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

 

Αυτό σημαίνει ότι το στοιχείο i[0] είναι 1, ενώ το i[9] έχει τιμή 10.

 

Οι πίνακες χαρακτήρων οι οποίοι κρατούν strings, επιτρέπουν μία συντομογραφική αρχικοποίηση της μορφής:

 

            char array_name[size] = “string”

 

Π.χ. αυτό το τμήμα κώδικα αρχικοποιεί το str στη λέξη hello:

 

char str[6]=”hello”;

 

Το ίδιο συμβαίνει κι αν γράψουμε:

 

char str[6] = “ ‘h’, ‘e’, ‘l’, ‘l’, ‘o’, \o’};

 

Επειδή όλα τα strings τελειώνουν με null, πρέπει να σιγουρέψουμε ότι ο πίνακας που δηλώνουμε είναι αρκετά μεγάλος για να το χωρέσει.

 

Αυτό μας δείχνει γιατί ενώ ο πίνακας είναι 6 στοιχείων το string που του αποδίδουμε είναι μόνο 5 στοιχείων. Όταν χρησιμοποιείται μία string σταθερά, ο compiler αυτομάτως τοποθετεί το null terminator αυτομάτως στο τέλος της.

 

Οι πολυδιάστατοι πίνακες αρχικοποιούνται με τον ίδιο τρόπο όπως οι μονοδιάστατοι.

Π.χ.

 

            int sqrs[10] [2] = {

                        1,1,

                        2,4,

                        3,9,

                        4,16,

                        5,25,

                        6,36,

                        7,49,

                        8,64,

                        9,81,

                        10,100

            };

 

Το παραπάνω παράδειγμα αρχικοποιεί τον πίνακα sqrs με τους αριθμούς 1 ως 10 και τα τετράγωνά τους.

 

 

 

ix)                Αρχικοποίηση Μη Καθορισμένων Πινάκων

 

Ας φανταστούμε ότι θέλουμε να αρχικοποιήσουμε πίνακες οι οποίοι θα κρατούν μηνύματα λαθών. Π.χ.:

 

            char e1[12] = “Read Error\n”;

            char e2[13] = “Write Error\n”;

            char e3[18] = “Cannot Open File\n”;

 

Είναι αρκετά δύσκολο να μετράμε τους χαρακτήρες κάθε μηνύματος από μόνοι μας, με σκοπό να οριοθετήσουμε τον πίνακα. Είναι πιθανό να αφήσουμε τη C να θέσει διαστάσεις στους πίνακες αυτόματα χρησιμοποιώντας μη οριοθετημένους πίνακες. Εάν το μέγεθος του πίνακα δεν ορίζεται σε μία δήλωση αρχικοποίησης, ο compiler της C αυτομάτως δημιουργεί ένα πίνακα αρκετά μεγάλο για να χωρέσει το string. Έτσι με αυτή τη λογική μπορούμε να έχουμε:

 

            char e1[ ] = “Read Error\n”;

            char e2[ ] = “Write Error\n”;

            char e3[ ] = “Cannot Open File\n”;

 

Δίνοντας αυτές τις αρχικοποιήσεις η παρακάτω εντολή θα δώσει:

 

                        printf( “%s has length %d \n”, e2, sizeof e2);

            Write Error

            Has Length 13

 

Στην περίπτωση πολυδιάστατων πινάκων πρέπει να ορίζουμε όλες τις διαστάσεις (εκτός την αριστερότερη).

Π.χ.:

 

            int sqrs [  ] [2] = {

                        1, 1,

                        2, 4,

                        3, 9,

                        4, 16,

                        5, 25,

                        6, 36,

                        7, 49,

                        8, 64,

                        9, 81,

                        10, 100

            };

 

 

 

x)                  Ολοκληρωμένο Παράδειγμα (Τρίλιζα)

 

#define SPACE ‘ ‘

char matrix [ 3] [ 3] = {

         SPACE, SPACE, SPACE,

         SPACE, SPACE, SPACE,

         SPACE, SPACE, SPACE

};

 

void get_computer_move( );

void get_player_move( );

void disp_matrix( );

 

main( ) 

{

         char done ;

 

         printf(“ Αυτή Είναι Μία Τρίλιζα\n”);

         printf(“ Παίζεται εναντίον του Υπολογιστή, \n”);

         done = SPACE;

         do  {

               disp_matrix( );

               get_player_move( );

               done = check( );

               if(done!=SPACE)  break;

               get_computer_move( );

               done = check( );

               }  while (done==SPACE);

               if (done== ’ X ’) printf(“ Με Νίκησες! \n”);

               else printf(“Σε Νίκησα!!! \n”);

               disp_matrix( );

         }

 

         void get_player_move( )

         {

               int x, y;

 

               printf(“ Δώσε Συντεταγμένες για το Χ: “);

               scanf(“%d%d”, &x, &y);

               x-- ; y -- ;

               if (matrix [x] [y] ! = SPACE)  {

                     printf(“Invalid Move, Try Again. \n”);

                     get_player_move( );

               }

               else matrix[x] [y] = ‘x’;

         }

         void get_computer_move( )

         {

               register int t;

               char *p;

               p = (char *) matrix;

               for (t = 0 ; *p ! = SPACE && t<9; ++t) p++;

               if (t==9)   {

                     printf(“ Ισοπαλία !!! \ n “);

                     exit(0);

               }

               else *p=’o’;

         }

         void disp_matrix( )

         {

               int t, i ;

               for(t = 0 ; t<3 ; t++)    {

                        printf( “%c ¦ %c ¦ %c”, matrix[t] [0],

                                                            matrix[t] [1],

                                                            matrix[t] [2]) ;

                        if (t !=2) printf(“\n --- ¦ --- ¦ --- \n”);

               }

               printf(“\n”);

         }

 

         check( )

         {

               int t;

               char *p;

               for(t=0; t<3; t++)   {

                     p=&matrix [t] [0];

                     if(*p==* (p+1) && * (p+1) == * (p+2)) return *p;

               }

               for(t=0; t<3; t++)   {

                     p=&matrix [0 [t] ;

                     if(*p==* (p+3) && * (p+3) == * (p+6)) return *p;

               }

               if (matrix[0] [0] == matrix [1] [1]  &&

                   matrix[1] [1] == matrix [2] [2] return matrix [0] [0] ;

               if (matrix[0] [2] == matrix[1] [1]  &&

                   matrix[1] [1] == matrix [2] [0] return matrix [0] [2] ;

               return SPACE ;

         }


Στο παραπάνω παράδειγμα εξομοιώνουμε ένα παιχνίδι τρίλιζας. Χρησιμοποιούμε έναν 3x3 πίνακα, ο οποίος αρχικοποιείται με κενά. Όταν ο παίκτης παίζει ένα Χ τοποθετείται στην αντίστοιχη θέση. Όταν παίζει ο υπολογιστής τοποθετεί ένα Ο στην πρώτη άδεια θέση. Αν δε βρει άδεια θέση αναφέρει ισοπαλία. Προσέξτε ότι η συνάρτηση get_player_move είναι αναδρομική όταν δοθούν λάθος συντεταγμένες. Η συνάρτηση check ελέγχει αν υπάρχει νικητής και επιστρέφει Χ ή Ο, ανάλογα με το νικητή. Η συνάρτηση αυτή ελέγχει γραμμές, στήλες, και διαγώνιους.