Ο φίλος Σταματίου μάλλον έχει πάει για μπάνια (και πολύ καλά έχει κάνει... ζηλεύω

) οπότε δίνω κι εγώ τη λύση της άσκησης για να πάμε παρακάτω (ελπίζω φίλε Σταματίου να την παλέψεις μονάχος σου, γιατί θα σου εμπεδώσει τις βασικές αρχές... αν δεν μπορέσεις, ΜΗΝ τη δεις έτοιμη, γράψε εδώ τι απορία έχεις, το θεωρώ πολύ σημαντικό να την φτιάξεις μόνος σου).
ΚΩΔΙΚΑΣ- Κώδικας: Επιλογή όλων
#include <stdio.h>
// ----------------------------------------------------------
void swap( int *x, int *y )
{
int temp = *x;
*x = *y;
*y = temp;
return;
}
// ----------------------------------------------------------
int main( void )
{
char inbuf[256]="";
int x, y;
printf("Δώστε 2 ακέραιους, ξεχωρισμένους με κενό διάστημα (x y): ");
fgets(inbuf, 256, stdin);
sscanf(inbuf, "%d %d", &x, &y);
printf("\tΔώσατε: %d %d\n", x, y);
swap( &x, &y );
printf("Αντεστραμμένοι: %d %d\n", x, y);
return 0;
}
ΣΗΜΕΙΩΣΕΙΣΑλγοριθμικά η εναλλαγή γίνεται με τη βοήθεια μιας βοηθητικής μεταβλητής, που την ορίζουμε τοπικά μέσα στη συνάρτηση swap (και την έχω ονομάσει: temp στον παραπάνω κώδικα).
Αρχικά "σώζουμε" στην temp την τιμή του πρώτου αριθμού: *x, κατόπιν βάζουμε στο *x την τιμή του *y και τέλος βάζουμε στο *y την τιμή του *temp (που κρατάει "σωσμένη" την τιμή που είχε το *x πριν του αναθέσουμε την τιμή του *y).
Παρατηρήστε για μια ακόμα φορά πως επειδή μας ενδιαφέρουν οι τιμές των x και y και μετά το τέλος της συνάρτησης, όταν την καλούμε στην main της περνάμε τις διευθύνσεις των x και y (δηλαδή με τον τελεστή & μπροστά από τα ονόματά του) ΚΑΙ μέσα στη συνάρτηση χρησιμοποιούμε τα εν λόγω ορίσματα ως δείκτες (δηλαδή με αστερίσκο μπροστά από τα ονόματά τους).
Ας επιστρέψουμε τώρα σε λίγη ακόμα θεωρία.
Έστω ότι θέλουμε να γράψουμε με χρήση συνάρτησης ένα πρόγραμμα που θα διαβάζει 2 πραγματικούς αριθμούς και θα τυπώνει το αποτέλεσμα της διαίρεσής τους:
Ευκολάκι

