Ενδεικτικές Λύσεις σε Επιλεγμένα Θέματα της C++

Σχετικά έγγραφα
Ενδεικτικές Λύσεις σε Επιλεγμένα Θέματα της C++

Ενδεικτικές Λύσεις σε Επιλεγμένα Θέματα της C++

Προγραμματισμός Υπολογιστών με C++ Φύλλο Διαγωνίσματος Ακαδημαϊκό εξάμηνο: Χειμερινό

Προγραμματισμός Υπολογιστών με C++ Φύλλο Διαγωνίσματος Ακαδημαϊκό εξάμηνο: Χειμερινό

Προγραμματισμός Υπολογιστών με C++ Φύλλο Διαγωνίσματος Ακαδημαϊκό εξάμηνο: Χειμερινό

ΚΑΤΑΣΚΕΥΑΣΤΕΣ ΑΝΤΙΓΡΑΦΗΣ

Κλήση Συναρτήσεων ΚΛΗΣΗ ΣΥΝΑΡΤΗΣΕΩΝ. Γεώργιος Παπαϊωάννου ( )

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Αναφορές

Πίνακες: μια σύντομη εισαγωγή. Πίνακες χαρακτήρων: τα "Αλφαριθμητικά"

Προγραμματισμός Ι. Δείκτες. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Προγραμματισμός Ι (ΗΥ120)

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

ΔΕΙΚΤΕΣ ΚΑΙ ΔΙΕΥΘΥΝΣΕΙΣ

Διάλεξη 13η: Δυναμική Διαχείρηση Μνήμης, μέρος 1

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αναφορές Στοίβα και Σωρός Μνήμης Αντικείμενα ως ορίσματα

ΕΡΓΑΣΤΗΡΙΟ 9: Συμβολοσειρές και Ορίσματα Γραμμής Εντολής

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα ως ορίσματα Εισαγωγή στις αναφορές

Εισαγωγή στον Προγραμματισμό

Προγραμματισμός Η/Υ (ΤΛ2007 )

FAIL PASS PASS οριακά

#include <stdlib.h> Α. [-128,127] Β. [-127,128] Γ. [-128,128]

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

ΚΑΛΟΥΠΩΜΑΤΑ & ΜΕΤΑΤΡΟΠΕΣ

Δυναμική δέσμευση και αποδέσμευση μνήμης. Προγραμματισμός II 1

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Α Β Γ static; printf("%c\n", putchar( A +1)+2); B DB BD. int i = 0; while (++i); printf("*");

lab13grades Άσκηση 2 -Σωστά απελευθερώνετε ολόκληρη τη λίστα και την κεφαλή

Δομημένος Προγραμματισμός

Α. unsigned int Β. double. Γ. int. unsigned char x = 1; x = x + x ; x = x * x ; x = x ^ x ; printf("%u\n", x); Β. unsigned char

Προγραμματισμός Η/Υ (ΤΛ2007 )

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αναφορές Στοίβα και Σωρός Αναφορές-Παράμετροι

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα με πίνακες. Constructors. Υλοποίηση Στοίβας

ΒΑΣΙΚΟΙ ΤΥΠΟΙ ΚΑΙ ΠΙΝΑΚΕΣ

Sheet2. Σωστή, και µπράβο που µεριµνήσατε για λίστες διαφορετικών µεγεθών.

Ανάπτυξη και Σχεδίαση Λογισμικού

- Το πρόγραµµα σας δίνει τα αναµενόµενα αποτελέσµατα.

Δομές Δεδομένων. Καθηγήτρια Μαρία Σατρατζέμη. Τμήμα Εφαρμοσμένης Πληροφορικής. Δομές Δεδομένων. Τμήμα Εφαρμοσμένης Πληροφορικής

Διαδικασιακός Προγραμματισμός

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Η/Υ Ακαδημαϊκό έτος ΤΕΤΡΑΔΙΟ ΕΡΓΑΣΤΗΡΙΟΥ #4

Ενδεικτικές λύσεις και στατιστικά

Εισαγωγή στον Προγραμματισμό (με. τη C)

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

Ονοματεπώνυμο και ΑΜ: Είχα παραδώσει εργασίες τα εξής ακαδημαϊκά έτη: Διάρκεια: 2,5 ώρες, κλειστά βιβλία και σημειώσεις ΚΑΛΗ ΕΠΙΤΥΧΙΑ!

Οργάνωση Υπολογιστών ΕΛΛΗΝΙΚΗ ΔΗΜΟΚΡΑΤΙΑ ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΡΗΤΗΣ. Ασκήσεις 7: Πρόγραμμα Συνδεδεμένης Λίστας και Διαδικασιών. Μανόλης Γ.Η.

Στη C++ υπάρχουν τρεις τύποι βρόχων: (a) while, (b) do while, και (c) for. Ακολουθεί η σύνταξη για κάθε μια:

