Μετάβαση στο περιεχόμενο
Topos Uranos

Περίληψη

Αυτό το μάθημα μελετά από ποιους δρόμους μπαίνουν τα δεδομένα σε μια συνάρτηση και βγαίνουν τα αποτελέσματά της. Εξ ορισμού, η C++ μεταβιβάζει τα ορίσματα με τιμή: η παράμετρος είναι ένα αντίγραφο, και τίποτα από όσα της κάνει η συνάρτηση δεν φτάνει στο πρωτότυπο. Μια αναφορά, αντίθετα, είναι ένα δεύτερο όνομα για ένα κελί που ήδη υπάρχει, και επιτρέπει στη συνάρτηση να τροποποιήσει τη μεταβλητή εκείνου που την καλεί· η σταθερή αναφορά επιτρέπει να διαβαστεί ένα μεγάλο δεδομένο χωρίς να αντιγραφεί και χωρίς να μπορεί να αλλοιωθεί. Για να επιστραφούν περισσότερα αποτελέσματα, η βιβλιοθήκη προσφέρει τα std::pair και std::tuple, που λαμβάνονται με δομημένες δεσμεύσεις. Ακολουθούν η εμβέλεια ενός ονόματος (πού φαίνεται) και η διάρκεια ζωής μιας μεταβλητής (πότε υπάρχει), με την επισκίαση ενός ονόματος και τις τοπικές μεταβλητές static, και κλείνει η αναφορά που επιζεί της μεταβλητής της. Η έννοια που τα ενοποιεί όλα είναι η ροή των δεδομένων ανάμεσα στα μέρη ενός προγράμματος: αντιγραφή απέναντι σε κοινή χρήση, και η ορατότητα των ονομάτων ως μηχανισμός απομόνωσης.

Μαθησιακοί στόχοι

  1. Να προβλέπεται η επίδραση μιας συνάρτησης στα ορίσματά της ανάλογα με το αν μεταβιβάζονται με τιμή ή με αναφορά.
  2. Να επιλέγεται ο τρόπος μεταβίβασης κάθε παραμέτρου (τιμή για μικρούς τύπους, σταθερή αναφορά για μεγάλα δεδομένα, αναφορά για ό,τι τροποποιείται) και να αιτιολογείται η επιλογή.
  3. Να επιστρέφονται περισσότερα αποτελέσματα με std::pair ή std::tuple και να λαμβάνονται με δομημένες δεσμεύσεις.
  4. Να προσδιορίζονται η εμβέλεια και η διάρκεια ζωής κάθε μεταβλητής ενός προγράμματος, μαζί με τις τοπικές μεταβλητές static και εκείνες που επισκιάζονται από άλλη.
  5. Να αναγνωρίζεται η αναφορά σε μια τοπική μεταβλητή ή σε μια προσωρινή τιμή που δεν υπάρχει πια, με την προειδοποίηση του μεταγλωττιστή που την αναγγέλλει, και να διορθώνεται.

Μεταβίβαση με τιμή

Η παράμετρος είναι αντίγραφο

Το μάθημα Συναρτήσεις έδειξε ότι κάθε κλήση δημιουργεί ένα πλαίσιο στη στοίβα, με τις παραμέτρους και τις τοπικές μεταβλητές της συνάρτησης, και ότι το πλαίσιο εξαφανίζεται κατά την επιστροφή. Μένει να διευκρινιστεί ποια σχέση έχει μια παράμετρος με το όρισμα που την αρχικοποιεί. Η εξ ορισμού απάντηση της C++ είναι η απλούστερη: η παράμετρος είναι μια νέα τοπική μεταβλητή, που αρχικοποιείται με ένα αντίγραφο της τιμής του ορίσματος. Αν αυτό ισχύει, μια συνάρτηση που τροποποιεί τις παραμέτρους της τροποποιεί τα αντίγραφά της, και οι μεταβλητές εκείνου που την καλεί δεν αλλάζουν. Το πρόγραμμα που ακολουθεί το θέτει σε δοκιμή με μια συνάρτηση που επιχειρεί να αντιμεταθέσει δύο ακεραίους.

