Τμήμα Ηλεκτρονικών Μηχανικών Τ.Ε.Ι. Κρήτης Προγραμματισμός Η/Υ (ΤΛ2007 ) Δρ. Μηχ. Νικόλαος Πετράκης (npet@chania.teicrete.gr) Ιστοσελίδα Μαθήματος: https://eclass.chania.teicrete.gr/ Εξάμηνο: Εαρινό 2014-15
Δομές Δεδομένων (επαναληπτικά) Μέχρι τώρα έχουμε δει μεταβλητές που ο τύπος τους είναι ένας από τους βασικούς τύπους που υποστηρίζει η C (π.χ. int, char, float, κλπ). Μια σχετική εξαίρεση ήταν oι διατάξεις (πίνακες) που λίγοπολύ μοιάζουν με «δεξαμενές» που μπορούμε να αποθηκεύσουμε δεδομένα (ενός μόνο συγκεκριμένου τύπου) σε μια ακολουθιακή μορφή. Η δομή δεδομένων είναι μια νέα μορφή μεταβλητής που και αυτή μοιάζει με «δεξαμενή» αλλά: 1. Επιτρέπει να αποθηκευτούν δεδομένα διαφορετικών τύπων 2. Είναι πιο δομημένη από την απλή ακολουθιακή μορφή των διανυσμάτων 2
Παράδειγμα Ετικέτα struct person { int number; int sex; char name[10]; char address[40]; int age; }; Δομή Δεδομένων Η λέξη struct προσδιορίζει την αρχή ενός ορισμού δομής και ακολουθείται από το όνομα της δομής ή την ετικέτα (tag). Εδώ ορίζεται μια δομή δεδομένων αλλά δεν δημιουργείται ένα στιγμιότυπο δομής, δε δηλώνεται ή δε δεσμεύεται χώρος μνήμης για να δηλωθούν δομές. Τα μέλη της δομής 3
Παράδειγμα struct person { int number; int sex; char name[10]; char address[40]; int age; } maleperson, femaleperson; Εδώ ορίζουμε τη δομή με ετικέτα person και δηλώνουμε δύο μεταβλητές, τις maleperson, femaleperson τύπου δομής person. Μεταβλητές τύπου δομής 4
Παράδειγμα struct person { int number; Η ετικέτα δομής είναι ένα int sex; συμβολικό όνομα που ορίζει μία συγκεκριμένη δομή π.χ. char name[10]; char address[40]; int age; }; /* και όπου χρειαζόμαστε μεταβλητές */ struct person maleperson, femaleperson; Μεταβλητές τύπου δομής 5
Κι άλλα παραδείγματα struct { int number; int sex; char name[20]; char address[40]; int age; } person1, person2; struct { int number; char name[15]; int yearsofemployment; } employee1; 6
Απόδοση αρχικών τιμών σε δομές Όπως και στις διατάξεις, έτσι και μία δομή μπορεί να πάρει αρχικές τιμές κατά τη δήλωση της, π.χ. struct { int number; int sex; char name[20]; } person1 = {154, 1, Harry Potter }, person2 = {180, 2, Mary Brown }; Δηλώνουμε και συγχρόνως δίνουμε αρχική τιμή σε μεταβλητές τύπου δομής. 154 1 Harry Potter 180 2 Mary Brown 7
Λειτουργίες σε δομές Όπως και στα διανύσματα, έτσι και στις δομές, η πιο κοινή λειτουργία είναι να προσπελάσουμε κάποιο στοιχείο τους Τα μέλη δομών προσπελαύνονται με τη χρήση του τελεστή μέλους δομής (.) που λέγεται και τελεστής τελεία (dot operator) και τοποθετείται ανάμεσα στο όνομα της δομής και στο όνομα του μέλους. printf( The name of person %d is %s, person1.number, person1.name); Δομή (μεταβλητή) Τελεστής Πρόσβασης Μέλος της Δομής 8
Παράδειγμα scanf( %d, &person1.number); /* Διαβάζουμε το μέλος number */ person1 = person2; /* Αποθέτουμε τις τιμές όλων των μελών της person2, στα αντίστοιχα μέλη της person1 */ Όταν το πρόγραμμά σας χρησιμοποιεί πολύπλοκες δομές με πολλά μέλη αυτή η σημειογραφία μας δίνει μεγάλος κέρδος χρόνου. 9
Ορισμός νέου τύπου δεδομένων τύπου δομής δεδομένων Μέχρι τώρα είδαμε πως να δηλώνουμε μεταβλητές που ήταν δομές. Ας υποθέσουμε ότι σε ένα πρόγραμμα θέλουμε να δηλώσουμε πολλές μεταβλητές που έχουν την ίδια δομή (ίδια μέλη). Εάν όλες αυτές οι μεταβλητές μπορούν να δηλωθούν μαζί τότε το πρόβλημα δεν είναι μεγάλο. Εάν όμως δηλώνονται σε διάφορα σημεία του προγράμματος τότε τα πράγματα είναι πιο δύσκολα (πρέπει να επαναλαμβάνουμε τη δήλωση της δομής). Ιδανικά, θα θέλαμε να έχουμε την ευχέρεια να δηλώσουμε τον δικό μας τύπο και απλά να χρησιμοποιούμε το όνομά του όπως θα χρησιμοποιούσαμε int, float, κλπ. Αυτό επιτυγχάνεται με την εντολή typedef 10
Παράδειγμα typedef struct { int idnumber; char descr[100]; int avail; } SparePartType; Η κατασκευή του Τύπου Το όνομα του Τύπου SparePartType var1 = {123, Wheel, 1}; Δήλωση και αρχικοποίηση μεταβλητής 11
Δομές που περιέχουν δομές Μία δομή μπορεί να περιέχει άλλες δομές struct coord { int x; int y; }; struct rectangle { struct coord topleft; struct coord bottomrt; } ; struct rectangle mybox; mybox.topleft.x = 0; mybox.topleft.y = 10; mybox.bottomrt.x = 100; mybox.bottomrt.y = 200; Ορίζουμε ορθογώνιο με συντεταγμένες (0, 10) και (100, 200). 12
Δομές που περιέχουν πίνακες Μία δομή μπορεί να περιέχει μία ή περισσότερες διατάξεις οποιουδήποτε τύπου δεδομένων ως μέλη struct data { }; int x[5]; char y[10]; struct data record; record.x[2] = 100; record.y[0] = a ; record.y[1] = b ; record.y[2] = c ; record.y[3] = \0 ; Tα στοιχεία της διάταξης x καταλαμβάνουν διπλάσιο χώρο από της y (διότι 5 x 4 = 20 bytes σε σχέση με τα 10 x 1 = 10 bytes) H έκφραση record.y είναι ένας δείκτης στο πρώτο στοιχείο της διάταξης y[] στη δομή record. Θα μπορούσαμε να προβάλουμε τα περιεχόμενα της y[] στην οθόνη με puts(record.y) ή printf ( %s, record.y); εφόσον το y είναι string (δηλαδή έχει οριοθετημένο το τέλος του με \0 όπως εδώ) 13
Πίνακες δομών Οι πίνακες (διατάξεις) δομών είναι πολύ ισχυρά εργαλεία προγραμματισμού. struct entry { char fname[10]; char lname[13]; char phone[12]; }; struct entry list[1000]; list[1] = list[5]; strcpy(list[1].phone, list[5].phone); list[2].phone[3] = list[5].phone[1]; Δηλώνει έναν πίνακα με το όνομα list η οποία περιέχει 1000 στοιχεία. Κάθε στοιχείο είναι μία δομή τύπου entry. Καθεμιά από αυτές τις δομές έχει τρία μέλη, καθένα των οποίων είναι μία διάταξη τύπου char Εκχωρεί σε κάθε μέλος της δομής list[1] τις τιμές που περιέχονται στα αντίστοιχα μέλη της list[5]. Αντιγράφει τη συμβολοσειρά της list[5].phone στη list[1].phone. Μετακινεί το δεύτερο χαρακτήρα του αριθμού τηλεφώνου της list[5] στην τέταρτη θέση του αριθμού τηλεφώνου στη list[2]. 14
Σημείωση - Διευκρίνηση Η ετικέτα μίας δομής χρησιμοποιείται για να δηλώσει τη μορφή μίας δομής. Το στιγμιότυπο είναι μία μεταβλητή που δηλώνεται με χρήση της ετικέτας. Μην ξεχνάτε τη λέξη κλειδί struct όταν δηλώνετε ένα στιγμιότυπο από μία δομή που έχει οριστεί προηγουμένως, παρότι σε κάποιες υλοποιήσεις της γλώσσας C δεν απαιτείται πια. 15
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) Οι δείκτες μπορούν να αποτελέσουν μέλη δομών struct data { int *value; int *rate; } first; Εδώ δηλώνουμε μία δομή της οποίας τα δύο μέλη είναι δείκτες προς τύπο int. Πρέπει να δώσουμε αρχικές τιμές: int cost=7, interest=12; first.value = &cost; first.rate = &interest; Η έκφραση *first.value έχει την τιμή της cost και ανάλογα η *first.rate έχει την τιμή της interest. 16
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) O δείκτης που χρησιμοποιείται πιο συχνά ως μέλος δομής είναι ο δείκτης προς τύπο char. Μία συμβολοσειρά είναι μία ακολουθία χαρακτήρων που περιγράφεται από ένα δείκτη που δείχνει στον πρώτο χαρακτήρα και από ένα μηδενικό χαρακτήρα που δηλώνει το τέλος της συμβολοσειράς. char *p_message; p_message = Teach yourself C in 21 days ; struct msg { char *p1; char *p2; } myptrs; myptrs.p1 = Teach yourself C in 21 days ; myptrs.p2 = by SAMS Publishing ; printf( %s %s, myptrs.p1, myptrs.p2); 17
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) Σε αυτή τη δομή βλέπουμε 2 μεθόδους αποθήκευσης μίας συμβολοσειράς σε μία δομή. struct msg { char p1[30]; char *p2; } myptrs; strcpy(myptrs.p1, Teach yourself C in 21 days ); strcpy(myptrs.p2, by SAMS Publishing ); Περιορισμένος χώρος στη μνήμη /*πρόσθετος κώδικας*/ puts(myptrs.p1); puts(myptrs.p2); 18
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες σε δομές) struct part { }; int number; char name[10]; struct part *p_part; struct part va; p_part = &va; (*p_part).number = 100; p_part->number = 100; Η p_part είναι ένας δείκτης σε τύπο part και όχι ένα στιγμιότυπο τύπου part. Μεταβλητή τύπου δομής Εκχωρεί τη διεύθυνση της va στον p_part) Εκχωρεί τη τιμή 100 στη va.number Το ίδιο με πριν, με τον τελεστής βέλος έμμεσης σχέσης μέλους, (arrow operator) Ισοδύναμες προτάσεις va.number = 100; 19
struct part { } Δείκτες και διατάξεις δομών int number; char name[10]; Αφού ορίσουμε τη δομή part, μπορούμε να δηλώσουμε μία διάταξη τύπου part: struct part data[100]; Μετά μπορούμε να δηλώσουμε ένα δείκτη διεύθυνσης σε τύπο part και να του δώσουμε ως αρχική τιμή να δείχνει στην πρώτη δομή (δηλαδή το πρώτο στοιχείο) της διάταξης data: struct part *p_part; p_part = &data[0]; // ή καλύτερα: p_part = data; Προβολή του περιεχομένου της πρώτης δομής της διάταξης δομών, με pointer: printf( %d %s\n, p_part->number, p_part->name); ή ισοδύναμα με πίνακα και δείκτη πίνακα: printf( %d %s\n, data[0].number, data[0].name); 20
Ενώσεις Η ένωση (union) είναι ένας τύπος της C που μοιάζει με τη δομή. Μια ένωση έχει στοιχεία διαφορετικών τύπων, όπως η δομή, αλλά μόνο ένα από αυτά μπορεί να χρησιμοποιείται κάθε φορά. Για τα στοιχεία της ένωσης δεσμεύεται ένας κοινός χώρος μνήμης, ίσος με το μέγεθος του μεγαλύτερου στοιχείου. H ανάθεση τιμής σε ένα στοιχείο της ένωσης «γράφει» στην περιοχή μνήμης που μπορεί να υπήρχε τιμή για άλλο στοιχείο. Παράδειγμα: union shared { char c; int i; }; union shared generic_variable = {'@'}; Χρησιμοποιούμε τον τελεστή μέλους (dot operator) για να προσπελαύνουμε τα μέλη της ένωσης. 21
Παράδειγμα #include <stdio.h> #define CHARACTER 'C' #define INTEGER 'I' #define FLOAT 'F' struct generic_tag{ }; char type; union shared_tag { char c; int i; float f; } shared; void print_function(struct generic_tag gen); main() { struct generic_tag var; var.type = CHARACTER; var.shared.c = '$'; print_function(var); var.type = FLOAT; var.shared.f = (float)3.1415; print_function(var); var.type = x'; var.shared.i = 111; print_function(var); return 0; } 22
Παράδειγμα (συνέχεια) void print_function(struct generic_tag gen) { printf("\n\nthe generic value is..."); switch(gen.type) { case CHARACTER: printf("%c", gen.shared.c); break; case INTEGER: printf("%d", gen.shared.i); break; case FLOAT: printf("%f", gen.shared.f); break; default: printf("an unknown type: %c\n", gen.type); break; } return ; } Αποτέλεσμα: The generic value is...$ The generic value is...3.1415 The generic value is...an unknown type: x 23
Ροές ή Ρεύματα Κειμένου (Text Streams ) Στη C ο όρος (text) stream ορίζει μία πηγή στοιχείων εισόδου, ή ένα προορισμό για στοιχεία εξόδου (μία ακολουθία bytes δεδομένων) και είναι συνώνυμο με το αρχείο (κειμένου). Δύο βασικά και οριζόμενα αυτόματα από τη C streams είναι το πληκτρολόγιο (keyboard) για είσοδο (input) [stdin], και η οθόνη (screen or terminal), για έξοδο (output) [stdout], Τα μεγάλα προγράμματα μπορούν να έχουν πολλές πηγές εισόδου π.χ. Modems, CD drives, files, κλπ. Τα προγράμματα συνήθως διαβάζουν στοιχεία από μια πηγή και στέλνουν αποτελέσματα σε ένα προορισμό (που μπορεί να είναι πηγή στοιχείων για κάποιο άλλο πρόγραμμα). Στη C θα μπορούσαμε να χειριστούμε μια ροή είτε ως κειμένου (text) είτε ακόμα και ως δυαδική (binary). 24
Ροές ή Ρεύματα Κειμένου (Text Streams ) Μία δυαδική ροή μπορεί να χειριστεί οποιοδήποτε είδος δεδομένων συμπεριλαμβανομένων και των δεδομένων κειμένου. Όλα τα δεδομένα γράφονται και διαβάζονται αμετάβλητα και χωρίς τη χρήση των χαρακτήρων τέλους γραμμής. Κάποιες συναρτήσεις εισόδου/εξόδου αρχείου περιορίζονται σε μία μορφή αρχείου ενώ άλλες μπορούν να χρησιμοποιούν κάθε μορφή. 25
Προκαθορισμένες ροές Η ΑΝSI C έχει τρεις προκαθορισμένες ροές, stdin (προκαθορισμένη είσοδος πληκτρολόγιο), stdout (προκαθορισμένη έξοδος οθόνη) και stderr (προκαθορισμένη έξοδος λάθους οθόνη). Με printf() ή puts() χρησιμοποιούμε τη stdout, με scanf() ή gets() λαμβάνουμε είσοδο από πληκτρολόγιο, δηλ. τη stdin. Οι περισσότερες συναρτήσεις της C είτε χρησιμοποιούν τις προκαθορισμένες ροές, είτε απαιτούν τον προσδιορισμό της ροής από το χρήστη (π.χ. getc() / getchar(), putc() / putchar(), κλπ) 26
Παράδειγμα αναδρομικής συνάρτησης #include <stdio.h> #include <stdlib.h> #include <math.h> float dynami(float, int); main() { float bash, dyn; int ekth; } printf("\nδώστε βάση (πραγματικό) : "); scanf("%f", &bash); printf("\nδώστε εκθέτη (ακέραιο) : "); scanf("%d", &ekth); dyn = dynami(bash, ekth); printf("\nη ύψωση του %.3f εις την %d κάνει %.4f\n", bash, ekth, dyn); system("pause"); return 0; Υλοποίηση προγράμματος για τον υπολογισμό της ύψωσης ενός πραγματικού αριθμού σε έναν οποιοδήποτε ακέραιο. Η ύψωση να γίνει σε ξεχωριστή συνάρτηση και μάλιστα τόσο σε μη αναδρομική μορφή όσο και σε αναδρομική μορφή. 27
Παράδειγμα (συνέχεια) /*Μη αναδρομική μορφή της συνάρτησης */ /*Αναδρομική μορφή της συνάρτησης */ float dynami(float b, int e) { float p=1.0; int i; for(i=0; i < abs(e); i++) p = p*b; if (e<0) p = 1/p; return p; } float dynami(float b, int e) { float p; if (e == 0) p = 1.0; else if (e > 0) p = dynami(b, e-1)*b; else p = dynami(b, e+1)/b; return p; } Σημείωση: Η συνάρτηση για την απόλυτη τιμή, abs() absolute value, ευρίσκεται μέσα στο αρχείο επικεφαλίδα math.h, γι αυτό και το συμπεριλάβαμε στην αρχή. 28