Διάλεξη 18η: Διαχείρηση Αρχείων

Προγραμματισμός Ι. Κλάσεις και Αντικείμενα. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Διάλεξη 5: Δείκτες και Συναρτήσεις

Εισαγωγή στον Αντικειμενοστρεφή Προγραμματισμό Διάλεξη #2

ΠΡΟΗΓΜΕΝΟΙ ΜΙΚΡΟΕΠΕΞΕΡΓΑΣΤΕΣ PROJECT 2: MEMORY MANAGEMENT

lab13grades 449 PASS 451 PASS PASS FAIL 1900 FAIL Page 1

ΕΠΛ232 Προγραμματιστικές Τεχνικές και Εργαλεία Δυναμική Δέσμευση Μνήμης και Δομές Δεδομένων (Φροντιστήριο)

Δομημένος Προγραμματισμός (ΤΛ1006)

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Μαθήματα από τα εργαστήρια

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Προγραμματισμός Δομές Δεδομένων

Δομημένος Προγραμματισμός (ΤΛ1006)

ΠΛΗΡΟΦΟΡΙΚΗ ΙΙ (JAVA) 11/3/2008

Προγραμματισμός Η/Υ (ΤΛ2007 )

Δείκτες (Pointers) Ένας δείκτης είναι μια μεταβλητή με τιμή μια διεύθυνση μνήμης. 9.8

Προγραμματισμό για ΗΜΥ

Διαδικασιακός Προγραμματισμός

Διάλεξη 3η: Τύποι Μεταβλητών, Τελεστές, Είσοδος/Έξοδος

Αντικειμενοστραφείς Γλώσσες Προγραμματισμού C++ / ROOT

Τύποι Δεδομένων και Απλές Δομές Δεδομένων. Παύλος Εφραιμίδης V1.0 ( )

Προγραμματισμός Υπολογιστών με C++

ΕΡΓΑΣΤΗΡΙΟ 9: Συμβολοσειρές και Ορίσματα Γραμμής Εντολής

Προγραμματισμός Υπολογιστών με C++

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

Α' Εξάμηνο ΕΙΣΑΓΩΓΗ ΣΤΟ ΔΟΜΗΜΕΝΟ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟ

Προγραμματισμός Υπολογιστών με C++

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα

Ανάπτυξη και Σχεδίαση Λογισμικού

Διάλεξη 9η: Πίνακες (arrays)

ΤΕΜ-101 Εισαγωγή στους Η/Υ Εξεταστική Ιανουαρίου 2011 Θέματα Β

Προγραμματισμός Υπολογιστών με C++

Τι είναι κλάση Κλάση

ΕΙΣΑΓΩΓΗ ΣΤΟΝ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟ Ενδεικτικές Απαντήσεις Εξετάσεων Α' Περιόδου Θέµα 1. (α') 2 - ii 3 - iii 4 - iv

Προγραμματισμός Η/Υ (ΤΛ2007 )

Προγραμματισμός Υπολογιστών με C++

Εισαγωγή στην C. Μορφή Προγράµµατος σε γλώσσα C

ΚΛΗΡΟΝΟΜΙΚΟΤΗΤΑ ΚΑΙ ΠΟΛΥΜΟΡΦΙΣΜΟΣ

Κεφάλαιο Αλφαριθμητικές Σειρές Χαρακτήρων (Strings) (Διάλεξη 20) 1) Strings στη C

2 Ορισμός Κλάσεων. Παράδειγμα: Μηχανή για Εισιτήρια. Δομή μιας Κλάσης. Ο Σκελετός της Κλάσης για τη Μηχανή. Ορισμός Πεδίων 4/3/2008

Sheet2. - Άσκηση 1 οκ - Άσκηση 2 οκ. Σκέψου πώς θα µπορούσες να την

3ο σετ σημειώσεων - Πίνακες, συμβολοσειρές, συναρτήσεις

B. Ενσωμάτωση Ιθαγενών Μεθόδων

Η βασική συνάρτηση προγράμματος main()

Ονοματεπώνυμο και ΑΜ: Είχα παραδώσει εργασίες τα προηγούμενα ακαδημαϊκά έτη: ΚΑΛΗ ΕΠΙΤΥΧΙΑ!

Στοίβες με Δυναμική Δέσμευση Μνήμης

Διάλεξη 20: Χαμηλού Επιπέδου Προγραμματισμός II

Προγραμματισμός Υπολογιστών με C++

ΠΛΗΡΟΦΟΡΙΚΗ Ι JAVA Τμήμα θεωρίας με Α.Μ. σε 3, 7, 8 & 9 17/1/08

Δείτε τώρα και πώς θα έπρεπε να ήταν το παραπάνω: Page 1

Λύβας Χρήστος Αρχική επιµέλεια Πιτροπάκης Νικόλαος και Υφαντόπουλος Νικόλαος