Πρόγραμμα σε C++
#include <print>​// Επιχειρεί να αντιμεταθέσει τα a και b· αντιμεταθέτει μόνο τα αντίγραφά τους.void swapValues(int a, int b){    const int saved{a};    a = b;    b = saved;    std::println("Μέσα: a = {}, b = {}", a, b);}​int main(){    int x{1};    int y{2};    swapValues(x, y);    std::println("Μετά: x = {}, y = {}", x, y);}
Έξοδος
Μέσα: a = 2, b = 1Μετά: x = 1, y = 2

Μέσα στη συνάρτηση η αντιμετάθεση γίνεται σωστά· μετά την κλήση, η x και η y διατηρούν τις τιμές τους. Ο μεταγλωττιστής δεν προειδοποιεί, γιατί δεν υπάρχει τίποτα για να προειδοποιήσει: ο κώδικας αντιμεταθέτει δύο τοπικές μεταβλητές που εξαφανίζονται όταν τελειώνει. Το σχήμα παρακολουθεί τα πλαίσια της στοίβας κατά την κλήση.

ΠαράδειγμαΔύο πλαίσια, τέσσερα κελιά

Το πλαίσιο της main περιέχει τη x και τη y· η κλήση δημιουργεί από πάνω το πλαίσιο της swapValues, με δικά του κελιά.

Απόδειξη

  1. Πριν από την κλήση, το πλαίσιο της main έχει δύο κελιά: τη x με 1 και τη y με 2.

  2. Η κλήση δημιουργεί το πλαίσιο της swapValues. Οι παράμετροί της a και b είναι νέα κελιά, και σε αυτά αντιγράφονται οι τιμές των ορισμάτων: 1 και 2. Από αυτή τη στιγμή δεν μένει κανένας δεσμός ανάμεσα στην a και στη x, εκτός από το ότι ξεκίνησαν με την ίδια τιμή.

  3. Η τοπική μεταβλητή saved δημιουργείται στο ίδιο πλαίσιο και κρατά ένα αντίγραφο της a.

  4. Οι δύο αναθέσεις αντιμεταθέτουν την a και τη b. Τα κελιά που αλλάζουν βρίσκονται όλα στο πάνω πλαίσιο· εκείνα της main δεν αγγίζονται.

  5. Κατά την επιστροφή, το πλαίσιο της swapValues εξαφανίζεται ολόκληρο, μαζί με την αντιμετάθεση που περιείχε. Η x και η y εξακολουθούν να έχουν τιμές 1 και 2.

Τι προστατεύει το αντίγραφο

Η μεταβίβαση με τιμή δεν είναι ελάττωμα, αλλά ο κανόνας που κάνει τις συναρτήσεις λογικές. Πρώτον, το όρισμα μπορεί να είναι οποιαδήποτε έκφραση, όχι μόνο μια μεταβλητή: η swapValues(x + 1, 7) είναι έγκυρη, γιατί η παράμετρος αρχικοποιείται με μια τιμή, από όπου κι αν προέρχεται. Δεύτερον, όποιος καλεί ξέρει από τη δήλωση, χωρίς να διαβάσει το σώμα, ότι οι μεταβλητές του δεν θα αλλάξουν. Τρίτον, μέσα στη συνάρτηση η παράμετρος είναι μια ακόμη τοπική μεταβλητή, που μπορεί να χρησιμεύσει ως μεταβλητή εργασίας χωρίς συνέπειες έξω.

Πρόγραμμα σε C++
#include <print>​// Πλήθος δεκαδικών ψηφίων του n.// Προσυνθήκη: n >= 0.// Μετασυνθήκη: επιστρέφει το πλήθος των ψηφίων του n (1 για το 0).int digitCount(int n){    int count{1};    while (n >= 10) {        n = n / 10;        ++count;    }    return count;}​int main(){    const int population{48'210};    std::println("Ο αριθμός {} έχει {} ψηφία", population, digitCount(population));}
Έξοδος
Ο αριθμός 48210 έχει 5 ψηφία

Η συνάρτηση καταστρέφει την παράμετρό της διαιρώντας την με το 10 ώσπου να έχει ένα μόνο ψηφίο· επειδή είναι αντίγραφο, η population μένει ανέπαφη και μπορεί να είναι const. Από αυτό έπεται ένας κανόνας ανάγνωσης που ισχύει για όλον τον κύκλο: αν μια παράμετρος δεν έχει το σύμβολο & που μελετάται στην παράγραφο που ακολουθεί, η συνάρτηση δεν μπορεί να τροποποιήσει τη μεταβλητή εκείνου που την καλεί, ό,τι κι αν κάνει με αυτήν.