Προγραμματισμός Υπολογιστών με C++ ( 2012-13 ) 5η διάλεξη Ίων Ανδρουτσόπουλος http://www.aueb.gr/users/ion/ 1
Τι θα ακούσετε σήμερα Πίνακες ως ορίσματα συναρτήσεων. Τα ορίσματα argc και argv της main. Συναρτήσεις που επιστρέφουν δείκτες ή αναφορές. 2
Πίνακες ως ορίσματα συναρτήσεων void incr(int* arg, int size); // Δήλωση της συνάρτησης. int main() { int myarray[] = {1, 2, 3; incr(myarray, 3); // Το myarray χρησιμοποιείται ως δείκτης. // Κατά την κλήση της incr, ο arg δείχνει εκεί που δείχνει ο // myarray, δηλαδή δείχνει στην αρχή του πίνακα myarray. for(int i = 0; i < 3; i++) { cout << myarray[i] << endl; // Τυπώνει «2, 3, 4». void incr(int* arg, int size) { // Το arg είναι τοπικός δείκτης, // αλλά δείχνει στην πρώτη θέση του myarray της main. for(int i = 0; i < size; i++) { arg[i]++; // Αλλάζει τον πίνακα της main. 3
Περισσότερα για πίνακες/δείκτες X void incr(int arg[3], int size); // Δεν επιτρέπεται. void incr(int arg[], int size); // Tο ίδιο με το ακόλουθο: void incr(int* arg, int size); Στα ορίσματα συναρτήσεων, int arg[] και int* arg είναι (. κλπ ισοδύναμα. (Το ίδιο για float arg[] και float* arg Στις άλλες περιπτώσεις όμως είναι διαφορετικά: int arr[] = {1, 2, 3; // Στατικός πίνακας. int* arr = new int[3]; // Δυναμικός πίνακας. X int* arr = {1, 2, 3; // Δεν επιτρέπεται. X int arr[] = new int[3]; // Δεν επιτρέπεται. char s[] = "test"; // Στατικός πίνακας χαρακτήρων. char* s = "test"; // ΟΚ! Για συμβατότητα με τη C. char* s1; // Οι s και s1 θα δείχνουν στην s1 = "test"; // 1η θέση του πίνακα χαρακτήρων 4 // "test".
() main Τα ορίσματα argc και argv της #include <iostream> using namespace std; Αριθμός ορισμάτων, μαζί με το όνομα του προγράμματος. int main( int argc, char* argv[] ) { // ή char** argv for(unsigned i = 0; i < argc; i++) { cout << argv[i] << endl; Ένας δείκτης σε πίνακα χαρακτήρων για κάθε λέξη της γραμμής εντολών. Ο πίνακας χαρακτήρων περιέχει την αντίστοιχη λέξη. Πρώτη λέξη θεωρείται το όνομα του προγράμματος. γραμμή εντολών: > myprog first second myprog first second argv argc = 3 "myprog" "first" "second" 5
Ο πίνακας του argv ακριβέστερα argv argc = 3 Άρα: Ισοδύναμα: (δείκτης σε char) (δείκτης σε char) (δείκτης σε char) char* argv[] char** argv 's' 'e' 'c' 'o' 'n' 'd' '\0' 'f' 'i' 'r' 's' 't' '\0' 'm' 'y' 'p' 'r' 'o' 'g' '\0'
int f(int& arg) { arg++; return arg; Επιστροφή κατά τιμή // Το arg θα αναφέρεται στο i της main. // Αυξάνει αυτό στο οποίο αναφέρεται το arg. // Επιστρέφει την τιμή του i της main. int main() { int i = 1; f(i); // i = 2. Η τιμή που επιστρέφεται (2) αγνοείται. int j = f(i); // i = 3. Η επιστρεφόμενη τιμή (3) αποθηκεύεται στο j. i++; // i = 4. cout << j << i << endl; // Τυπώνει 34. cout << f(i) << endl; // Τυπώνει 5. // f(i) = 10; // Δεν επιτρέπεται. Για τον ίδιο λόγο // που δεν επιτρέπεται το «6 =10». 7
Επιστροφή αναφοράς int& g(int& arg) { // Το arg θα αναφέρεται στο i της main. arg++; // Αυξάνει αυτό στο οποίο αναφέρεται το arg. return arg; // Σε συναρτήσεις που επιστρέφουν αναφορά (( g(i // μπορώ να σκέφτομαι την κάθε κλήση της συνάρτησης (π.χ. // σαν μια αναφορά σε αυτό που καθορίζει το return. int main() { int i = 1; int& ref = i; // Αναφορά στο i. g(i); // i = 2. Το g(i) είναι αναφορά στο i, δεν χρησιμοποιείται. int j = ref; // Το j παίρνει την τιμή του i, δηλαδή j = 2. j = g(i); // i = 3. To j παίρνει την τιμή του i, δηλαδή j = 3. int& r = g(i); // i = 4. To r αναφέρεται σε αυτό στο οποίο // αναφέρεται και το g(i), δηλαδή αναφορά στο i. // int& r2 = f(i); // Δεν επιτρέπεται, για τον ίδιο λόγο που δεν // επιτρέπεται το int& r2 = 5. // Συνεχίζεται στην επόμενη διαφάνεια. 8
Επιστροφή αναφοράς συνέχεια r++; cout << i << endl; // i = 5. Τυπώνει 5. j++; cout << i << endl; // To i δεν αλλάζει. Τυπώνει 5. cout << g(i) << endl; // i = 6 και τυπώνει αυτό στο οποίο // αναφέρεται, δηλαδή το i. Τυπώνει 6. g(i) = 10; // i = 7, αλλά αμέσως μετά i = 10, γιατί // το g(i) είναι αναφορά στο i. // Τέλος της main. 9
Επιστροφή δείκτη int* h(int* arg) { // Το arg θα δείχνει στο i της main. (*arg)++; // Αυξάνει αυτό στο οποίο δείχνει ο δείκτης. return arg; // Σε συναρτήσεις που επιστρέφουν δείκτη (( h(i // μπορώ να σκέφτομαι την κάθε κλήση της συνάρτησης (π.χ. // σαν ένα δείκτη που δείχνει εκεί που καθορίζει το return. int main() { int i = 1; h(&i); // i = 2. Το h(&i) είναι δείκτης προς το i, δεν χρησιμοποιείται. int* p = h(&i); // i = 3. Το p δείχνει σε αυτό που δείχνει και ο // δείκτης h(&i), δηλαδή στο i. cout << *p << endl; // Τυπώνει την τιμή του i, δηλαδή 3. cout << *(h(&i)) << endl; // i = 4. Το * εφαρμόζεται στο δείκτη // h(&i). Τυπώνει 4. *(h(&i)) = 10; // i = 5, αλλά αμέσως μετά i = 10. 10
Δεν επιστρέφουμε δείκτες σε τοπικές μεταβλητές int* h1(int* p) { (*p)++; // Αυξάνουμε μια εξωτερική μεταβλητή. Η κλήση return p; // της h1 θα είναι δείκτης στην εξωτερική μεταβλητή. int* h2() { int* p = new int; // Δυναμική δέσμευση θέσης μνήμης για *p = 1; // ακέραιο. Διατηρείται μέχρι delete. Η κλήση return p; // της h2 είναι δείκτης στη θέση που δεσμεύσαμε. X int* h3() { int j = 1; // Το j δεν υπάρχει μετά το τέλος της συνάρτησης. int* p = &j; // Το p δείχνει στο j. return p; // Λάθος! Η κλήση της h3 θα είναι δείκτης σε κάτι // που δεν υπάρχει μετά το τέλος της συνάρτησης. 11
Δεν επιστρέφουμε αναφορές σε τοπικές μεταβλητές int& g1(int& r) { r++; // Αυξάνουμε μια εξωτερική μεταβλητή. Η κλήση της return r; // g1 θα είναι αναφορά στην εξωτερική μεταβλητή. X int& g2() { int j = 1; // Το j δεν υπάρχει μετά το τέλος της συνάρτησης. int& r = j; // Το r αναφέρεται στο j. return r; // Λάθος! Η κλήση της g2 θα είναι αναφορά σε κάτι // που δεν υπάρχει μετά το τέλος της συνάρτησης. 12
Παράδειγμα επιστροφής τιμής #include <iostream> using namespace std; int f(unsigned arg) { int k = arg + 1; return k; int main() { int howmany = 10; int j = f(howmany); cout << j << endl; // Τυπώνει 11. Η j της main παίρνει την τιμή που είχε η k κατά την εκτέλεση του return. Ο τύπος επιστροφής της συνάρτησης ταιριάζει με τον τύπο της μεταβλητής όπου αποθηκεύεται το αποτέλεσμα. 13
Παράδειγμα επιστροφής δείκτη #include <iostream> using namespace std; int* createstorage(unsigned size); // Δηλώσεις των δύο void print(int* ptr, unsigned size); // συναρτήσεων. int main() { unsigned howmany; cin >> howmany; int* storage = createstorage(howmany); print(storage, howmany); delete []storage; Ο τύπος επιστροφής της συνάρτησης ταιριάζει με τον τύπο της μεταβλητής όπου αποθηκεύεται το αποτέλεσμα. 14
Ορισμοί των δύο συναρτήσεων int* createstorage(unsigned size) { int* ptr = new int[size]; for(unsigned i = 0; i < size; i++) { cin >> ptr[i]; return ptr; // Ο ptr καταστρέφεται κατά την έξοδο από τη // συνάρτηση, αλλά ο χώρος όπου δείχνει διατηρείται. void print(int* ptr, unsigned size) { for(unsigned i = 0; i < size; i++) { cout << i << ": " << ptr[i] << endl; Μπορώ να σκεφτώ την κλήση της συνάρτησης σαν έναν δείκτη που θα δείχνει εκεί όπου έδειχνε ο ptr. 15