Προγραμματισμός Ι (ΗΥ120)

ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΗΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ

Μεθόδων Επίλυσης Προβλημάτων

Κεφάλαιο 8.7. Πολυδιάστατοι Πίνακες (Διάλεξη 19)

Εισαγωγή στον Προγραµµατισµό, Αντώνιος Συµβώνης, ΣΕΜΦΕ, ΕΜΠ,, Slide 6

Transcript:

Ενδεικτικές Λύσεις σε Επιλεγμένα Θέματα της C++ class ListNode public: T data; ListNode<T> * next; ListNode(const ListNode<T> & src) ; data = src.data; if (src.next!=null) next = new ListNode<T> ((const ListNode<T>) * src.next); else next = NULL; return; Αναδρομική λύση (πιο σύντομος κώδικας) class List protected: ListNode<T> * start; public: List( const List<T> & src) ; if (src.start == NULL) else start = NULL; return; start = new ListNode<T>( src.start ); Δώστε τον κώδικα για τον κατασκευαστή αντιγραφής της παραπάνω κλάσης ListNode έτσι ώστε να δημιουργείται πλήρες αντίγραφο των περιεχομένων που δείχνει ο κόμβος src. Βοήθεια: μπορεί να γίνει αναδρομικά. Η κλάση ListNode χρησιμεύει στην κατασκευή της λίστας List και ο κατασκευαστής αντιγραφής καλείται για να κάνει deep copy μιας λίστας, όπως φαίνεται.

Συμπληρώστε τον κώδικα ώστε να δουλεύει το κύριο πρόγραμμα παρακάτω: class Shape public: virtual void draw () = 0; virtual void print () ; class Box : public Shape public: void draw () ; void print () cout << I am a box ; ; Η Shape έχει μια pure virtual μέθοδο (draw), η οποία πρέπει οπωσδήποτε να οριστεί σε κάποια παράγωγη κλάση πριν μπορέσει να δημιουργηθεί κάποιο στιγμιότυπο της τελευταίας. void main () Shape *a = new Box(); a->print(); // τυπώνει I am a box char buf[] = a, b ; Ποιο θα είναι το αποτέλεσμα τις πράξης: cout << *( (long *) ( (void*) buf ) +1); α) Τυπώνει το χαρακτήρα b. β) Τυπώνει τον ASCII κωδικό (αριθμό) που αντιστοιχεί στο χαρακτήρα b. γ) Τυπώνει κάτι απροσδιόριστο δ) Παράγει compilation error Απάντηση: (γ) Δικαιολογήστε την απάντησή σας: Το buf μετατρέπεται σε δείκτη σε long που μετά αυξάνεται κατά 4 ή 8 bytes βγαίνοντας έξω από τα όρια του αρχικού πίνακα (2 bytes). (void*)buf A : είναι πλέον δείκτης σε απροσδιόριστου μεγέθους δεδομένα (η επαύξηση ενός τέτοιου pointer γίνεται κατά ένα byte). ((long*)a) B : είναι πλέον δείκτης σε δεδομένα τύπου long, άρα με δεδομένη μια υλοποίηση του long με 4 ή 8 bytes (ελάχιστο 4), κάθε επόμενος αριθμός long σε έναν πίνακα (δηλ. σε συνεχόμενες θέσεις μνήμης), απέχει από τον προηγούμενο τουλάχιστο 4 bytes. B+1 C : ο δείκτης έχει προχωρήσει κατά ένα «βήμα», όπου το βήμα είναι ίσο με το μέγεθος του τύπου του δείκτη (εδώ είναι «δείκτης σε long»), άρα τουλάχιστο 4 bytes. *(C) D : παίρνουμε το περιεχόμενο του δείκτη, το οποίο ξεκινάει τουλάχιστο 4 bytes μακριά από τη διεύθυνση buf και άρα δε γνωρίζουμε τι υπάρχει εκεί ή αν μπορούμε καν να το διαβάσουμε (πιθανό access violation).

Ποια είναι η διαφορά μεταξύ των δύο σημειωμένων γραμμών; unsigned int number = 12002; ofstream fs = ofstream("a.out", ios_base::out ); if (!fs.bad()) fs.write(reinterpret_cast<char*>(&number), sizeof(int)); // ΓΡΑΜΜΗ Α fs << number; // ΓΡΑΜΜΗ Β fs.close(); Έστω ότι sizeof(int) 4. Τότε το αρχείο θα περιέχει: Α) 0x00 0x00 0x2E 0xE2, που είναι τα 4 bytes του 12002 σε δεκαεξαδική μορφή (0x00002EE2) B) 0x31 0x32 0x30 0x30 0x32, που είναι οι κωδικοί ASCII των 5 χαρακτήρων του 12002 Η γραμμή Α γράφει με δυαδικό τρόπο τα 2 ή 4 bytes (sizeof(int)) που αναπαριστούν το number Η γραμμή Β μετατρέπει τον αριθμό σε συμβολοσειρά και γράφει σε μορφή κειμένου το 12002

