ΣΧΟΛΗ ΔΙΟΙΚΗΣΗΣ ΚΑΙ ΟΙΚΟΝΟΜΙΑΣ ΤΜΗΜΑ ΔΙΟΙΚΗΣΗΣ ΕΠΙΧΕΙΡΗΣΕΩΝ (ΠΑΤΡΑ) ΓΛΩΣΣΑ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ C Γιάννης Κουτσονίκος Επίκουρος Καθηγητής
Υλοποίηση λογισμικού Συγγραφή πηγαίου κώδικα Ο προγραμματιστής επιλέγοντας μία από τις διαθέσιμες γλώσσες προγραμματισμού και χρησιμοποιώντας έναν επεξεργαστή κειμένου (text editor) δημιουργεί ένα αρχείο που περιέχει τον πηγαίο κώδικα του προγράμματος του (.c). #include <stdio.h> main( ) { printf("hello, world"); } Μεταγλώττιση Η διεργασία της μεταγλώττισης είναι πλήρως αυτοματοποιημένη και αρκεί να πατηθεί το κουμπί της μεταγλώττισης (compiler). Σε περίπτωση συντακτικού λάθους, η μεταγλώττιση δεν είναι επιτυχής και εμφανίζεται η λίστα με τα συντακτικά λάθη, π.χ. example.c(4): Error! E1077: Missing '}' example.c: 4 lines, 0 warnings, 1 errors Σε αντίθετη περίπτωση, δημιουργείται το αντικείμενο αρχείο (object file). 2
Υλοποίηση λογισμικού Σύνδεση Σύνδεση μέσω του συνδέτη (Linker) του δημιουργημένου αντικείμενου αρχείου με άλλα αντικείμενα αρχεία (εάν ζητείται) καθώς και με βιβλιοθήκες. Δημιουργία εκτελέσιμου (.exe) αρχείου Τα λάθη στο λογισμικό Συντακτικά λάθη (Syntax errors) Παραβίαση συντακτικών κανόνων. Λάθη κατά την εκτέλεση (Run-time errors). Λογικά λάθη (Σημασιολογικά λάθη) Bugs Debugging. 3
Υλοποίηση λογισμικού Αρχή Συγγραφή/Επεξεργασία Πηγαίου Κώδικα test.c Μεταγλώττιση Πηγαίου Κώδικα Βιβλιοθήκες Object αρχεία ναι Συντακτικά Λάθη; όχι Σύνδεση (Link) test.obj ναι Εκτέλεση Λάθη; όχι ΤΕΛΟΣ test.exe 4
Μερικά χαρακτηριστικά στοιχεία της C Έχει ένα πολύ μικρό πλήθος λέξεων-κλειδιών (keywords), το οποίο περιλαμβάνει ένα πλήρες σύνολο δομών/εντολών ελέγχου ροής: for, if/else, while, switch, και do/while, goto. Υπάρχει ένα μεγάλο πλήθος αριθμητικών και λογικών τελεστών, όπως οι: +, +=, ++, -, -=, --, *, *=, /, /=, ==, &, &&,,, ~, κ.ά. Σε μία εντολή μπορεί να γίνουν παραπάνω από μια εκχωρήσεις τιμών. Η τιμή που επιστρέφει μια συνάρτηση, μπορεί να αγνοηθεί εάν δεν χρειάζεται. Η C είναι case-sensitive γλώσσα (ευαίσθητη σε πεζά - κεφαλαία), δηλαδή τα πεζά και τα κεφαλαία έχουν διαφορετική σημασία. Ο χρήστης μπορεί να ορίσει δικούς του τύπους (και σύνθετους), εάν το επιθυμεί. Μπορεί επίσης να ορίσει και τύπους εγγραφών (structs στη C, records σε άλλες γλώσσες). 5
Μερικά χαρακτηριστικά στοιχεία της C Το πρώτο στοιχείο κάθε πίνακα έχει πάντα τη θέση μηδέν (0). Π.χ.: Το στοιχείο month[0] είναι το πρώτο στοιχείο του πίνακα month. Δεν υπάρχει ιδιαίτερος τύπος για αλφαριθμητικά, τα οποία παραδοσιακά υλοποιούνται και αντιμετωπίζονται σαν πίνακες από χαρακτήρες, και έχουν έναν μηδενικό χαρακτήρα να σημαδεύει το τέλος τους (null-terminated arrays of characters). Τα υποπρογράμματα που δεν επιστρέφουν τιμή ("procedures" σε άλλες γλώσσες) είναι συναρτήσεις που ορίζονται να είναι τύπου "void" (ψευδοτύπος που δείχνει την απουσία επιστρεφόμενης τιμής, αλλά χρησιμοποιείται και για δείκτες που δεν δείχνουν σε αντικείμενο συγκεκριμένου τύπου). Δεν μπορούν να οριστούν συναρτήσεις μέσα σε άλλες συναρτήσεις (εμφωλιασμένες). 6
Δομή προγράμματος σε C #include <stdio.h> /* Χρήση standard τύπων & συναρτήσεων */ #define Α 100 /* Ορισμός συμβολικού ονόματος */ const int b=10; /* Σταθερά */ typedef int akeraios; /* Δήλωση τύπου */ int i; /* Ακέραια Μεταβλητή καθολικής εμβέλειας*/ char c; /* Μεταβλητή χαρακτήρα καθολικής εμβέλειας*/ int funct1(παράμετροι) /* Συνάρτηση (πρότυπο ή υλοποίηση)*/ { /* Εντολές συνάρτησης */ } main() /* Κυρίως πρόγραμμα */ { /* Εντολές */ } /* Τέλος κυρίως προγράμματος */ 7
Κανόνες δημιουργίας ευανάγνωστου προγράμματος Αποφύγετε ονόματα ενός χαρακτήρα όπως i, j, l, m (υπάρχουν κάποιες περιπτώσεις, όπως θα δούμε στη συνέχεια, όπου χρησιμοποιούνται ονόματα αυτής της μορφής). Χρησιμοποιήστε εκφραστικά ονόματα. Πιο συγκεκριμένα: α) ονομάστε τη μεταβλητή η οποία αναπαριστά την ταχύτητα velocity και τη μέγιστη τιμή της max_velocity ή maxvelocity. β) ονομάστε τη συνάρτηση που εμφανίζει τα λάθη στην οθόνη display_error ή displayerror. Για την καλύτερη αναγνωσιμότητα των μεταβλητών που αποτελούνται από δύο ή περισσότερες λέξεις της καθομιλουμένης, αποφασίστε αν θα χρησιμοποιείτε σαν διαχωριστικό τον χαρακτήρα υπογράμμισης ή θα γράφετε κεφαλαίο το πρώτο γράμμα των λέξεων μετά την πρώτη. Προσπαθήστε να μην παραβιάζετε τους κανόνες σας. Χρησιμοποιήστε μικρά γράμματα για ονόματα μεταβλητών. 8
Αναγνωσιμότητα κώδικα Χρήση εκφραστικών ονομάτων. Χρήση εσοχών. Σχολιασμός κώδικα (χρήση /*...*/). Κώδικας 1 j = 120; if (i>j) func1(); else func2(); Κώδικας 2 /*αρχικοποίηση μέγιστης ταχύτητας*/ max_velocity = 120; /*εάν η ταχύτητα είναι μεγαλύτερη από την μέγιστη*/ if (velocity > max_velocity) decrease_velocity(); else /* διαφορετικά */ increase_velosity(); 9
Προεπεξεργαστής C Η προεπεξεργασία αποτελεί το πρώτο στάδιο κατά τη μεταγλώττιση ενός προγράμματος C. Δίνει τη δυνατότητα να συμπεριλάβουμε στον κώδικά μας έτοιμα header αρχεία και να ορίσουμε συμβολικά ονόματα ή μακροεντολές. Εντολές προεπεξεργαστή: #include <όνομα αρχείου επικεφαλίδας> π.χ. #include <stdio.h> /* ενσωμάτωση βιβλιοθήκης εισόδου/εξόδου */ #include "myheader.h" /* ενσωμάτωση βιβλιοθήκης χρήστη */ #define Σύμβολο Τιμή π.χ. #define A 50 /* Συμβολικό όνομα */ #define PI 3.14159 /* Συμβολικό όνομα */ 10
Βασικοί τύποι δεδομένων της C int float double char Ακέραιος αριθμός Πραγματικός αριθμός Πραγματικός αριθμός διπλής ακρίβειας Χαρακτήρας 11
Εύρος τιμών Τύπος δεδομένων Ενδεικτική Μνήμη (byte) Εύρος τιμών char 1 Το σύνολο χαρακτήρων int 4-2,147,483,648.. 2,147,483,647 float 4 Δεκαδικές τιμές από -3.4E-38 έως -3.4Ε+38 και 3.4E-38 έως 3.4Ε+38 double 8 Δεκαδικές τιμές από -1.7E-308 έως - 1.7Ε+308 και 1.7E-308 έως 1.7Ε+308 Σημείωση: Το εύρος τιμών καθώς και η δεσμευμένη μνήμη εξαρτώνται από το λειτουργικό σύστημα, τον επεξεργαστή και τον compiler. 12
Μεταβλητές Είναι μια υπολογιστική οντότητα που παριστάνει ένα μέγεθος (έννοια) που σχετίζεται με το πρόβλημα και έχει ένα συγκεκριμένο τύπο δεδομένων. Είναι διαφορετική η τιμή μιας θέσης μνήμης (τιμή μεταβλητής) και άλλο η τιμή της θέσης αυτής (διεύθυνση μεταβλητής). Δήλωση μεταβλητής (δέσμευση χώρου μνήμης) int x; Αρχικοποίηση μεταβλητής x=0; Ανάθεση τιμής x=7; (αποθήκευση της τιμής στο δεσμευμένο χώρο της μνήμης) Οι δηλώσεις των μεταβλητών πρέπει να προηγούνται της χρήσης τους. 13
Σταθερές Μεταβλητές που δεν αλλάζουν, με άμεσο τρόπο, τιμή: const int a=50; Χρήση συμβολικών ονομάτων: #define A 50 14
Εντολές εισόδου/εξόδου getchar() διαβάζει χαρακτήρα - c = getchar(); putchar() εμφανίζει χαρακτήρα - putchar(c) scanf(" ", & ) διαβάζει τιμές μεταβλητών - scanf("%d", &i) (i: ακέραιος αριθμός) - scanf("%f", &a) (a: πραγματικός αριθμός) - scanf("%c", &c) (c: χαρακτήρας) - scanf("%s", name) (name: συμβολοσειρά - πίνακας χαρακτήρων) printf(" ", ) εμφανίζει τιμές μεταβλητών ή εκφράσεων - printf("%d", i) (i: ακέραιος αριθμός) - printf("%f", a) (a: πραγματικός αριθμός) - printf("%c", c) (c: χαρακτήρας) - printf("%s",name) (name: συμβολοσειρά - πίνακας χαρακτήρων) gets(str) διαβάζει τη συμβολοσειρά str puts(str) εμφανίζει τη συμβολοσειρά str σε μια γραμμή 15
Εντολή printf printf("<κείμενο και προσδιοριστές>",<αποτελέσματα>); Κείμενο: όταν θέλουμε να εμφανίζεται μήνυμα. Προσδιοριστές: προσδιορίζουν τη μορφή εμφάνισης των αποτελεσμάτων με τη χρήση του %. Αποτελέσματα: μεταβλητές ή εκφράσεις, των οποίων εμφανίζεται η τιμή. Για παράδειγμα, έστω ότι a=2 και b=3. Η εντολή: printf("a=%d, b=%d\na*b=%d", a, b, a*b); θα εμφανίσει στην οθόνη: a=2, b=3 a*b=6 16
Εντολή scanf scanf("<προσδιοριστής>",<διεύθυνση μεταβλητής>); Προσδιοριστής: προσδιορίζει τον τύπο δεδομένων της μεταβλητής προς εισαγωγή με τη χρήση του %. Διεύθυνση μεταβλητής: η διεύθυνση μνήμης της μεταβλητής στην οποία θα αποθηκευθεί η τιμή που θα εισαχθεί. Παράδειγμα: scanf("%d",&velocity); Θα διαβάσει μια ακέραια τιμή και θα την αποθηκεύσει στη μεταβλητή velocity. Ο χαρακτήρας ampersand (&) &velocity (να γραφεί στη θέση μνήμης που έχει δεσμευτεί για τη μεταβλητή velocity η τιμή που θα εισαχθεί χρησιμοποιώντας μετατροπή με βάση τον προσδιοριστή). 17
Προσδιοριστές Προσδιοριστής Έξοδος Παράδειγμα %d ή %i Ακέραιος (int) 755 %c Χαρακτήρας (char) a %s Συμβολοσειρά test %f Πραγματικός αριθμός κινητής υποδιαστολής (float) 23.52 %lf Πραγματικός αριθμός κινητής υποδιαστολής διπλής ακρίβειας (double) 66.1584 %e Πραγματικός αριθμός κινητής υποδιαστολής σε εκθετική μορφή (float) 2.3522e+3 %u Ακέραιος χωρίς πρόσημο (unsigned int) 6916 %o Οκταδικός αριθμός (χωρίς πρόσημο) 410 %x Δεκαεξαδικός αριθμός (χωρίς πρόσημο) 3fc %X Δεκαεξαδικός αριθμός (χωρίς πρόσημο) με κεφαλαία 3FC Οι αριθμοί στους προσδιοριστές ορίζουν το εύρος εμφάνισης του δεδομένου, π.χ. %5d προσδιορίζει ακέραιο αριθμό με πλήθος ψηφίων 5, ενώ %8.3f προσδιορίζει πραγματικό αριθμό με 8 ψηφία τουλάχιστον (συμπεριλαμβανομένης και της υποδιαστολής) από τα οποία τα 3 θα είναι από το δεκαδικό μέρος. 18
Ειδικοί χαρακτήρες Χαρακτήρας Περιγραφή \n Αλλαγή γραμμής \t Χαρακτήρας οριζόντιου διαστήματος (tab) \v Χαρακτήρας κάθετου διαστήματος (vertical tab) \' Απλό εισαγωγικό \" Διπλό εισαγωγικό \? Λατινικό ερωτηματικό (?) \\ Ανάποδη πλάγια κάθετος (backslash \) Σημείωση: Εάν θέλουμε στο κείμενο της printf να τυπώσουμε ελληνικά, π.χ. printf("το αποτέλεσμα είναι: %d",result); θα πρέπει η πρώτη εντολή της main() να είναι η system("chcp 1253>nul"); (η εντολή system περιλαμβάνεται στην stdlib.h). Το τερματικό θα πρέπει να ρυθμιστεί και αυτό από τη Γραμμή Εντολών (Font->Lucida Console). 19
Άσκηση Τι θα γραφεί στη μνήμη του Η/Υ σε κάθε μια από τις παρακάτω περιπτώσεις: char i; int j; scanf("%c", &i); scanf("%d", &j); στην περίπτωση που εισάγουμε το 7 και στις δυο scanf; 20
Απάντηση Εφόσον scanf("%c", &i); και εισαχθεί το 7 τότε η μετατροπή με βάση τον προσδιοριστή "%c" για ανάγνωση χαρακτήρα θα έχει ως συνέπεια στη μνήμη να γραφεί: char 7 = ASCII(55) = [00110111] Εφόσον scanf("%d", &j); και εισάγεται το 7 τότε η μετατροπή με βάση τον προσδιοριστή "%d" για ανάγνωση ακεραίου θα έχει ως συνέπεια στη μνήμη να γραφεί: int 7 = [00000000 00000000 00000000 00000111] 21
Το 1 ο πρόγραμμα Ας υποθέσουμε ότι θέλουμε ένα πρόγραμμα που να ζητά από το χρήστη να του δώσει ένα χαρακτήρα και στη συνέχεια να τυπώσει το χαρακτήρα και τον επόμενο του, καθώς και τους ASCII κωδικούς τους. 22
Απάντηση #include <stdio.h> main() { } char xaraktiras, next_xar; printf("dose ena xaraktira:\t"); scanf("%c", &xaraktiras); /* εντολή προεπεξεργαστή για συμπερίληψη του αρχείου stdio.h */ printf("o ASCII kodikas tou xaraktira %c einai %d\n", xaraktiras, xaraktiras); next_xar = xaraktiras + 1; /* βρίσκει τον επόμενο χαρακτήρα */ printf("o ASCII kodikas tou epomenou xaraktira %c einai %d\n", next_xar, next_xar); 23
Τελεστές της C Ο τελεστής (operator) αναπαριστά μια πράξη που εκτελείται πάνω σε ένα ή περισσότερα δεδομένα, τα οποία καλούνται τελεστέοι (operands) και μπορεί να έχουν τη μορφή μεταβλητών ή σταθερών. Το αποτέλεσμα της διεργασίας είναι ο υπολογισμός μιας τιμής η οποία και αποτελεί τη τιμή της έκφρασης (expression). Οι τελεστές ταξινομούνται, ανάλογα με τον αριθμό των τελεστέων στους οποίους δρουν, σε μοναδιαίους (unary), δυαδικούς (binary) και τριαδικούς (ternary). 24
Αριθμητικοί τελεστές Πρόσθεσης (+) Αφαίρεσης (-) Πολλαπλασιασμού (*) Διαίρεσης (/) Υπολοίπου (%) 25
Μοναδιαίοι τελεστές αύξησης και μείωσης Μεταβάλλουν την τιμή μιας μεταβλητής, προσθέτοντας ή αφαιρώντας από αυτήν μια μονάδα. x=10; y=x++; /* Η μεταβλητή y θα πάρει την αρχική τιμή του x, δηλαδή 10 ενώ στη συνέχεια η μεταβλητή x θα αυξήσει την τιμή της κατά 1 και θα γίνει 11 */ x=10; y=++x; /* Η μεταβλητή x πρώτα θα αυξηθεί κατά 1 και στη συνέχεια θα δώσει την τιμή της στην y, επομένως και η y και η x θα έχουν την τιμή 11 */ Ανάλογα ισχύουν και για τον τελεστή --. 26
Άσκηση Προσδιορίστε την τιμή των x και z μετά την εκτέλεση κάθε μίας από τις παρακάτω εκφράσεις, θεωρώντας πριν την εκτέλεση της κάθε έκφρασης, ως τιμές των x και y, τα 10 και 20 αντίστοιχα: α) z = ++x + y; β) z = --x + y; γ) z = x++ + y; δ) z = x-- + y; 27
Απάντηση x=10; y=20; Έκφραση τιμή x τιμή z z = ++x + y; 11 31 z = --x + y; 9 29 z = x++ + y; 11 30 z = x-- + y; 9 30 28
Ο τριαδικός τελεστής (ternary operator)? : Ο τριαδικός τελεστής είναι πράξη και παράγει μια τιμή ως αποτέλεσμα. Ο τριαδικός τελεστής επιλέγει μία από δύο δυνατές τιμές ανάλογα με την τιμή μιας συνθήκης. συνθήκη? τιμή1 : τιμή2 Έάν η συνθήκη είναι αληθής τότε το αποτέλεσμα είναι η τιμή1, διαφορετικά η τιμή2. Παράδειγμα: int a,b; scanf("%d",&a); scanf("%d",&b); printf("ο μεγαλύτερος αριθμός είναι:%d\n",a>b?a:b); 29
Τελεστές σύνθετης απόδοσης τιμής Τελεστής Το a+=b; ισοδυναμεί με το a=a+b; Το a-=b; ισοδυναμεί με το a=a-b; Το a*=b; ισοδυναμεί με το a=a*b; Το a/=b; ισοδυναμεί με το a=a/b; Το a%=b; ισοδυναμεί με το a=a%b; Περιγραφή += Πρόσθεση και ανάθεση -= Αφαίρεση και ανάθεση *= Πολλαπλασιασμός και ανάθεση /= Διαίρεση και ανάθεση %= Υπολογισμός υπολοίπου και ανάθεση Σημείωση: Η έκφραση: x*=y+1 ισοδυναμεί με x=x*(y+1) και όχι με x=x*y+1 30
Τελεστές σύγκρισης Τελεστής Περιγραφή == Ίσο!= Διάφορο < Μικρότερο > Μεγαλύτερο <= Μικρότερο ή ίσο >= Μεγαλύτερο ή ίσο Το μηδέν (0) αντιστοιχεί στο ψευδές (false), ενώ οποιαδήποτε άλλη τιμή στο αληθές (true). Σημείωση: Ο έλεγχος ισότητας γίνεται με τον τελεστή == και όχι με τον = (καταχώρηση). 31
Άσκηση Υπολογίστε τις τιμές των παρακάτω εκφράσεων: α) count = 8 β) num == 9 γ) num = count + 3 δ) count >= num - 4 Με δεδομένο ότι οι count και num είναι ακέραιες μεταβλητές και πριν από κάθε έκφραση έχουν τιμές 8 και 12 αντίστοιχα. 32
Απάντηση count=8; num=12; Έκφραση count = 8 8 num == 9 0 (false) num = count + 3 11 count >= num - 4 1 (true) Τιμή 33
Λογικοί Τελεστές Λογικός τελεστής Σύμβολο AND && OR NOT! AND: Η τελική έκφραση είναι αληθής όταν ισχύουν όλες οι επιμέρους. OR: Η τελική έκφραση είναι αληθής όταν ισχύει τουλάχιστον μια. ΝΟΤ: Αντιστρέφει μια λογική πρόταση. 34
Άσκηση α) Πώς θα ελέγξουμε ότι η τιμή μιας μεταβλητής x είναι μεταξύ 10 και 100; β) Πώς θα ελέγξουμε ότι η τιμή μιας μεταβλητής x δεν είναι μεταξύ 10 και 100; 35
Απάντηση α) Η τιμή του x μεταξύ 10 και 100. (x>10) && (x<100) β) Η τιμή του x δεν είναι μεταξύ 10 και 100. (x<=10) (x>=100) ή!((x>10) && (x<100)) 36
Τελεστές διαχείρισης δυαδικών ψηφίων - bitwise operators Τελεστής Λειτουργία OR & AND ^ XOR ~ Συμπλήρωμα ως προς 1 >> Ολίσθηση προς τα δεξιά << Ολίσθηση προς τα αριστερά 37
Εκφράσεις - Προτεραιότητα πράξεων Πράξεις σε εκφράσεις (η εκτέλεση γίνεται από τα αριστερά προς τα δεξιά - προσεταιριστικότητα). Χρησιμοποιούμε παρενθέσεις: είτε για να προσδιορίσουμε συγκεκριμένη σειρά εφαρμογής, όπως στην έκφραση (2-3)*4, είτε για να αυξήσουμε την αναγνωσιμότητα μιας έκφρασης όπως στην 2-(3*4) (πλεονασμός) Προτεραιότητα Τελεστές Υψηλή ( ) [ ] ->! ~ ++ -- * & * / % - + << >> < <= > >= ==!= & ^ &&? Χαμηλή = += -= *= /= 38
Δήλωση πίνακα: Πίνακες <Τύπος στοιχείων> <Όνομα πίνακα>[<διαστάσεις πίνακα>]; Για παράδειγμα: int students[100]; char names[8][8]; O students είναι μονοδιάστατος πίνακας 100 ακεραίων. Ο names είναι δισδιάστατος πίνακας (8 επί 8) χαρακτήρων (στην ουσία 8 ονομάτων - συμβολοσειρών). Η θέση του πρώτου στοιχείου κάθε διάστασης είναι υποχρεωτικά η 0. Έτσι, τα στοιχεία του πίνακα students είναι τα students[0], students[1], students[2],, students[98], students[99] και τα στοιχεία του πίνακα names είναι τα names[0][0], names[0][1],, names[0][7], names[1][0],, names[7][7]. Το όνομα του πίνακα ισοδυναμεί με τη διεύθυνση του πρώτου στοιχείου. Σημείωση: Το 1ο στοιχείο ενός πίνακα στην C αποθηκεύεται πάντα στη θέση 0. 39
Ιδιότητες πίνακα Μία ή περισσότερες διαστάσεις: Κάθε διάσταση έχει συγκεκριμένα όρια. Κάθε στοιχείο χαρακτηρίζεται από τη θέση του στον πίνακα, που αποτελείται από τόσες τιμές όσες και οι διαστάσεις. Ομοειδή στοιχεία: Όλα ακέραιοι ή όλα χαρακτήρες (συγκεκριμένου τύπου). Γραμμική δομή: Υπάρχει «λογική» διάταξη των στοιχείων, δηλαδή κάθε στοιχείο έχει ένα επόμενο (πλην του τελευταίου) και ένα προηγούμενο (πλην του πρώτου). Αποθήκευση στοιχείων σε συνεχόμενες θέσεις: Τυχαία προσπέλαση (εύρεση κάθε στοιχείου με υπολογισμό, χρόνος ανεξάρτητος της θέσης). 40
Αρχικοποίηση πίνακα Αρχικοποίηση O τελεστής ανάθεσης μπαίνει μετά τη διάσταση του πίνακα και ακολουθεί μέσα σε αγκύλες η λίστα με τις τιμές που θα αποδοθούν στα στοιχεία του πίνακα. Η πρόταση float num[5] = {1, 2, 3.5, 4, 5}; αρχικοποιεί όλα τα στοιχεία του πίνακα num με τις τιμές της λίστας, ενώ η πρόταση float num[5] = {1, 2, 3.5}; αρχικοποιεί τα 3 πρώτα στοιχεία δηλαδή τα num[0], num[1] και num[2] με τις τιμές της λίστας και τα υπόλοιπα με μηδέν. Η πρόταση float num[5] ={[0]=1, [2]=3.5}; αρχικοποιεί το 1ο και 3ο στοιχείο με τις τιμές 1 και 3.5 αφήνοντας τα υπόλοιπα 0, float num[2][2]={ 1, 2, 3.5, 4}; αρχικοποιεί έναν πίνακα δυο διαστάσεων γραμμή προς γραμμή, float num[2][2]={{1, 2},{3.5, 4}}; αρχικοποιεί έναν πίνακα δυο διαστάσεων γραμμή προς γραμμή διαχωρίζοντας την κάθε γραμμή. Ανάθεση τιμών μπορεί να γίνει με την εισαγωγή δεδομένων από το πληκτρολόγιο ή με τη χρήση επανάληψης. 41
Δείκτες Δείκτης είναι μια μεταβλητή που η τιμή της είναι η διεύθυνση μιας άλλης μεταβλητής. Η δήλωση μιας μεταβλητής δείκτη για την γλώσσα C έχει την παρακάτω μορφή: <όνομα τύπου> *<όνομα μεταβλητής δείκτη>; Το * ονομάζεται τελεστής έμμεσης αναφοράς. Η δήλωση: int *p; σημαίνει ότι η μεταβλητή p «δείχνει» σε ακέραιο ή το περιεχόμενο της θέσης μνήμης που δείχνει η p είναι int. 42
Αρχικοποίηση και ανάθεση τιμής σε δείκτη Αρχικοποίηση δείκτη Μετά τη δήλωση του, ένας δείκτης πρέπει να δείχνει σε μια θέση μνήμης που ανήκει στο πρόγραμμα. Για το λόγο αυτό δίνουμε συνήθως τιμή στο δείκτη ταυτόχρονα με τη δήλωση του: <όνομα τύπου> *<όνομα δείκτη> = <διεύθυνση>; όπου <διεύθυνση> μπορεί να είναι: α) μία άμεση διεύθυνση όπως στην πρόταση: int *p = 1000; /*ο δείκτης p δείχνει ακέραιο στη θέση μνήμης 1000*/ β) η διεύθυνση μιας μεταβλητής ακέραιου τύπου την οποία παίρνουμε εφαρμόζοντας μπροστά από μια μεταβλητή τον τελεστή διεύθυνσης &: int *p = # /*εφόσον η num είναι int*/ Ανάθεση τιμής Η ανάθεση τιμής γίνεται με ίδιο τρόπο: <όνομα δείκτη> = <διεύθυνση>; Για παράδειγμα p = 1000; ή p = # (το p ισούται με τη διεύθυνση) (το *p ισούται με την τιμή του num) 43
Άσκηση Τι θα εμφανίσουν οι ακόλουθες εντολές; int count = 10, *temp=null, sum = 0; temp = &count; *temp = 20; temp = *temp = count; printf("count = %d, *temp = %d, sum = %d\n", count, *temp, sum ); 44
Απάντηση Εντολή Μεταβλητή Ενδεικτική διεύθυνση μνήμης int count = 10, *temp=null, sum = 0; Τιμή count 1000 10 temp 1100 NULL sum 1200 0 temp = &count; *temp = 20; temp = *temp = count; count 1000 10 temp 1100 1000 sum 1200 0 count 1000 20 temp 1100 1000 sum 1200 0 count 1000 20 temp 1100 1200 sum 1200 0 count 1000 20 temp 1100 1200 sum 1200 20 Θα εμφανίσουν: count = 20, *temp = 20, sum = 20 45
Συμβολοσειρές Η C για να αποθηκεύσει και να διαχειριστεί συμβολοσειρές (string) χρησιμοποιεί τον τύπο του πίνακα. Μια συμβολοσειρά για την C είναι ένας πίνακας χαρακτήρων που τερματίζει με τον μηδενικό (null) χαρακτήρα. Ο μηδενικός χαρακτήρας έχει ASCII κωδικό 0 και αναπαρίσταται με την ακολουθία διαφυγής '\0'. Δήλωση: char book_title[30], isbn[12]; Σημαίνει τη δήλωση δύο μεταβλητών για αποθήκευση του τίτλου και του ISBN κωδικού ενός βιβλίου (μεγέθους 30 και 12 χαρακτήρων, αντίστοιχα). Αρχικοποίηση: Γενικός κανόνας απόδοσης αρχικής τιμής σε πίνακα: char isbn[] = {'0','-','3','8','7','-','9','7','6','-', '1','\0'}; Στην πράξη γίνεται χρήση αλφαριθμητικής σταθεράς: char isbn[] = "0-387-976-1"; (το null προστίθεται αυτόματα) Σημείωση: Ο μηδενικός χαρακτήρας '\0' καταλαμβάνει πάντα μια θέση στον πίνακα της συμβολοσειράς. Επομένως, μια συμβολοσειρά Ν θέσεων περιέχει Ν-1 διαθέσιμες θέσεις για χαρακτήρες. 46
Άσκηση Δώστε την έκφραση για εισαγωγή τιμής από το χρήστη στη μεταβλητή isbn και την πρόταση για εκτύπωση της μεταβλητής isbn. 47
Απάντηση char isbn[12]; scanf("%s",isbn); printf("o ISBN κωδικός του βιβλίου είναι : %s\n", isbn); Θα πρέπει όμως να τονίσουμε ότι: α) Δεν χρειάζεται ο τελεστής & πριν από το όνομα της μεταβλητής isbn όπως συνέβαινε για άλλους τύπους, όπως π.χ. του χαρακτήρα και του ακεραίου σε ανάλογη περίπτωση, γιατί το όνομα του πίνακα αναπαριστά την διεύθυνση του πρώτου στοιχείου του πίνακα. β) H εισαγωγή περισσοτέρων από 11 χαρακτήρων έχει σαν αποτέλεσμα να καταστραφούν τα περιεχόμενα των επομένων θέσεων μνήμης που πιθανότατα αποτελούν τιμές άλλων μεταβλητών. Αυτό έχει απρόβλεπτα αποτελέσματα στην παραπέρα λειτουργία του προγράμματος. γ) Η ανάγνωση συμβολοσειρών μπορεί να γίνει και με τη συνάρτηση gets αντί της scanf. Η τελευταία δε δέχεται συμβολοσειρές που περιέχουν κενούς χαρακτήρες (spaces). 48
Η εντολή if if (συνθήκη) εντολή; if (συνθήκη) εντολή1; else εντολή2; Αν αντί μιας εντολής έχουμε περισσότερες τότε χρησιμοποιούμε σύνθετη εντολή, δηλαδή περικλείουμε τις εντολές σε άγκιστρα. { /*Συμβολίζει την αρχή*/ } /*Συμβολίζει το τέλος*/ Εντολές στη C if (συνθήκη) { Εντολές; } Παράδειγμα C if (a>0) { a = 2*a; printf("%d",a); } 49
Η πολλαπλή εντολή if if (συνθήκη1) εντολή1; else if (συνθήκη2) εντολή2; else if (συνθήκη3) εντολή3; else εντολή; Παράδειγμα C if (vathmos >= 8.5) printf("arista\n"); else if (vathmos >= 6.5) printf("poly kala\n"); else if (vathmos >= 5) printf("kala\n"); else printf("mi provivasimos\n"); 50
Η εντολή switch switch / case / default Παρόμοια με τις πολλαπλές if else εντολές είναι η εντολή switch. Είναι χρήσιμη όταν χρειαζόμαστε διακλαδώσεις εντολών για την εκτίμηση μιας πρότασης. Επιβάλλεται η χρήση break για τερματισμό περίπτωσης. switch (ch) { case 'a': /* if(ch=='a') */ printf("alpha\n"); break; case 'b': /* else if(ch=='b') */ case 'c': /* else if(ch=='c') */ printf("beta or c\n"); break; default: /* else*/ printf("other\n"); } 51
Η εντολή for for (έκφραση 1; έκφραση 2; έκφραση 3) εντολή1; Η έκφραση 1 δίνει αρχική τιμή στη μεταβλητή (στο μετρητή). Η έκφραση 2 είναι μια λογική έκφραση που καθορίζει αν θα συνεχιστεί η επανάληψη. Η έκφραση 3 καθορίζει τον τρόπο αύξησης ή μείωσης της μεταβλητής. Η εντολή1 είναι αυτή που επαναλαμβάνεται. Αν αντί μιας εντολής θέλουμε να επαναλαμβάνονται περισσότερες τότε τοποθετούμε άγκιστρα. Παράδειγμα C for (i=0; i<=5; i++) { a = 2*i; printf("%d",a); } 52
Παραδείγματα Αρνητικό βήμα: for(i=5;i>=0;i--){ a = 2*i; printf("%d\n",a); } Χωρίς κάποια μέλη της for: int i=0; for(;i<10;){ } printf("%d ", i); i++; 53
Η εντολή for σε μονοδιάστατους πίνακες Διάβασμα πίνακα: int i,x[n]; /* Δηλώσεις. Το Ν είναι συμβολική τιμή */ for (i=0;i<n;i++)/* Διάβασμα πίνακα από το χρήστη*/ { printf("dose to %d stoixeio: ",i+1); scanf("%d",&x[i]); } Εκτύπωση πίνακα: for (i=0;i<n;i++) /* Εκτύπωση πίνακα*/ printf("%d\t",x[i]); Εύρεση μέγιστου στοιχείου πίνακα: int max=x[0]; /*Αρχικοποίηση μεταβλητής max*/ for (i=1;i<n;i++) { if (x[i]>max) max=x[i]; } 54
Η εντολή for σε δισδιάστατους πίνακες Διάβασμα πίνακα: int i, j, x[n][m]; /* Δηλώσεις. Τα Ν, Μ είναι συμβολικές τιμές */ for (i=0; i<n; i++) /* Διάβασμα πίνακα (γραμμές) */ for (j=0; J<M; j++) /* (στήλες) */ { printf("dose to stoixeio x[%d][%d]: ", i+1, j+1); scanf("%d", &x[i][j]); } Εκτύπωση πίνακα: for (i=0; i<n; i++) { for (j=0; j<m; j++) printf("%d\t", x[i][j]); printf("\n"); /*Αλλαγή γραμμής*/ } 55
Η εντολή for με χαρακτήρες #include <stdio.h> main() { char i; printf("the alphabet, in lower-case:\n"); for(i='a'; i<='z'; i++) printf("%c", i ); printf("\nthe alphabet in upper-case:\n"); for(i='a'; i<='z'; i++) printf("%c", i ); } 56
Παράδειγμα - Μέσος όρος στοιχείων πίνακα #include <stdio.h> #define N 3 /* Αριθμός γραμμών του πίνακα */ #define M 2 /* Αριθμός στηλών του πίνακα */ main() { int sum=0, i, j, x[n][m]; /* Δήλωση μεταβλητών */ float av; for (i=0; i<n; i++) /* Ανάγνωση πίνακα */ for (j=0; j<m; j++) { printf("dose to stoixeio x[%d][%d]: ", i, j); scanf("%d",&x[i][j]); } for (i=0; i<n; i++) for (j=0; j<m; j++) sum+=x[i][j]; /*Υπολογισμός αθροίσματος των στοιχείων */ av=(float)sum/(n*m); /* Υπολογισμός μέσου όρου */ printf("\no mesos oros tou pinaka = %.2f \n",av); } 57
Η εντολή while while (συνθήκη) εντολή1; Η εντολή1 είναι αυτή που επαναλαμβάνεται ενόσω (while) ισχύει η συνθήκη. Αν αντί μιας εντολής θέλουμε περισσότερες τότε τοποθετούμε άγκιστρα. Παράδειγμα C i=1; while (i<=10) { Εντολές; i++; } 58
Η εντολή do..while do εντολή1; while (συνθήκη); και πάλι αν αντί μιας εντολής θέλουμε να επαναλαμβάνονται περισσότερες τότε τοποθετούμε άγκιστρα. Παράδειγμα C i=0; do { Εντολές; i++; } while(i<5); Σημείωση: Στη do - while θα εκτελεσθεί τουλάχιστον μια επανάληψη, ενώ στη while μπορεί να μην εκτελεσθεί καμία. 59
Αμυντικός προγραμματισμός Παράδειγμα εισαγωγής θετικού ακεραίου: do { printf("dose thetiko akeraio:\n"); scanf("%d",&a); if (a<=0) printf("parakalw eisagete thetiko arithmo\n"); } while (a<=0); ή εναλλακτικά: printf("dose thetiko akeraio:\n"); scanf("%d",&a); while (a<=0){ printf("parakalw eisagete thetiko arithmo\n"); scanf("%d",&a); } 60
Παράδειγμα - Συγχώνευση πινάκων #include <stdio.h> #define N 5 #define M 4 main() { /* Βοηθητικές μεταβλητές */ int i, j, k, c[9]; /* Ορισμός πίνακα a */ int a[n]={1, 4, 6, 33, 34}; /* Ορισμός πίνακα b */ int b[m]={2, 3, 7, 8}; i = j = k = 0; while (i<n && j<m) { if (a[i]<b[j]) c[k++]=a[i++]; else c[k++]=b[j++]; } if(i<n) { int t; for (t=i; t<n; t++) c[k++]=a[i++]; } else { int t; for (t=j; t<m; t++) c[k++]=b[j++]; } printf("merged Array C:\n"); /* Εκτύπωση συγχωνευμένου πίνακα */ for (k=0; k<(n+m); k++) printf("\n %d ",c[k]); } 61
Δομές Μια Δομή (structure) ομαδοποιεί μεταβλητές διαφορετικών τύπων δεδομένων. Με απλά λόγια μια δομή μαζεύει διαφορετικά δεδομένα με τέτοιο τρόπο, που μπορούν να αναφερθούν ως μια μονάδα. Τα δεδομένα μιας δομής ονομάζονται μέλη της δομής. Κάθε μέλος της δομής έχει το δικό του όνομα και το δικό του τύπο. Η γενική μορφή δήλωσης μιας δομής είναι: struct <όνομα δομής > { <τύπος 1-ου μέλους> <όνομα 1-ου μέλους>; <τύπος 2-ου μέλους> <όνομα 2-ου μέλους>; <τύπος 3-ου μέλους> <όνομα 3-ου μέλους>;. <τύπος n-ου μέλους> <όνομα n-ου μέλους>; }; Η δεσμευμένη λέξη struct εισάγει τη δήλωση της δομής η οποία ακολουθείται από την ετικέτα δομής (structure tag) (όνομα δομής). 62
Δήλωση μεταβλητής τύπου δομής Η δήλωση μεταβλητών ακολουθεί τον γενικό κανόνα δηλώσεων της C και γίνεται με πρόταση δήλωσης της μορφής: struct <όνομα δομής > <όνομα μεταβλητής >; ή struct <όνομα δομής > λίστα-ονομάτων-μεταβλητών ; Εναλλακτικά μπορεί να γίνει δήλωση μεταβλητής ή μεταβλητών ταυτόχρονα με τον ορισμό της δομής όπως παρακάτω (δομή με τις συντεταγμένες ενός σημείου στο επίπεδο): struct point{ float x; float y; } pt_1, pt_2; ή struct point pt_1, pt_2; Οι pt_1 και pt_2 είναι τα ονόματα δύο διαφορετικών δομών. Η κάθε μία δομή έχει μέλη τις συντεταγμένες ενός σημείου. 63
Αναφορά στα μέλη δομής Η αναφορά σε ένα μέλος μιας συγκεκριμένης δομής μέσα σε μια παράσταση γίνεται ως: όνομα-δομής.μέλος Έτσι, στο συγκεκριμένο παράδειγμα struct point{ float x; float y; }; struct point pt_1, pt_2; αναφερόμαστε στις συντεταγμένες x και y των pt_1 και pt_2 ως: συντεταγμένη x του pt_1: pt_1.x συντεταγμένη y του pt_1: pt_1.y συντεταγμένη x του pt_2: pt_2.x συντεταγμένη y του pt_2: pt_2.y Ο τελεστής μέλους δομής. συνδέει το όνομα της δομής με το όνομα του μέλους. Η είσοδος τιμών στα μέλη μιας δομής μπορεί να γίνει και μέσω της scanf ή και με απευθείας ανάθεση τιμής στα μέλη. 64
Πίνακας δομής Στη C μπορούμε να δηλώσουμε έναν πίνακα τα στοιχεία του οποίου είναι δομές. Οι πίνακες δομών χρησιμοποιούνται για μια εύκολη κατασκευή μιας Βάσης Δεδομένων. Ας δούμε ένα παράδειγμα κατασκευής μιας βάσης δεδομένων για πρωτοετείς φοιτητές με τον βαθμό τους στο μάθημα Προγραμματισμός σε C: struct student{ char eponimo[30]; char onoma[20]; int code; float vathmos; }; struct student foititis[200]; 65
Ανάθεση τιμής στα μέλη πίνακα δομής strcpy(foititis[56].eponimo, "Νικολάου"); strcpy(foititis[56].onoma, "Γιάννης"); foititis[56].code=1214; foititis[56].vathmos=8.5; Διάβασμα - εκτύπωση τέτοιων δομών με εντολή επανάληψης. H έκφραση foititis[0].eponimo αναφέρεται στο επώνυμο του πρώτου στοιχείου του πίνακα (1ο φοιτητή), ενώ η foititis[1].eponimo αναφέρεται στο 2ο φοιτητή. Η έκφραση: foititis[1].eponimo[0]αναφέρεται στον πρώτο χαρακτήρα του επωνύμου του δεύτερου στοιχείου του πίνακα foititis. Σημείωση: Η strcpy() αντιγράφει το περιεχόμενο μιας συμβολοσειράς σε μια άλλη (απαιτείται η δήλωση #include <string.h> στην αρχή του προγράμματος). 66
Άσκηση Αναπτύξτε ένα πρόγραμμα που να εισάγει από το πληκτρολόγιο τα στοιχεία 100 φοιτητών. Για διευκόλυνση χρησιμοποιήστε τη δομή: struct student{ char eponimo[30]; char onoma[20]; int code; float vathmos; }; 67
Απάντηση #include <stdio.h> #define N 100 struct student { char eponimo[30]; char onoma[20]; int code; float vathmos; }; main() { struct student foitites[n]; int i; for (i=0; i<n; i++) { printf("dose eponimo tou %dou student\n",i+1); scanf("%29s",foitites[i].eponimo); printf("dose onoma tou %dou student\n",i+1); scanf("%19s",foitites[i].onoma); printf("dose kodiko tou %dou student\n",i+1); scanf("%d",&foitites[i].code); printf("dose bathmo tou %dou student\n",i+1); scanf("%f",&foitites[i].vathmos); } } 68
Συναρτήσεις Δήλωση συνάρτησης: <τύπος αποτελέσματος> <όνομα συνάρτησης> (<τύπος και όνομα παραμέτρων>) { <δηλώσεις τοπικών μεταβλητών> εντολές return ; } Αν δεν επιστρέφεται αποτέλεσμα, η συνάρτηση ορίζεται ως void και δεν υπάρχει η εντολή return στο τέλος. Παράδειγμα: int max(int x, int y) { return (x>y? x : y); } Παράδειγμα κλήσης συνάρτησης: printf("%d", max(a,b)); Σημείωση: Εάν μια συνάρτηση επιστρέφει τιμή (όχι void) τότε θα πρέπει σε κάθε περίπτωση η ροή του κώδικα να οδηγεί σε εντολή return. 69
#include <stdio.h> Παράδειγμα συνάρτησης int sum(int n, int m); /* δήλωση συνάρτησης */ main() { int x, y; printf("enter the first number:\n"); scanf("%d", &x); printf("enter the second number:\n"); scanf("%d", &y); printf("the sum is %d\n", sum(x,y)); } int sum(int n, int m) { return (n + m); /* επιστροφή αθροίσματος των δυο παραμέτρων */ } 70
Μεταβίβαση πινάκων σε συναρτήσεις Πίνακες μίας διάστασης #include <stdio.h> double getaverage(int array[], int size); /* Ισοδύναμα double getaverage(int *array, int size); */ main (){ /* Ορισμός πίνακα */ int balance[5] = {30, 15, 5, 0, 6}; double avg; /* Μεταβιβάζουμε τον πίνακα στη συνάρτηση ως δείκτη */ avg = getaverage(balance, 5); printf( "Average value is: %f ", avg ); } double getaverage(int array[], int size){ int i; } double sum=0.0; for (i = 0; i < size; i++) sum += array[i]; return sum/size; 71
Μεταβίβαση πινάκων σε συναρτήσεις Πίνακες δύο διαστάσεων #include <stdio.h> double getaverage(int array[][5], int size); /* Η δεύτερη διάσταση του πίνακα δίνεται ρητά */ main (){ /* Ορισμός πίνακα */ int balance[2][5] = {{30, 15, 5, 0, 6},{2, 5, 6, 6, 10}}; double avg; /* Μεταβιβάζουμε τον πίνακα στη συνάρτηση ως δείκτη, καθώς και τη 2η διάσταση */ avg = getaverage(balance, 2); printf( "Average value is: %f ", avg ); } double getaverage(int array[][5], int size){ int i, j; double sum=0.0; for (i=0; i<size; i++) for (j=0; j<5; j++) sum += array[i][j]; return sum/(size*5); } 72
Συμβουλές καλού προγραμματισμού Τα ονόματα μεταβλητών πρέπει να έχουν σχέση με αυτό που αντιπροσωπεύουν, να αποφεύγονται ονόματα ενός γράμματος (a, b, i, j, k, m, x, y) που δεν βοηθούν την αναγνωσιμότητα του κώδικα. Προσοχή στις ακραίες τιμές (π.χ. αν θέλουμε θετικό αριθμό, στον αμυντικό προγραμματισμό ελέγχουμε αν είναι <=0). Δεν συγκρίνουμε μεταβλητές που δεν έχουν πάρει ακόμη τιμή. Όταν έχουμε απόδοση τιμής με αναδρομική σχέση (π.χ. sum := sum + a), απαιτείται αρχικοποίησης της τιμής. Στον αμυντικό προγραμματισμό δεν αρκεί ο έλεγχος (με εντολή if) και το ξαναδιάβασμα, απαιτείται επανάληψη (while) μέχρι να διαβαστεί «κανονική» τιμή. Τα μηνύματα πριν την εισαγωγή τιμών πρέπει να είναι σαφή. Π.χ. όχι «Δώσε αριθμό», αλλά «Δώσε θετικό αριθμό για πρόσθεση». Όταν έχουμε πίνακες, ΑΠΑΙΤΕΙΤΑΙ χρήση επαναληπτικών εντολών (for). Ακόμα και αν ο πίνακας έχει μικρό μέγεθος, θα πρέπει να τον χειριζόμαστε σαν να ήταν τεράστιος, για λόγους επαναχρησιμοποίησης και όγκου προγράμματος. 73