Εργαστήριο Δομημένος Προγραμματισμός (C#) Τμήμα Μηχανολογίας Νικόλαος Ζ. Ζάχαρης Καθηγητής Εφαρμογών Σκοπός Nα κατασκευάσουν λίστες από δεδομένα. Να κατασκευάσουν συναρτήσεις με λίστες. Να διαβάσουν και να αποθηκεύσουν δεδομένα σε αρχεία. Εκφώνηση Ο αισθητήρας των θερμοκρασιών ενός μηχανήματος καταγράφει σε ένα αρχείο, όλες τις θερμοκρασίες που υπερβούν την τιμή 80 καθώς και την ώρα που συνέβη το γεγονός. Να δημιουργήσετε ένα πρόγραμμα που θα α) διαβάζει τις τιμές του αρχείου β) θα υπολογίζει το μέσο όρο καθώς την μεγαλύτερη θερμοκρασία, γ) θα καταγράφει στο αρχείο results.txt, το πλήθος των τιμών του αρχείου, το μέσο όρο τους, την μεγαλύτερη τιμή καθώς και την χρονική στιγμή που συνέβη το γεγονός. Επίλυση Πατήστε το κουμπί Έναρξη (Start) και εν συνεχεία Όλα τα Προγράμματα (All Programs) και μέσα στο φάκελο Βοηθήματα (Accessories) επιλέξτε το πρόγραμμα Σημειωματάριο (NotePad). Στο παράθυρο κειμένου του προγράμματος πληκτρολογήστε τις τιμές, όπως εμφανίζονται στην παρακάτω εικόνα : και αποθηκεύστε το αρχείο στο δίσκο C με το όνομα values.txt. Τα δεδομένα είναι 1
αποθηκευμένα ανά γραμμή, όπου στην πρώτη καταγράφετε η θερμοκρασία και στην δεύτερη, η χρονική στιγμή. Δημιουργείστε στο περιβάλλον της C# ένα νέο έργο (File New Project) τύπου Windows Application με όνομα Lists. Σχεδιάστε την διεπαφή της εφαρμογής όπως παρακάτω : Επιλέξτε ένα προς ένα τα αντικείμενα της φόρμας και στο παράθυρο Properties αλλάξτε τις παρακάτω ιδιότητες : Η φόρμα Form1 Name frmfiles Το όνομα του Text Διαχείριση Αρχείων Η γραμμή τίτλου MaximizeButton False Για να μην FormBorderStyle FixedSingle αλλάζει το μέγεθος της φόρμας. Το κουτί κειμένου textbox1 Name txtdisplay Το όνομα του Multiline True Περισσότερες από μια γραμμές κειμένου. Text Το πρώτο κουμπί εντολών 2
button1 Name btnread Το όνομα του Text Ανάγνωση Το δεύτερο κουμπί εντολών button2 Name btnwrite Το όνομα του Text Αποθήκευση To παράθυρο διαλόγου για το άνοιγμα ενός αρχείου openfiledialog1 Name openfiledialog1 Το όνομα του Filename Title Επιλέξτε αρχείο θερμοκρασιών To παράθυρο διαλόγου για την αποθήκευση ενός αρχείου savefiledialog1 Name savefiledialog1 Το όνομα του Filename Title Επιλέξτε αρχείο εξόδου Η ανάγνωση πληροφοριών από ένα αρχείο κειμένου δεν προϋποθέτει προηγούμενη γνώση για το πλήθος των τιμών (ή γραμμών) που βρίσκονται μέσα στο αρχείο. Για παράδειγμα, αν ένα αρχείο έχει ακεραίους αριθμούς τους οποίους θέλετε να διαβάσετε και να αποθηκεύσετε σε ένα πίνακα τότε το πρόβλημα που έχετε να αντιμετωπίσετε είναι πόσες θέσεις θα δεσμεύσετε για το πίνακα. Αν δημιουργήσετε το πίνακα start με 5 θέσεις τότε θα πρέπει να δηλώσετε int [] start new int [5]; Αν το αρχείο έχει 3 τιμές, λιγότερες από τις συνολικές θέσεις του πίνακα, τότε θα πρέπει να έχετε και ένα μετρητή ώστε να γνωρίζετε τις θέσεις του πίνακα που έχουν 3
πάρει τιμές από το αρχείο. Ο μετρητής είναι απαραίτητος αφού η μέθοδος Length του πίνακα start δείχνει πάντα 5, όσες είναι οι συνολικές θέσεις του πίνακα. Οπότε δηλώνετε και ένα μετρητή που θα έχει αρχική τιμή 0 και με την ανάγνωση ενός αριθμού τότε θα αυξάνει την τιμή του κατά 1. int couter = 0; Στην περίπτωση που θα πρέπει να αποδώσετε μια επιπλέον τιμή πέραν από αυτές που μπορεί να αποθηκεύσει ο πίνακας, (π.χ. το αρχείο έχει 6 ή περισσότερες τιμές), τότε θα πρέπει να κάνετε τα εξής : α) να δημιουργήσετε ένα νέο πίνακα π.χ. temp, o οποίος θα έχει τόσες θέσεις όσες και ο start β) να αντιγράψετε το πίνακα start στο temp γ) να δημιουργήσετε καινούργιο πίνακα με το όνομα start, o οποίος θα έχει όσες ήταν οι προηγούμενες θέσεις του, συν μια. δ) να αντιγράψετε το πίνακα temp στο start ε) να βάλετε στην τελευταία θέση του start την επιπλέον τιμή. Όλη η ανωτέρω διαδικασία, της αποθήκευσης της τιμής της μεταβλητής x στο πίνακα start, υλοποιείται προγραμματιστική από το παρακάτω κώδικα : int x = 44; if(counter < start.length) { start[counter] =x; counter += 1; } else { int[] temp = new int[start.length]; // Βήμα α for(int i= 0; i< start.length; i++) { // Βήμα β temp[i] = start[i]; 4
} start = new int[counter + 1]; // Βήμα γ } for(int i= 0; i< temp.length; i++) // Βήμα δ { start[i] = temp[i]; } start[counter] = x; // Βήμα ε counter += 1; Όλες οι ανωτέρω εργασίες είναι αρκετά χρονοβόρες ειδικά όταν ο πίνακας είναι αρκετά μεγάλος. Επιπλέον σε ένα πίνακα όλα τα στοιχεία θα πρέπει να είναι του ιδίου τύπου.η C# διαθέτει την βιβλιοθήκη System.Collections η οποία υποστηρίζει όλες τις κλάσεις και τα εργαλεία για την δημιουργία, διαχείριση και αποθήκευση πληροφοριών σε αποθηκευτικούς χώρους που αυξάνουν δυναμικά την χωρητικότητα τους. Για παράδειγμα η κλάση ArrayList, διαθέτει τα πλεονεκτήματα ενός πίνακα αφού η πρόσβαση γίνεται με την χρήση ενός δείκτη και επιπλέον υποστηρίζει τις μεθόδους : Add : για την προσθήκη ενός στο ArrayList Count : επιστρέφει το πλήθος των στοιχείων του ArrayList RemoveΑt : διαγράφει ενός αντικείμενο από το ArrayList Clear : καθαρίζει τα περιεχόμενα του ArrayList Για την δημιουργία ενός ArrayList με το όνομα test θα πρέπει να γράψετε : ArrayList test = new ArrayList(); Για την προσθήκη των ακεραίων τιμών 3, 7 και 4 θα πρέπει να χρησιμοποιήσετε την μέθοδο Add, όπως παρακάτω : test.add(3); test.add(7); 5
test.add(4); Η πρόσβαση στα στοιχεία ενός ArrayList γίνεται όπως και σε ένα πίνακα με την χρήση ενός ακεραίου δείκτη : α) Απόδοση της τιμής 7 στη μεταβλητή x int x = (int) test[1]; β) Υπολογισμός του αθροίσματος των στοιχείων του test int sum = 0; for(int i = 0; i < test.count; i++) { sum = sum + (int) test[i]; } γ) Διαγραφή του στοιχείου στην θέση 1 test.removeat(1); Σε ένα ArrayList όταν διαβάζετε ένα αντικείμενο που έχετε τοποθετήσει σε μια θέση του, όπως στο α και β παράδειγμα, θα πρέπει να προηγείται μέσα σε παρενθέσεις ο τύπος δεδομένων, στην ανωτέρω περίπτωση (int). Αυτή η ενέργεια ονομάζετε μετατροπή τύπου (type casting) και είναι απαραίτητη γιατί το ArrayList σας επιτρέπει να αποθηκεύσετε διαφορετικού τύπου αντικείμενα, για παράδειγμα : ArrayList test = new ArrayList(); test.add(3); test.add("hello"); int f = (int) test[0]; string x = (string) test[1]; Άρα η μετατροπή τύπου είναι αναγκαία για την ανάγνωση της εκάστοτε τιμής από 6
μια θέση του ArrayList. Για το πρόβλημα της ανάγνωσης των διαφορετικών τιμών από το αρχείο values.txt θα χρησιμοποιήσετε ένα ArrayList, στο οποίο οι τιμές θα αποθηκεύονται ανά ζεύγη, αρχικά η θερμοκρασία και μετά η χρονική στιγμή. Αυτή η ενέργεια θα επαναλαμβάνετε για όλα τα ζεύγη τιμών που έχει το αρχείο. Επιλέξτε το κουμπί εντολών btnread και στο παράθυρο Properties κάντε κλικ στο εικονίδιο με το κεραυνό για να εμφανιστούν τα γεγονότα. Βρείτε το γεγονός Click κάντε διπλό κλικ πάνω για να εμφανιστεί το παράθυρο με το προγραμματισμό των γεγονότων. Πληκτρολογείστε μέσα στο παράθυρο του κώδικα τις παρακάτω εντολές : Πίνακας 1. Ο κώδικας της εφαρμογής 1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Text; 7 using System.Windows.Forms; 8 using System.IO; 9 using System.Collections; 10 11 namespace Lists 12 { 13 public partial class frmlist : Form 14 { 15 ArrayList values = new ArrayList(); 16 17 public frmlist() 18 { 19 InitializeComponent(); 20 } 21 22 23 int findmaxpos(arraylist x) 24 { 25 int pos = -1; 26 27 if (x.count > 0) 28 { 29 pos = 0; 30 float max = (float)x[0]; 31 int i; 32 33 for (i = 2; i < x.count; i=i+2) 7
34 { 35 if (max < (float) x[i]) 36 { 37 max = (float) x[i]; 38 pos = i; 39 } 40 } 41 } 42 return pos; 43 } 44 45 void FillArrays(string filename, ArrayList A) 46 { 47 StreamReader fin = new StreamReader(fileName); 48 string Aline; 49 string Bline; 50 51 for (; ; ) 52 { 53 Aline = fin.readline(); 54 Bline = fin.readline(); 55 if ( (Aline == null) (Bline == null) ) 56 { 57 fin.close(); 58 break; 59 } 60 else 61 { 62 float temp = float.parse(aline.tostring()); 63 A.Add(temp); 64 A.Add(Bline); 65 } 66 } 67 } 68 69 70 string FormatMessage(ArrayList x) 71 { 72 string line = ""; 73 int ans = findmaxpos(x); 74 if (ans >= 0) 75 { 76 line = "Μέγιστη Θερμοκρασια : " + (float)x[ans] + "\r\n"; 77 78 line = line + "Χρονική Στιγμή : " + (String)x[ans+1] + "\r\n"; 79 80 line = line + "Πλήθος Τιμών : " + x.count; 81 82 } 83 8
84 return line; 85 } 86 87 88 private void btnread_click(object sender, EventArgs e) 89 { 90 if (openfiledialog1.showdialog() == DialogResult.OK) 91 { 92 FillArrays(openFileDialog1.FileName, values); 93 94 txtresult.text = FormatMessage(values); 95 } 96 } 97 98 private void btnwrite_click(object sender, EventArgs e) 99 { 100 if (savefiledialog1.showdialog() == DialogResult.OK) 101 { 102 StreamWriter fout = new StreamWriter(saveFileDialog1.FileName); 103 104 fout.writeline(formatmessage(values)); 105 106 fout.close(); 107 } 108 } 109 } 110 } Στη γραμμή 8 θα πρέπει να δηλώσετε ότι θα χρησιμοποιήσετε τη βιβλιοθήκη System.IO με σκοπό να έχετε πρόσβαση στις κλάσεις StreamReader και StreamWriter, οι οποίες χρησιμοποιούνται για την ανάγνωση και την αποθήκευση δεδομένων σε αρχεία, και στην γραμμή 9 την System.Collections για να έχετε πρόσβαση στην ArrayList. Στην γραμμή 15 δηλώνετε σαν καθολική μεταβλητή το ArrayList values, το οποίο θα χρησιμοποιηθεί για την αποθήκευση των τιμών από το αρχείο. Πατώντας το πλήκτρο Ανάγνωση εκτελείται ο κώδικας της συνάρτησης btnread_click Πίνακας 1, γραμμές 88 έως 96 στο σώμα της οποίας γίνεται η ανάγνωση των τιμών από το αρχείο values.txt. Αρχικά εμφανίζετε το παράθυρο διαλόγου openfiledialog1 και αν ο χρήστης επιλέξει ένα αρχείο τότε το μονοπάτι του είναι αποθηκευμένο στην ιδιότητα openfiledialog1.filename. Το μονοπάτι του αρχείου καθώς και το ArrayList values χρησιμοποιούνται σαν παράμετροι για την μέθοδο FillArrays, στο σώμα της οποίας θα διαβάσετε τις τιμές του αρχείου. Αφού η values γεμίσει με τιμές 9
από το αρχείο εν συνεχεία θα χρησιμοποιήσετε την μέθοδο FormatMessage για να μορφοποιήσετε ένα αλφαριθμητικό το οποίο θα εμφανίσετε στο κουτί κειμένου txtresult. Στο σώμα της FillArrays, γραμμές 45 έως 67, με την βοήθεια της StreamReader ανοίγετε το αρχείο για ανάγνωση και εν συνεχεία μέσα σε μια ατελείωτη επανάληψη, γραμμή 51, διαβάζετε κάθε φορά δύο γραμμές κειμένου, γραμμές 53 και 54. Αν μια από τις δύο είναι κενή τότε σημαίνει ότι έχετε φτάσει στο τέλος του αρχείου και θα πρέπει να διακόψετε την επανάληψη αφού δεν υπάρχουν άλλα δεδομένα προς ανάγνωση. Η δήλωση null σημαίνει τίποτα, η πιο απλά ότι η μεταβλητή δεν έχει περιεχόμενο. Στην αντίθετη περίπτωση όπου οι μεταβλητές Aline και Bline έχουν περιεχόμενο τότε θα μετατρέψετε την πρώτη σε πραγματικό αριθμό και θα τον αποθηκεύσετε μαζί με την δεύτερη μεταβλητή στο values, γραμμές 62 έως 64. Στις γραμμές 23 έως 43 δημιουργείτε την συνάρτηση findmaxpos, η οποία δέχεται σαν παράμετρο ένα ArrayList και επιστρέφει την θέση με την μεγαλύτερη θερμοκρασία. Στην γραμμή 27 κάνετε έναν έλεγχο αν υπάρχουν δεδομένα μέσα στο ArrayList και αν ναι τότε θεωρείται ότι η μεγαλύτερη τιμή βρίσκετε στην θέση 0, γραμμή 30, και εν συνεχεία κάνετε μια επανάληψη για τις υπόλοιπες θέσεις του ArrayList και κάθε φορά συγκρίνετε αν η τρέχουσα θέση έχει μεγαλύτερη τιμή από την τιμή της μεταβλητής max. Αν ναι τότε καταγράφετε η θέση της, στην pos και η max παίρνει την αντίστοιχη τιμή. Στο τέλος επιστρέφετε την θέση την pos η οποία περιέχει την θέση της μεγαλύτερης θερμοκρασίας μέσα στο ArrayList. Να παρατηρήσετε ότι στην γραμμή 33, η επανάληψη ξεκινά από την θέση 2 γιατί η θέση 1 (καθώς και όλες οι περιττές θέσεις) περιέχει χρονική στιγμή και όχι θερμοκρασία. Επίσης και το βήμα της επανάληψης αυξάνει κατά δύο αφού μόνο οι άρτιες θέσεις στο ArrayList περιέχουν αριθμητικές τιμές. Η FormatMessage, γραμμές 70 έως 85, δέχεται σαν παράμετρο ένα ArrayList και αφού υπολογίσει με την βοήθεια της findmaxpos, την θέση της μεγαλύτερης θερμοκρασίας εν συνέχεια μορφοποιεί και επιστρέφει ένα αλφαριθμητικό με τις σχετικές πληροφορίες. Να παρατηρήσετε ότι η χρονική στιγμή της μεγαλύτερης θερμοκρασίας βρίσκετε στην επόμενη θέση μέσα στο ArrayList. 10
Πατώντας το πλήκτρο Εγγραφή εκτελείται ο κώδικας της συνάρτησης btnwrite_click Πίνακας 1, γραμμές 98 έως 108 στο σώμα της οποίας γίνεται η αποθήκευση της μεγαλύτερης θερμοκρασίας καθώς και άλλων πληροφοριών σε ένα αρχείο που θα επιλέξει ο χρήστης. Αρχικά εμφανίζετε το παράθυρο διαλόγου savefiledialog1 και αν ο χρήστης επιλέξει ένα αρχείο τότε το μονοπάτι του είναι αποθηκευμένο στην ιδιότητα savefiledialog1.filename. Αφού δημιουργήσετε το αρχείο με την StreamWriter εν συνεχεία θα χρησιμοποιήσετε την συνάρτηση FormatMessage για να αποθηκεύσετε το αλφαριθμητικό μέσα στο αρχείο. Στο τέλος κλείνετε το αρχείο. 11