Δίνεται η κλάση: class Container protected: T * storage; size_t num_items; size_t storage_size; public: virtual void operator += (const T item) virtual ~Container() Container() : storage(nullptr), num_items(0), storage_size(0) T & operator [] (size_t index) if (index < num_items) return storage[index]; size_t size() return num_items; ; Επεκτείνετε κατάλληλα την Container, ώστε ο παρακάτω κώδικας να δουλεύει χωρίς σφάλματα και να παράγει το αποτέλεσμα που φαίνεται: void main(int argc, char* argv[]) Container<long> * store = new Box(); *store+=10l; *store+=20l; *store+=30l; for (size_t i=0; i<store->size(); i++) std::cout << (*store)[i] << " "; std::cout << std::endl; delete store; Αποτέλεσμα της εκτέλεσης: 10 20 30 // Συμπληρώστε την αρχικοποίηση // αντικειμένου της δικής σας κλάσης Γράψτε εδώ τη δήλωση της δικής σας κλάσης. Δεν επιτρέπεται να αλλοιώσετε την Container, ούτε να χρησιμοποιήσετε κλάσεις της STL. H Container έχει μεν υλοποιημένο τον τελεστή +=, αλλά δεν κάνει τίποτα. Η παράγωγη κλάση αυτής (εδώ θα την πούμε Box ), πρέπει να υλοποιήσει πλήρως όλη τη διαδικασία εισαγωγής στοιχείων σε δυναμική γραμμική συλλογή. Επιπλέον, πρέπει να φτιάξουμε έναν καταστροφέα ο οποίος να μπορεί να διαγράφει την περιοχή μνήμης storage, αν αυτή έχει δεσμευθεί. Παρατηρήστε ότι ο καταστροφέας της Container είναι virtual, ώστε να μπορέσει να κληθεί σωστά ο καταστροφέας της Box. Για την παράγωγη κλάση πρέπει να ισχύουν τα ακόλουθα: - Πρέπει να αρχικοποιεί προαιρετικά τον πίνακα storage σε ένα προκαθορισμένο μέγεθος. Αυτό δεν είναι απαραίτητο όμως και για οικονομία, συνήθως δεν αρχικοποιούμε δυναμικές συλλογές αν δεν αρχίσουμε να τις γεμίζουμε. Στην προτεινόμενη υλοποίηση, κρατάμε τον default constructor της βασικής κλάσης και δεσμεύουμε μνήμη μόνο όταν χρειαστεί. - Πρέπει να αποδεσμεύει μνήμη της storage σε έναν δικό της καταστροφέα, μόνο στην περίπτωση που η storage δεν είναι NULL. - Πρέπει να μπορεί να χειριστεί εισαγωγές στοιχείων τυχαίου πλήθους με τον τελεστή +=. - Για την υλοποίηση του +=, χρειάζεται να προσέξουμε τα ακόλουθα: - Αν ο χώρος storage είναι μη εκχωρημένος, τον δημιουργούμε ίσο με έναν αριθμό στοιχείων της αρεσκείας μας. - Αν num_items < storage_size, μπορούμε να εισάγουμε άφοβα ένα νέο στοιχείο στη θέση num_items. - Αν num_items == storage_size (μεγαλύτερο δε θα είναι αφού πάντα αυξάνει κατά 1), πρέπει να επεκτείνουμε το storage

class Box : public Container<T> public: void operator += (const T item) if (storage_size==0) storage_size = 2; storage = new T[storage_size]; else if (storage_size==num_items) storage_size*=2; T * buffer = new T[storage_size]; memcpy(buffer,storage,sizeof(t)*storage_size/2); delete [] storage; storage = buffer; storage[num_items++] = item; Ο έλεγχος storage_size==0 εδώ είναι ισοδύναμος με τον storage==nullptr Αν δεν έχει δεσμευθεί το storage, δέσμευσε το χώρο και ενημέρωσε το storage_size Αν φτάσαμε στο όριο των στοιχείων του storage: - Όρισε το νέο επιθυμητό μέγεθος - Δέσμευσε έναν καινούριο χώρο (εδώ διπλάσιο) - Αντέγραψε τα παλιά δεδομένα στην αρχή του νέου χώρου - Διέγραψε τον παλιό χώρο και κράτησε ως storage τον καινούριο Πρόσθεσε στην επόμενη διαθέσιμη θέση το νέο στοιχείο ; ~Box() if (storage!=nullptr) delete [] storage; Διαγράφουμε μόνο όταν έχει δεσμευθεί η μνήμη Παρατήρηση: η memcpy θα μπορούσε να αντικατασταθεί από ένα loop αντιγραφής στοιχείων 1 προς 1.