- Κώδικας: Επιλογή όλων
#include <stdio.h>
// ----------------------------------------------------------------------------
float div2f(float x, float y)
{
return x/y;
}
// ----------------------------------------------------------------------------
int main()
{
char inbuf[256]="";
float x, y;
printf("Δώστε 2 πραγματικούς αριθμούς, ξεχωρισμένους με κενό διάστημα (x y): ");
fgets(inbuf, 256, stdin);
sscanf(inbuf, "%f %f", &x, &y);
printf("%f / %f = %f\n", x, y, div2f(x,y) );
return 0;
}
Έχουμε όμως ένα πρόβλημα: όταν ο χρήστης δώσει για διαιρέτη το 0 τότε η διαίρεση θα κρασάρει το πρόγραμμα (στην χειρότερη) ή θα βγάλει λάθος αποτέλεσμα (στην καλύτερη, ανάλογα τον compiler).
Άρα πρέπει το πρόγραμμά μας να διενεργεί έλεγχο για τον διαιρέτη πριν προβεί στην πράξη της διαίρεσης και να την αποτρέπει αν ο διαιρέτης ισούται με 0.
Εδώ λοιπόν καλούμαστε να διαλέξουμε ένα από αρκετά σενάρια υλοποίησης αυτού του ελέγχου, καθώς και τον τρόπο με τον οποίον θα αντιδράσει το πρόγραμμά μας σε περίπτωση που ο χρήστης μας δώσει 0 για διαιρέτη.
Ένα σενάριο είναι να κάνουμε τον έλεγχο στην main πριν καλέσουμε τη συνάρτηση και αν δούμε 0 για διαιρέτη είτε να τερματίσουμε το πρόγραμμα (με ή χωρίς ενημέρωση του χρήστη) είτε να ξανα-ζητάμε συνεχώς αριθμούς μέχρι να μας δώσει ο χρήστης μη μηδενικό διαιρέτη.
Π.χ η 2η περίπτωση του παραπάνω σεναρίου θα μπορούσε να γραφτεί κάπως έτσι....
- Κώδικας: Επιλογή όλων
#include <stdio.h>
// ----------------------------------------------------------------------------
float div2f(float x, float y)
{
return x/y;
}
// ----------------------------------------------------------------------------
int main()
{
char inbuf[256]="";
float x, y;
do {
printf("Διαιρετέος & διαιρέτης, ξεχωρισμένοι με κενό διάστημα (x y): ");
fgets(inbuf, 256, stdin);
sscanf(inbuf, "%f %f", &x, &y);
} while ( y == 0 );
printf("%f / %f = %f\n", x, y, div2f(x,y) );
return 0;
}
Δεν είναι κακή υλοποίηση για αρχή!
Από τη στιγμή όμως που ο έλεγχος γίνεται έξω από τη συνάρτηση div2f, γίνεται επιτακτική η ανάγκη για έλέγχο του επίφοβου 2ου ορίσματος
πριν καλεστεί η συνάρτηση. Αυτό είναι πολύ πιθανό να μας δημιουργήσει μεγάλο πρόβλημα (απροειδοποίητο κρασάρισμα του προγράμματός μας) αν χρησιμοποιούμε αυτή τη συνάρτηση σε πιο σύνθετο πρόγραμμα, όπου η div2f χρησιμοποιείται μέσα σε κάποια άλλη ή άλλες συναρτήσεις, ως μέρος ευρύτερων υπολογισμών που εμπεριέχουν μέσα τους και διαίρεση.
Σε πιο σύνθετες συναρτήσεις, είναι πολύ πιθανό το πρόβλημα να δημιουργηθεί από "κακές" τιμές περισσότερων του ενός ορίσματος, οπότε σε τέτοιες περιπτώσεις θα πρέπει κάθε φορά να ελέγχουμε ξεχωριστά όλα τα εν δυνάμει επίφοβα ορίσματα της συνάρτησης πριν την καλέσουμε.
Αντί λοιπόν να καθόμαστε να ελέγχουμε ένα-ένα όλα τα επίφοβα ορίσματα πριν από
κάθε κλήση της συνάρτησης, κάνουμε το εξής:
- διενεργούμε τους ελέγχους μέσα στη συνάρτηση
- αν διαπιστώσουμε "προβληματική" τιμή σε κάποιο από τα ορίσματά της, δεν κάνουμε την πράξη αλλά τερματίζουμε τη συνάρτηση βάζοντας όμως πρώτα ως τιμή επιστροφής της συνάρτησης κάποια τιμή η οποία θα μας σηματοδοτήσει πως η συνάρτηση δεν ολοκλήρωσε επιτυχώς το σκοπό της.
- έξω από τη συνάρτηση ελέγχουμε την τιμή επιστροφή της (ένα μόνο δηλαδή πράγμα) και όχι όλα τα επίφοβα εν δυνάμει ορίσματά της
Καταρχήν λοιπόν πρέπει να αλλάξουμε τη "λογική" της συνάρτηση div2f κι αντί να μας επιστρέφει το αποτέλεσμα της διαίρεσης να την βάλουμε να μας επιστρέφει κάτι που θα παίρνει διαφορετικές τιμές ανάλογα με το αν η συνάρτηση εκτέλεσε επιτυχώς ή όχι τον κύριο σκοπό της.
Σε αυτές τις περιπτώσεις συνήθως ορίζουμε την τιμή επιστροφής της συνάρτησης να είναι τύπου int, η οποία αν κάτι πήγε στραβά παίρνει την τιμή 0 (FALSE), αλλιώς παίρνει μια οποιαδήποτε άλλη τιμή.
Τώρα όμως η συνάρτησή δεν θα μας επιστρέφει το αποτέλεσμα της διαίρεσης! Οπότε πως θα πάρουμε το αποτέλεσμα της διαίρεσης, που στο κάτω-κάτω είναι και το βασικό ζητούμενο της συνάρτησης; Πολύ απλά, θα χρησιμοποιήσουμε και 3ο όρισμα, το οποίο θα το περνάμε by reference στη συνάρτηση και το οποίο θα περιέχει το αποτέλεσμα της διαίρεσης

