Τμήμα Ηλεκτρονικών Μηχανικών Τ.Ε.Ι. Κρήτης Προγραμματισμός Η/Υ (ΤΛ2007 ) Δρ. Μηχ. Νικόλαος Πετράκης (npet@chania.teicrete.gr) Ιστοσελίδα Μαθήματος: https://eclass.chania.teicrete.gr/ Εξάμηνο: Εαρινό 2015-16
Τυπικά ορίσματα με const Όπως είπαμε, όταν ένα όρισμα διοχετεύεται σε μία συνάρτηση (και δεν είναι δείκτης διεύθυνσης) τότε η τιμή του αντιγράφεται σε μία προσωρινή μεταβλητή, και χρησιμοποιείται στο σώμα της συνάρτησης. Όμως, όταν διοχετεύουμε μία διάταξη σαν όρισμα τότε ουσιαστικά περνάμε ένα δείκτη διεύθυνσης. Σε μερικές όμως περιπτώσεις, θέλουμε να είμαστε σίγουροι ότι η διάταξη δεν θα αλλαχθεί από την συνάρτηση. Τότε χρησιμοποιούμε τη λέξη κλειδί const στις παραμέτρους της συνάρτησης. 2
Παράδειγμα void init_zeros (int a[ ], int size) { int i; for (i = 0; i < size; i++) a[i] = 0; } void use_array(const int a[ ], int size,...) { /* Ο μεταγλωττιστής ελέγχει εάν αλλάζουμε την τιμή της διάταξης a */ } Δείκτης σε int, αναπαριστά ένα δείκτη σε μία διάταξη. Θα το δείτε κάποιες φορές ως *a 3
Δείκτες και πίνακες Απλά ας θυμηθούμε ότι οι πολυδιάστατοι πίνακες (διατάξεις) στη C καταχωρίζονται στη μνήμη σαν μία γραμμή (η μία γραμμή μετά την άλλη). Σημαντικό: Για πίνακες, ως όρισμα περνάει ο δείκτης στο πρώτο στοιχείο a[0] (ή b[0][0]). Δηλαδή, πρωτότυπο συνάρτησης: int find_largest(int x[], int m); κλήση συνάρτησης: find_largest(a, NR_COLS * NR_ROWS); 4
Παράδειγμα #define NR_ROWS 10 #define NR_COLS 15... int a[nr_rows][nr_cols]; int row, col;... for(i = 0; i < NR_ROWS; i++) for(j = 0; j < NR_COLS; j++) a[i][j] = 0; ή εναλλακτικά: int *p; for(p=&a[0][0]; p<=&a[nr_rows-1][nr_cols-1]; p++) *p = 0; 5
Χαρακτήρες και συμβολοσειρές Ένας χαρακτήρας είναι ένα γράμμα, ένα σημείο στίξης ή ένα σύμβολο. Η μνήμη του υπολογιστή αποθηκεύει όλα τα δεδομένα σε αριθμητική μορφή. Δεν υπάρχει άμεσος τρόπος αποθήκευσης χαρακτήρων, υπάρχει όμως ένας αριθμητικός κώδικας για αποθήκευση χαρακτήρων, ο κώδικας ASCII (0-255). Για παράδειγμα το γράμμα a είναι ο κώδικας ASCII=97. Π.χ. #define EX x char a, b=εχ, c = y ; c =! ; c = EX; 6
Χαρακτήρες και συμβολοσειρές Στη C, μία συμβολοσειρά ορίζεται ως μία ακολουθία χαρακτήρων που τελειώνει με τον μηδενικό χαρακτήρα, που αναπαρίσταται από το \0 (με τιμή ASCII = 0). Οι διατάξεις τύπου char δηλώνονται όπως όλες οι διατάξεις, π.χ.: char name[10]; Εδώ μπορούμε να αποθηκεύσουμε μία συμβολοσειρά μήκους μέχρι 9 χαρακτήρων. Αρχικοποίηση συμβολοσειρών char name[10] = { a, l, a, b, a, m, a, \0 }; char name[10] = Alabama ; // Ο χαρακτήρας \0 προστίθεται αυτόματα Ο μηδενικός χαρακτήρας καθορίζει το τέλος της συμβολοσειράς χωρίς αυτόν ο μεταγλωττιστής δεν έχει τρόπο να βρει το τέλος της συμβολοσειράς γι αυτό προχωράει μέχρι να συναντήσει κάποιον μηδενικό χαρακτήρα στη μνήμη. 7
Συμβολοσειρές και δείκτες Επειδή υπάρχει σήμανση για το τέλος μίας συμβολοσειράς το μόνο που χρειαζόμαστε για να ορίσουμε τη συμβολοσειρά είναι κάτι που να δείχνει την αρχή της. Άρα χρειάζεται μόνο το όνομα της συμβολοσειράς ή διάταξης χαρακτήρων για να αποκτήσουμε πρόσβαση σε αυτή. 8
Παράδειγμα 1 /* Μήκος συμβολοσειράς, εκδοχή με πίνακες */ int strlen (char s[]) { int n=0; while( s[n]!='\0') n++; return n; } /* Νέα εκδοχή με δείκτες διεύθυνσης (pointers) */ int strlen (char *s) { int n; for (n=0; *s!='\0' ; n++) s++; return n; } Στο αρχείο string.h υπάρχει ο ορισμός αυτής της συνάρτησης με πρωτότυπο: int strlen (char *s); 9
Παράδειγμα 2 /* Αντιγραφή του s στο d, εκδοχή με πίνακες */ void strcopy(char *d, char *s) { int i=0; while ((d[i]=s[i])!='\0') i++; return; } /* Eκδοχή με δείκτες διεύθυνσης (pointers) */ void strcopy(char *d, char *s) { } while ((*d=*s)!='\0') { d++; s++; } return; /* ή ακόμα καλύτερα */ void strcopy(char *d, char *s) { while ((*d++=*s++)!='\0') ; return; } Στο αρχείο string.h υπάρχει ο ορισμός της συνάρτησης με πρωτότυπο: char *strcpy(char *dest, const char *src); 10
Παράδειγμα 3 /* Επιστρέφει <0 αν s1<s2, 0 αν s1==s2, και >0 αν s1>s2 εκδοχή με πίνακες */ int strcomp(char *s1, char *s2) { int i; for (i=0; s1[i]==s2[i]; i++) if (s1[i]=='\0') return 0; return s1[i]-s2[i]; } /* Εκδοχή με δείκτες διεύθυνσης (pointers) */ int strcomp(char *s1, char *s2) { for ( ; *s1==*s2 ; s1++, s2++) if (*s1=='\0') return 0; return *s1-*s2; } Στο αρχείο string.h υπάρχει ο ορισμός της συνάρτησης με πρωτότυπο: int strcmp(const char *s1, const char *s2); 11
Κι άλλα παραδείγματα Συνάρτηση που να επιστρέφει τον ν-οστό όρο της ακολουθίας Fibonacci, όταν γνωρίζουμε ότι ο κάθε όρος προκύπτει από το άθροισμα των δύο προηγούμενων όρων και ότι ο πρώτος όρος ισούται με μηδέν (0), ενώ ο δεύτερος με ένα (1). Υλοποίηση ενός στοιχειώδους κατανεμητή μνήμης με συναρτήσεις για δέσμευση, alloc(n), και αποδέσμευση, afree(p), μνήμης. Οι λύσεις, που δόθηκαν στον πίνακα, υπάρχουν στις σημειώσεις της θεωρίας. 12
Συμβολοσειρές χωρίς πίνακες Ξέρουμε ότι μία συμβολοσειρά δηλώνεται από διάταξη (πίνακα) χαρακτήρων και ένα μηδενικό χαρακτήρα στο τέλος. Τι θα γινόταν αν μπορούσατε να βρείτε κάποιο χώρο στη μνήμη χωρίς να έχει κατανεμηθεί μία διάταξη? Θα μπορούσαμε να αποθηκεύσουμε μία συμβολοσειρά εκεί. Ένας δείκτης στον πρώτο χαρακτήρα θα μπορούσε να καθορίζει την αρχή μίας συμβολοσειράς ακριβώς όπως αν η συμβολοσειρά είχε εκχωρηθεί σε διάταξη. Η συνάρτηση malloc() κατανέμει χώρο ενώ εκτελείται το πρόγραμμα, δυναμική κατανομή. 13
Κατανομή χώρου συμβολοσειράς στη char *message; μεταγλώττιση // δείκτης σε τύπο char char *message = hello world ; Η συμβολοσειρά hello world με \0 αποθηκεύεται κάπου στη μνήμη, και ο δείκτης message λαμβάνει τη διεύθυνση του πρώτου χαρακτήρα της συμβολοσειράς. Οι *message και message[] είναι ισοδύναμες εκφράσεις Ο προηγούμενος τρόπος καταχώρισης συμβολοσειράς είναι πολύ καλός αλλά κάποιες φορές το πρόγραμμα έχει κυμαινόμενες ανάγκες αποθήκευσης συμβολοσειρών, π.χ. ανάλογα με την είσοδο του χρήστη Τότε χρησιμοποιούμε τη συνάρτηση malloc() η οποία μας επιτρέπει να κατανείμουμε αποθηκευτικό χώρο στη πορεία του προγράμματος (δυναμικά). 14
Συνάρτηση malloc() Στη malloc() διοχετεύουμε τον αριθμό των ψηφιολέξεων (bytes) μνήμης που απαιτούνται. Η συνάρτηση βρίσκει και δεσμεύει ένα μπλοκ μνήμης του επιθυμητού μεγέθους και επιστρέφει τη διεύθυνση του πρώτου byte του μπλοκ. Δηλαδή επιστρέφει ένα δείκτη τύπου void ο οποίος είναι συμβατός με όλους τους τύπους δεδομένων. Εάν δεν υπάρχει αρκετή μνήμη διαθέσιμη, η malloc() επιστρέφει 0 (NULL = μηδενικός δείκτης διεύθυνσης). Θα πρέπει να ελέγχεται ότι η απαιτούμενη μνήμη έχει κατανεμηθεί με επιτυχία, ως προς τη σταθερά NULL η οποία δηλώνεται στη <stdlib.h> 15
#include <stdlib.h> Χρήση της malloc() void *malloc(size_t size); //πρωτότυπο συνάρτησης Η malloc() κατανείμει ένα μπλοκ μνήμης που είναι ο αριθμός των bytes που δηλώθηκαν από την παράμετρο size Έτσι χρησιμοποιούμε πιο αποδοτικά τη μνήμη του υπολογιστή. /*Κατανέμει μνήμη για μία διάταξη 50 ακέραιων*/ int *numbers; numbers = (int *) malloc(50 * sizeof(int)); /*Κατανέμει μνήμη για μία διάταξη 10 float */ float *numbers; numbers = (float *) malloc(10 * sizeof(float)); 16
Χρήση της malloc() /* malloc() για κατανομή μνήμης για αποθήκευση ενός απλού char */ char *ptr; ptr = malloc(1); /* κατανομή μνήμης ενός byte και εκχώρηση της διεύθυνσής του στη ptr */ *ptr = x ; char *ptr; ptr = malloc(100); /* ίδιο με char ptr[100]; */ 17
H συνάρτηση puts() H συνάρτηση puts() προβάλλει μία συμβολοσειρά στην οθόνη. Το όρισμα που δέχεται είναι ένας δείκτης στη συμβολοσειρά που πρόκειται να προβληθεί. Η puts() εισάγει αυτόματα ένα χαρακτήρα νέας γραμμής στο τέλος κάθε συμβολοσειράς που προβάλλει. 18
H συνάρτηση printf() char *str = "A message to display"; printf("%s", str); 19
H συνάρτηση gets() H συνάρτηση gets() διαβάζει όλους τους χαρακτήρες από το πληκτρολόγιο μέχρι τον πρώτο χαρακτήρα νέας γραμμής (Enter). H συνάρτηση αφαιρεί το χαρακτήρα νέας γραμμής και προσθέτει ένα μηδενικό χαρακτήρα ( \0 ). Η συμβολοσειρά αποθηκεύεται στη θέση που δηλώνει ένας δείκτης τύπου char, o oποίος διοχετεύεται στη gets(). 20
Eρωτήσεις Τι θα συμβεί αν χρησιμοποιήσω τον τελεστή διεύθυνσης σε ένα δείκτη? Οι μεταβλητές πάντα αποθηκεύονται στην ίδια θέση? 21
Δομές Δεδομένων (Data Structure) Δομή δεδομένων είναι η συλλογή από δύο ή περισσότερες μεταβλητές, πιθανότατα διαφορετικού τύπου δεδομένων, που ομαδοποιούνται με ένα μόνο όνομα για ευκολία στον χειρισμό τους. Οι μεταβλητές αυτές αποτελούν τα μέλη (members) της δομής. Στη C μπορούμε να δηλώσουμε μεταβλητές τύπου δομής με τρεις τρόπους. Παράδειγμα: Έστω ότι θέλουμε μεταβλητές τύπου δομής με τρία μέλη για την ενοποίηση του ονόματος, του αρχικού πατρώνυμου και του επωνύμου. 22
Τρόποι δήλωσης μιας Δομής α) άμεση δήλωση μεταβλητών τύπου δομής struct { char first[10]; /* μικρό όνομα */ char midinit; /* αρχικό πατρώνυμο */ char last[15]; /* επώνυμο */ } aname, bname; // structure variables β) δήλωση δομής με ετικέτα (tag) struct Namet { char first[10]; char midinit; char last[15]; }; struct Namet aname, bname; // tag γ) ορισμός νέου τύπου δεδομένων typedef struct { char first[10]; char midinit; char last[15]; }NameType; NameType aname, bname; 23