// Η κλάση Counter κατασκευάζεται με βάση ένα std::vector // από δεδομένα οποιουδήποτε τύπου και αυτό που κάνει είναι να αναφέρει // πόσες φορές υπάρχει ένα συγκεκριμένο στοιχείο μέσα στο vector // κατά τη στιγμή της δημιουργίας ενός στιγμιοτύπου Counter. // class Counter private: std::unordered_map<t,size_t> histogram; public: // Μοναδικός κατασκευαστής Counter(std::vector<T> & input); // Συμπληρώστε πεδία που χρειάζεται η κλάση Στην συγκεκριμένη έκδοση του προβλήματος (εξεταστική Ιουνίου 2014), ο τύπος και το εύρος τιμών Τ δεν είναι γνωστά. Στην παραλλαγή του θέματος της εξεταστικής Σεπτεμβρίου 2014, είχαμε μικρό εύρος τιμών (256) και δεδομένο τύπο (char), οπότε υπήρχε και πιο απλή υλοποίηση, με σταθερό πίνακα τύπου size_t μήκους 256 για 256 ανεξάρτητους μετρητές. Πρέπει να δημιουργήσουμε μια δική μας δομή που να φυλάει δεδομένα για την καταμέτρηση. Η δημιουργία γίνεται κατά την αρχικοποίηση του Counter και δε μπορούμε να θεωρήσουμε ότι το input παραμένει αναλλοίωτο μετά από αυτή τη στιγμή. Χρειαζόμαστε μια «αραιή» συλλογή που να συνδέει ένα κλειδί (τιμή του T) με έναν πληθυσμό. ; // Αναφέρει πόσες φορές ένα συγκεκριμένο στοιχείο βρέθηκε μέσα στο vector<t> // input. Αν το στοιχείο δεν υπάρχει, αναφέρει 0, αλλιώς το αριθμό των φορών // που το έχει συναντήσει size_t GetCount(T item); Counter<T>::Counter(std::vector<T> & input) size_t num = input.size(); for ( size_t i=0; i<num; i++) histogram[input[i]]++; size_t Counter<T>::GetCount(T item) std::unordered_map<t,size_t>::iterator got = histogram.find(item); if (got == histogram.end()) return 0; else return got->second; ; Ο τελεστής [] του unordered_map, αν δεν υπάρχει το ζεύγος με κλειδί Τ, το δημιουργεί (καλεί «default constructor» για το size_t, δηλαδή το θέτει ίσο με 0, αφού το size_t είναι βασικός τύπος και όχι κλάση) και μας επιστρέφει αναφορά στο pair.second, δηλαδή στο πεδίο τύπου size_t. Αν υπάρχει ζεύγος με κλειδί Τ, μας επιστρέφει και πάλι το second του ζεύγους, οπότε πάντα ισχύει ο τελεστής ++ Αν δε βρεθεί το στοιχείο, επιστρέφουμε 0, αλλιώς το second του ζεύγους που επιστρέφει ο Iterator. Προσοχή: εδώ θα μπορούσαμε να καλέσουμε ακόμα και απλά: return histogram[item] γιατί αυτό που θα γίνει είναι να φτιάξει μια νέα εγγραφή με κλειδί item αν το item δεν υπάρχει και να του βάλει τιμή 0, οπότε και πάλι το αποτέλεσμα θα

Ενδεικτική χρήση: void main(int argc, char* argv[]) vector<long> data; data.push_back(-10); data.push_back(1); data.push_back(1); ήταν σωστό. Όμως, σε αυτή την περίπτωση για κάθε «άστοχη» κλήση της GetCount (δηλαδή για κλειδιά που δεν υπάρχουν ήδη στο histogram), θα επεκτείνεται χωρίς λόγο η συλλογή μας, με αποτέλεσμα κάποια στιγμή να γίνει βαριά, χωρίς λόγο. Κατά αντιστοιχία, θα μπορούσαμε να χρησιμοποιήσουμε μηχανισμό find/insert και στον κατασκευαστή. Counter<long> cnt = Counter<long>(data); size_t found = cnt.getcount(-10); found = cnt.getcount(0); found = cnt.getcount(1); // found==1 // found==0 // found==2 Πώς θα δεσμεύσω 100 στοιχεία τύπου string* και θα αναθέσω το αποτέλεσμα σε μια αντίστοιχη μεταβλητή με όνομα mydata; mydata = string ** mydata = new string * [100]; string ** mydata = (string**) new void * [100]; string ** mydata = (string**)malloc(sizeof(string*)*100); string ** mydata = (string**)malloc(sizeof(void*)*100); Δεσμεύουμε έναν πίνακα από δείκτες σε string. Όχι έναν πίνακα από string. Εναλλακτικά, μπορούμε να δεσμεύσουμε και με την malloc την μνήμη. Θυμόμαστε να αποδεσμεύσουμε τον πίνακα με τη free() σε αυτή την περίπτωση. Όπως βλέπετε, είναι αδιάφορο τι τύπου δείκτη θα δεσμεύσουμε πραγματικά, αφού το μέγεθος ενός δείκτη είναι δεδομένο (θέση μνήμης). Πρέπει όμως στη συνέχεια να κάνουμε το απαραίτητο cast στον τύπο της μεταβλητής. Δίνεται η ακόλουθη κλάση: class node_t protected: char data; public: node_t(const char letter) : next(0) data = letter; class node_t * next; ;