- Κώδικας: Επιλογή όλων
int div2f( float x, float y, float *result )
{
if (y == 0)
return 0;
*result = x / y;
return 1;
}
Μια μικρή παρένθεση: παρατηρήστε πως δεν χρειάζεται else μετά το if, διότι αν η ροή μπει μέσα στο if η συνάρτηση τερματίζεται έτσι κι αλλιώς λόγω του return (οπότε δεν έχει νόημα να υπάρχει και else... προφανώς θα δουλέψει κανονικά και με else, αλλά είναι τελείως περιττό).
========= ΔΕ ΧΡΕΙΑΖΕΤΑΙ ΝΑ ΔΙΑΒΑΣΕΤΕ ΑΥΤΟ ΤΟ ΚΟΜΜΑΤΙ (ΠΡΟΣΠΕΡΑΣΤΕ ΤΟ ΑΝ ΣΑΣ ΜΠΕΡΔΕΥΕΙ) ========
Μια ακόμα παρένθεση: δεν ξέρω αν γνωρίζετε τους τελεστές ? : της C οι οποίοι είναι εναλλακτική έκφραση του if-else (συνήθως εκτελείται και ταχύτερα) αλλά αν όχι, χρησιμοποιούνται για απευθείας εκχώρηση μιας τιμής σε μια μεταβλητή, ανάλογα με το αν ισχύει ή όχι κάποια συνθήκη. Το κλασικό παράδειγμα εδώ είναι όταν θέλετε να "τραβήξετε" το ελάχιστο ή το μέγιστο μεταξύ 2 μεταβλητών.
Για παράδειγμα, για να βάλετε το μέγιστο μεταξύ των x και y στο z, μπορείτε να γράψετε κάτι σαν το παρακάτω:
- Κώδικας: Επιλογή όλων
int main( void )
{
int x=10, y=20, z;
z = x > y ? x : y; // είναι το x μεγαλύτερο του y ? z = x αλλιώς (:) z = y
return 0;
}
Το συγκεκριμένο παράδειγμα μάλιστα αποτελεί κλασική περίπτωση για τη δημιουργία δυο macros (#define) στη C, ένα για MIN κι ένα για MAX...
- Κώδικας: Επιλογή όλων
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
#define MAX(x,y) ( (x) > (y) ? (x) : (y) )
// ----------------------------------------------
int main( void )
{
int n1 = 10, n2 = 20;
printf("Ο ελάχιστος μεταξύ των %d και %d είναι το %d\n", MIN(n1,n2) );
printf("Ο μέγιστος μεταξύ των %d και %d είναι το %d\n", MIN(n1,n2) );
return 0;
}
όταν ορίζετε macros που παίρνουν παραμέτρους, τότε στο κώδικά τους να βάζετε ΠΑΝΤΑ τις παραμέτρους μέσα σε παρενθέσεις.
Οπότε, μπορούμε τώρα να γράψουμε το πρόγραμμά μας ως εξής:
- Κώδικας: Επιλογή όλων
#include <stdio.h>
// ----------------------------------------------------------------------------
int div2f( float x, float y, float *result )
{
if (y == 0)
return 0;
*result = x / y;
return 1;
}
// ----------------------------------------------------------------------------
int main()
{
char inbuf[256]="";
float x, y, result;
do {
printf("Διαιρετέος & διαιρέτης, ξεχωρισμένοι με κενό διάστημα (x y): ");
fgets(inbuf, 256, stdin);
sscanf(inbuf, "%f %f", &x, &y);
} while ( div2f(x, y, &result) == 0 );
printf("%f / %f = %f\n", x, y, result );
return 0;
}
Στη συνθήκη του do-while loop δεν ελέγχουμε πια για την τιμή του y, αλλά για την τιμή επιστροφής της συνάρτησης div2f, η οποία καλείται συνεχώς μέχρι η τιμή επιστροφής της να μην είναι 0 (FALSE). Αν η ροή του προγράμματος φτάσει στο printf, σημαίνει πως η div2f επέστρεψε μη μηδενική τιμή, και άρα το result περιέχει το σωστό αποτέλεσμα της διαίρεσης.
Στην div2f δεν βλέπουμε μεγάλη διαφορά μεταξύ των δυο υλοποιήσεων, αλλά σε μια άλλη συνάρτηση με περισσότερα επίφοβα ορίσματα θα ήμασταν υποχρεωμένοι στη συνθήκη του do-while να τα ελέγχουμε όλα τα επίφοβα, ενώ τώρα ελέγχουμε μονάχα ένα πράγμα, την τιμής επιστροφής της συνάρτησης.
Επίσης, ακόμα κι αν καλέσουμε την div2f τελείως ξεκάρφωτα, χωρίς να ελέγξουμε την τιμή επιστροφής της ΔΕΝ ΥΠΑΡΧΕΙ ΠΕΡΙΠΤΩΣΗ ΠΟΤΕ να επιχειρήσει να κάνει διαίρεση με το 0... ακόμα κι αν της περαστεί το 0 ως τιμή του ορίσματος y (γιατί ο συγκεκριμένος έλεγχος γίνεται μέσα στο σώμα της, και άρα αποτρέπεται εσωτερικά από την ίδια την συνάρτηση το όποιο πρόβλημα). Θα βγάλει λάθος result αλλά το πρόγραμμά μας ΔΕΝ θα κρασάρει!
Τώρα, για να κάνουμε ακόμα πιο ξεκάθαρο στον κώδικα πως η τιμή επιστροφής της div2f δεν είναι ένας οποιοσδήποτε int αλλά είναι boolean (δηλαδή είτε FALSE είτε μη FALSE) που σηματοδοτεί την επιτυχία ή όχι της συνάρτησης, μπορούμε να χρησιμοποιήσουμε ένα macro FALSE με τιμή 0, για να φαίνεται ξεκάθαρα στη συνθήκη του do-while loop:
- Κώδικας: Επιλογή όλων
#include <stdio.h>
#define FALSE 0
// ----------------------------------------------------------------------------
int div2f( float x, float y, float *result )
{
if (y == 0)
return 0;
*result = x / y;
return 1;
}
// ----------------------------------------------------------------------------
int main()
{
char inbuf[256]="";
float x, y, result;
do {
printf("Διαιρετέος & διαιρέτης, ξεχωρισμένοι με κενό διάστημα (x y): ");
fgets(inbuf, 256, stdin);
sscanf(inbuf, "%f %f", &x, &y);
} while ( div2f(x, y, &result) == FALSE );
printf("%f / %f = %f\n", x, y, result );
system("pause");
return 0;
}
Έχω την αίσθηση πως θα έχουμε πολλές απορίες αυτή τη φορά, οπότε δεν βάζω άσκηση για να λύσουμε τις απορίες πρώτα!