Συμπληρώστε τον απαραίτητο κώδικα στις γραμμές που δίνονται ώστε να μετασχηματιστεί το περιεχόμενο του data στον παρακάτω κώδικα από αυτό που φαίνεται στο σχήμα 1Α σε αυτό του 1Β, χωρίς να προκαλέσετε διαρροή μνήμης: (25 μονάδες) void main() node_t ** data = new node_t*[4]; // γέμισμα της δομής, όπως φαίνεται στο σχήμα 1Α. // σας δίνεται έτοιμο... // Απάντηση στο ερώτημα: data[1] = data[0]; data[0] = 0; data[3] = data[2]; data[2] = new node_t( E ); delete data[3]->next; data[3]->next = data[1]->next; Ο πίνακας data αποθηκεύει δείκτες σε node_t αντικείμενα. Η μετάθεση των ακολουθιών ΑΒ και CD από τη θέση 0 στην 1 και τη 2 στην 3 αντίστοιχα γίνεται πολύ απλά βάζοντας το δείκτη στη νέα θέση να δείχνει στο ίδιο στοιχείο με την παλιά θέση. Στη συνέχεια, μηδενίζουμε το δείκτη data[0]. Προσοχή: ΔΕ διαγράφουμε το δείκτη, αφού η μνήμη δείχνεται πλέον από τη θέση data[1]! Αν το κάνουμε αυτό θα χάσουμε και τα δεδομένα του data[1]. Εναλλακτικά, αντί για data[χ]->next, μπορούμε να γράψουμε (*data[3]).next 0 0 0 Α C Α E C 0 Β D Β 0 0 Σχήμα 1Α 0 Σχήμα 1Β

char * d = You: 0 points ; while (*(++d)!=0) std::cout << * ; Τι θα τυπωθεί; ************ (12 αστεράκια) Η επανάληψη θα διακοπεί όταν συναντηθεί το μηδέν, ή αλλιώς ο χαρακτήρας \0. Προσοχή: ο χαρακτήρας 0 (ASCII code 48) δεν είναι ίσος με το μηδέν ή NULL ή nullptr ή \0 (ASCII code 0). Πάντα, ένας πίνακας χαρακτήρων που παριστάνει μια έγκυρη συμβολοσειρά, ειδικά αν είναι στατικά δοσμένη όπως εδώ, τερματίζεται με το 0, γι αυτό και λέγεται «nullterminated string». class A public: int number; A(int d) : number(d) ; A & operator = (const A & right) number = 2 * right.number; return *this; ; Τι θα συμβεί στις παρακάτω περιπτώσεις; A a(1); A * p_a; A a2; p_a = new A(a); // (α) // (β) // (γ) // (δ) std::cout << (p_a == &a); // (ε) a = 2; // (στ) α) Δεσμεύεται και αρχικοποιείται μια μεταβλητή τύπου Α στην call stack καλώντας τον δηλωμένο κατασκευαστή. β) Δηλώνεται ένας δείκτης σε A. Δεν αρχικοποιείται.

γ) Επιχειρείται δήλωση και αρχικοποίηση αντικειμένου Α με χρήση του default κατασκευαστή. Ο κατασκευαστής αυτός όμως έχει καταργηθεί, επειδή ορίσαμε δικό μας κατασκευαστή για την κλάση ο οποίος απαιτεί όρισμα compilation error. δ) Δίνουμε αρχική τιμή στο δείκτη p_a καλώντας τον προκατασκευασμένο copy constructor ε) Τυπώνει 0 (false), αφού οι δύο διευθύνσεις δεν είναι ίδιες. στ) Δημιουργείται προσωρινό αντικείμενο με κλήση του κατασκευαστή A(int) αφού υπάρχει κατασκευαστής που δέχεται ως όρισμα τον τύπο του δεδομένου (int 2). Στη συνέχεια, καλείται ο τελεστής αντιγραφής για το a με αποτέλεσμα να πάρει το πεδίο a.number την τιμή 4. Έστω μια συνάρτηση match() που παίρνει ως είσοδο 2 ακολουθίες από δεδομένα σε μορφή std::vector και να μας επιστρέφει ένα score ομοιότητας ίσο με τον αριθμό των στοιχείων που είναι ίδια ξεκινώντας από το πρώτο στοιχείο, χωρίς να αλλοιωθεί η διάταξή τους. Παραδείγματα: Ορίσματα: (1.2)(1.4)(1.6), (10.0)(1.4)(1.6)(1.3)(1.2) Έξοδος: 2 Ορίσματα: ( a )( bb )( ccc ), ( aa )( b )( cc ) Έξοδος: 0 Ορίσματα: ( a )( b )( c ), ( b )( c )( a ) Έξοδος: 0 Ορίσματα: (4)(3)(2)(1), (4)(1)(2) Έξοδος: 2 Ορίσματα: (4)(3)(2)(1), (2)(1) Έξοδος: 0 Όπως φαίνεται από τα παραδείγματα, η συνάρτηση match πρέπει να επιστρέψει έναν αριθμό ίσο με το πλήθος των «ίδιων» (άρα «ίσων») στοιχείων στην ίδια ακριβώς θέση στα 2 vectors. Συνεπώς, η συνάρτηση απλά πρέπει να εκτελεί ένα μονό Loop που ταυτόχρονα ανατρέχει στην ίδια θέση (έστω i) στα δύο vectors, μέχρι να εξαντληθεί το μήκος ενός από τα δύο. Επιπλέον, σύμφωνα με τα παραδείγματα, η ίδια συνάρτηση πρέπει να είναι ικανή να δουλέψει σε διαφορετικού τύπου δεδομένα, άρα πρέπει να είναι templated.

Συμπληρώστε στον κενό χώρο που σας παρέχεται παρακάτω τον κώδικα της δήλωσης και υλοποίησης της συνάρτησης match() ώστε ο παρακάτω κώδικας να δουλεύει σωστά και να μην κάνει πλεοναστικές πράξεις και αντιγραφές δεδομένων: #include <vector> #include <stdio.h> void main (int argc, char ** argv) std::vector<int> seq1, seq2; seq1.push_back(1); //γεμίζουμε με δεδομένα τις 2 ακολουθίες seq1.push_back(4);... seq2.push_back(23); size_t score = match(seq1, seq2); printf( %ld\n, score); Απάντηση: size_t match(const std::vector<t> & v1, const std::vector<t> & v2 ) size_t length = v1.size()>v2.size()? v2.size() : v1.size(); size_t counter = 0; for (size_t i = 0; i < length; i++) if (v1[i] == v2[i]) counter++; return counter; Μια άλλη προσέγγιση είναι με χρήση iterators: size_t match(std::vector<t> & v1, std::vector<t> & v2) size_t counter = 0; std::vector<t>::iterator iter1 = v1.begin(); std::vector<t>::iterator iter2 = v2.begin(); Λαμβάνοντας αυτά υπόψη, μια ενδεικτική υποδειγματική υλοποίηση δίνεται παρακάτω. Έχει σημασία το γεγονός ότι τα δύο vectors που παίρνει ως ορίσματα θα πρέπει να περνάνε by reference (&) ή εναλλακτικά με pointer σε vector<t>, δηλαδή std::vector<t> * v1 αντί για const std::vector<t> & v1. Όπως ζητάει η άσκηση, δεν πρέπει να γίνονται πλεοναστικές πράξεις. Αν περνάγαμε τα vectors by value, τότε για κάθε ένα από τα ορίσματα θα έπρεπε να κληθεί ένας κατασκευαστής αντιγραφής που θα έφτιαχνε το τοπικό αντίγραφο στη stack για τα ορίσματα όσο τρέχει η match. Κάτι τέτοιο προφανώς δεν έχουμε λόγο να το κάνουμε, καθώς αν τα vectors είναι αρκετά μεγάλα, αυτό σημαίνει άσκοπο διπλασιασμό μνήμης και περιττή αντιγραφή περιεχομένου. To πλεονέκτημα του να περάσουμε εδώ με αναφορά και όχι Pointer είναι ότι δεν έχει νόημα να περάσουμε ποτέ σε μια τέτοια συνάρτηση nullptr, οπότε βάζοντας reference, εξασφαλίζουμε ότι πάντα περνάμε δύο έγκυρα αντικείμενα και αυτό ελέγχεται κατά το compilation (δεν είναι αποδεκτό να περνάς reference σε r-value). Βάζοντας const reference, εξηγούμε στον χρήστη της συνάρτησης ότι δεν πρόκειται να συμβεί καμία μεταβολή στα δύο αντικείμενα όσο τρέχει η match. Εναλλακτικές αποδεκτές υλοποιήσεις, μπορούν να έχουν ένα if then else όπου ανάλογα με το πιο vector είναι μικρότερο, εκτελείται με βάση το δικό του μέγεθος το Loop. H περίπτωση του να έχει κανείς τουλάχιστο ένα κενό vector Καλύπτεται από το εύρος της επανάληψης (μήκος 0 άμεσος τερματισμός). while (iter1!= v1.end() && iter2!= v2.end()) if (*iter1 == *iter2) counter++; ++iter1; ++iter2; return counter; Μια διαφορετική προσέγγιση είναι η σύγκριση με χρήση iterators, όπως φαίνεται στο διπλανό παράδειγμα. Εδώ, αν χρησιμοποιηθούν απλοί iterators, θα πρέπει να αλλάξει η δήλωση της match ώστε να μην είναι const τα ορίσματά της, καθώς δεν επιτρέπεται να πάρουμε iterator τυχαίας προσπέλασης πάνω σε μια const συλλογή. Και εδώ, θυμίζουμε ότι είναι προτιμότερο να χρησιμοποιούμε τον προθεματικό ++ τελεστή, γιατί όπως εξηγήθηκε στις διαφάνειες, είναι πιο αποδοτική πράξη σε iterators.

ή για const ορίσματα: size_t match(const std::vector<t> & v1, const std::vector<t> & v2) size_t counter = 0; std::vector<t>::const_iterator iter1 = v1.begin(); std::vector<t>::const_iterator iter2 = v2.begin(); Σημείωση: Αν θέλαμε οπωσδήποτε να κρατήσουμε τα ορίσματά μας const, για να ειδοποιήσουμε τον compiler ότι δε σκοπεύουμε να πειράξουμε τα περιεχόμενα των vectors κατά το iteration, θα έπρεπε να χρησιμοποιήσουμε const_iterator (εκτός ύλης δε ζητήθηκε). while (iter1!= v1.end() && iter2!= v2.end()) if (*iter1 == *iter2) counter++; ++iter1; ++iter2; return counter; Σας δίνεται ο παρακάτω κώδικας: class Fancy protected: size_t length = 0; unsigned char * buf = 0; public: Fancy( size_t len = 10) length = len; buf = new unsigned char[len]; buf[len-1] = 0; Fancy( const Fancy & src) buf = new unsigned char[src.length]; length = src.length; memcpy(buf,src.buf,length); Κατασκευαστής της Fancy που δρα και ως default κατασκευαστής, αφού αν δε δώσουμε όρισμα, χρησιμοποιείται το 10. Copy constructor ; ~Fancy() delete [] buf; inline size_t getlength() return length; Παράγεται αυτόματα για την κλάση ο copy assignment operator, αφού δεν έχουμε ορίσει εμείς κάποιον.

void main(int argc, char* argv[]) Fancy f1(20); Fancy f2; f2 = f1; std::cout << ( f1.getlength()==f2.getlength() ); O παραπάνω κώδικας προκαλεί run-time error. Τι συμβαίνει; Εξηγήστε αναλυτικά ποιο είναι το πρόβλημα. Ο παραπάνω κώδικας αρχικά κατασκευάζει 2 αντικείμενα f1 και f2 με τιμές για το πεδίο length ίσες με 20 και 10 αντίστοιχα. Στη συνέχεια καλείται ο default τελεστής ανάθεσης (copy assignment), αφού δεν ορίσαμε δικό μας. Ο (default) τελεστής ανάθεσης αντιγράφει τις τιμές των πεδίων (shallow copy) από την f1 στην f2, οπότε α) και τα 2 στιγμιότυπα δείχνουν στην ίδια θέση μνήμης για το πεδίο buf και β) προκαλείται κατά την ανάθεση memory leak αφού η παλιά τιμή του δείκτη f2.buf χάνεται (δεν προκαλεί αυτό όμως το run-time error). Προσοχή: Δεν καλείται copy constructor στη γραμμή f2=f1; Τα αντικείμενα έχουν ήδη αρχικοποιηθεί. Όταν στο τέλος της συνάρτησης main καλείται ο καταστροφέας των δύο αντικειμένων f1 και f2 πριν αδειάσει το call stack frame, θα κληθεί η delete [] buf και μετά θα ξανα-επιχειρηθεί η αποδέσμευση της ίδιας μνήμης με τη δεύτερη κλήση του destructor, αφού και τα δύο αντικείμενα βλέπουν την ίδια τιμή για το δείκτη buf. Έτσι προκαλείται καταστροφικό σφάλμα. Προσθέστε στην κλάση Fancy τη δήλωση και επί τόπου υλοποίηση του τελεστή () με όρισμα size_t i, έτσι ώστε να επιστρέφει αναφορά στο i-στο στοιχείο του πίνακα buf. unsigned char & operator () (size_t i) return buf[i]; Προσοχή: είναι λάθος να επιστρέφετε αναφορά σε τοπική μεταβλητή ή r-value! unsigned char & operator () (size_t i) if (i>=length i<0) return 0; else return buf[i]; unsigned char & operator () (size_t i) unsigned char a = buf[i]; return a;