Περίληψη
Ως εδώ, κάθε πρόγραμμα του κύκλου μαθημάτων χωρούσε σε ένα μόνο αρχείο. Ένα πρόγραμμα χιλίων γραμμών δεν διαβάζεται πια με μια ματιά, και οι συναρτήσεις του αξίζει να εξυπηρετούν και άλλα προγράμματα. Αυτό το μάθημα διδάσκει πώς να το χωρίσουμε: μια κεφαλίδα που δηλώνει τι προσφέρει ένα άρθρωμα, ένα αρχείο υλοποίησης που το ορίζει και ένα main.cpp που το χρησιμοποιεί. Παρουσιάζει τους φρουρούς συμπερίληψης· τον κανόνα του ενός ορισμού, που εξηγεί τα δύο κλασικά σφάλματα του συνδέτη και τη σύγχρονη σημασία του inline· τη χωριστή μεταγλώττιση, που επαναμεταγλωττίζει μόνο ό,τι άλλαξε· και κλείνει με μια δική μας βιβλιοθήκη, φτιαγμένη από συναρτήσεις μαθημάτων που προηγήθηκαν.
Μαθησιακοί στόχοι
- Να χωρίζουμε ένα πρόγραμμα σε μια κεφαλίδα με δηλώσεις, ένα αρχείο υλοποίησης και ένα
main.cpp, και να το μεταγλωττίζουμε. - Να γράφουμε φρουρούς συμπερίληψης και να εξηγούμε ποιο πρόβλημα λύνουν· να αναγνωρίζουμε το
#pragma onceως επέκταση που δέχονται όλοι οι μεταγλωττιστές, αλλά που το πρότυπο δεν περιλαμβάνει. - Να εξηγούμε τον κανόνα του ενός ορισμού και να διαγιγνώσκουμε τις παραβιάσεις του από το μήνυμα του συνδέτη («multiple definition», «undefined reference»).
- Να μεταγλωττίζουμε χωριστά με
-cκαι να συνδέουμε τα αντικειμενικά αρχεία, εξηγώντας τι επαναμεταγλωττίζεται όταν αλλάζει κάθε αρχείο.
Διεπαφή και υλοποίηση
Η αρθρωτότητα
Μια συνάρτηση με σύμβαση, όπως εκείνες του μαθήματος Σχεδίαση με συναρτήσεις, ήδη χωρίζει ό,τι υπόσχεται, γραμμένο στη δήλωσή της και στη σύμβασή της, από τον τρόπο που το εκπληρώνει, γραμμένο στο σώμα της· όποιος την καλεί χρειάζεται μόνο το πρώτο. Αν αυτό ισχύει, τίποτε δεν εμποδίζει να φυλάξουμε τα δύο μέρη σε χωριστά αρχεία. Ένα άρθρωμα (module) είναι μια ομάδα συναρτήσεων (και των τύπων που χρησιμοποιούν) με κοινό σκοπό· η διεπαφή του είναι ό,τι προσφέρει σε όποιον το χρησιμοποιεί, και η υλοποίησή του, ο τρόπος με τον οποίο το κάνει. Στη C++, η διεπαφή γράφεται σε μια κεφαλίδα (επέκταση .hpp, ή .h, κληρονομημένη από τη C) και η υλοποίηση σε ένα αρχείο .cpp με το ίδιο όνομα.
Από αυτό προκύπτουν τρία πλεονεκτήματα. Κατανοείται κατά μέρη: για να χρησιμοποιήσουμε το άρθρωμα αρκεί να διαβάσουμε την κεφαλίδα του. Μεταγλωττίζεται κατά μέρη: μια αλλαγή στην υλοποίηση δεν αναγκάζει να επαναμεταγλωττιστούν όσοι χρησιμοποιούν μόνο τη διεπαφή. Και δοκιμάζεται κατά μέρη, απέναντι σε κάθε σύμβαση, όπως θα διδάξει το μάθημα Δοκιμές προγραμμάτων. Ο κύκλος μαθημάτων «LaTeX για την ακαδημαϊκή γραφή» χωρίζει με τον ίδιο τρόπο ένα μεγάλο έγγραφο, στο μάθημά του «Μεγάλα έγγραφα».
Τρία αρχεία
Το άρθρωμα επίπεδης γεωμετρίας αυτού του παραδείγματος προσφέρει τρεις συναρτήσεις· οι δύο από αυτές προέρχονται από το μάθημα 5.3. Η κεφαλίδα μόνο τις δηλώνει, καθεμία με τη σύμβασή της. Οι τρεις γραμμές που αρχίζουν με # θα εξηγηθούν στην παράγραφο που ακολουθεί· προς το παρόν αρκεί να ξέρουμε ότι κάθε κεφαλίδα τις έχει.
// geometry.hpp: διεπαφή του αρθρώματος επίπεδης γεωμετρίας.#ifndef GEOMETRY_HPP#define GEOMETRY_HPP// Εμβαδόν του κύκλου με ακτίνα radius.// Προσυνθήκη: radius >= 0.// Μετασυνθήκη: επιστρέφει το εμβαδόν του κύκλου με ακτίνα radius.double circleArea(double radius);// Αν τρία μήκη είναι οι πλευρές ενός τριγώνου.// Προσυνθήκη: καμία.// Μετασυνθήκη: επιστρέφει true αν και μόνο αν υπάρχει τρίγωνο με πλευρές a, b και c.bool isTriangle(double a, double b, double c);// Εμβαδόν του τριγώνου με πλευρές a, b και c.// Προσυνθήκη: isTriangle(a, b, c).// Μετασυνθήκη: επιστρέφει το εμβαδόν του τριγώνου με πλευρές a, b και c (τύπος του Ήρωνα).double triangleArea(double a, double b, double c);#endifΤο αρχείο υλοποίησης συμπεριλαμβάνει τη δική του κεφαλίδα και ορίζει τις συναρτήσεις. Οι κεφαλίδες της πρότυπης βιβλιοθήκης που χρειάζεται (<cmath> για την τετραγωνική ρίζα, <numbers> για το ) συμπεριλαμβάνονται εδώ και όχι στην κεφαλίδα, επειδή όποιος χρησιμοποιεί το άρθρωμα δεν τις χρειάζεται.
// geometry.cpp: υλοποίηση του αρθρώματος επίπεδης γεωμετρίας.#include "geometry.hpp"#include <cmath>#include <numbers>double circleArea(double radius){ return std::numbers::pi * radius * radius;}bool isTriangle(double a, double b, double c){ return a > 0 && b > 0 && c > 0 && a < b + c && b < a + c && c < a + b;}double triangleArea(double a, double b, double c){ const double s{(a + b + c) / 2}; return std::sqrt(s * (s - a) * (s - b) * (s - c));}Τέλος, το main.cpp συμπεριλαμβάνει την κεφαλίδα και χρησιμοποιεί το άρθρωμα, χωρίς να βλέπει ούτε μία γραμμή της υλοποίησής του:
#include <print>#include "geometry.hpp"int main(){ std::println("Κύκλος ακτίνας 2: {:.4f}", circleArea(2.0)); std::println("Υπάρχει τρίγωνο με πλευρές 3, 4, 5; {}", isTriangle(3.0, 4.0, 5.0)); std::println("Το εμβαδόν του: {}", triangleArea(3.0, 4.0, 5.0)); std::println("Και με πλευρές 1, 2, 3; {}", isTriangle(1.0, 2.0, 3.0));}Κύκλος ακτίνας 2: 12.5664Υπάρχει τρίγωνο με πλευρές 3, 4, 5; trueΤο εμβαδόν του: 6Και με πλευρές 1, 2, 3; falseΤα εισαγωγικά του #include "geometry.hpp" αναζητούν το αρχείο πρώτα απ' όλα στον φάκελο του αρχείου που το συμπεριλαμβάνει, και οι γωνιακές αγκύλες του #include <print>, στους φακέλους του συστήματος: τα εισαγωγικά είναι για τα δικά μας αρχεία. Το αρχείο geometry.cpp συμπεριλαμβάνει πρώτη τη δική του κεφαλίδα, ώστε, αν έλειπε από την κεφαλίδα μια συμπερίληψη που η ίδια χρειάζεται, το σφάλμα να εμφανιστεί κατά τη μεταγλώττιση του αρθρώματος και όχι στο αρχείο κάποιου άλλου. Και τρίγωνο με πλευρές 1, 2 και 3 δεν υπάρχει (), οπότε η προσυνθήκη του triangleArea υποχρεώνει να το ρωτήσουμε πρώτα.
Για τη μεταγλώττιση, η εντολή δέχεται τα δύο αρχεία .cpp· η κεφαλίδα δεν αναφέρεται, επειδή μπαίνει στο καθένα μέσω του #include. Οι επιλογές του κύκλου μαθημάτων φυλάγονται σε μια μεταβλητή του τερματικού, τη FLAGS, για να μην τις επαναλαμβάνουμε σε κάθε εντολή· το $FLAGS τις ανακτά. Έτσι γράφεται στο Linux και στο macOS· στο PowerShell οι μεταβλητές γράφονται αλλιώς, και καλό είναι να γράφουμε τις επιλογές ολόκληρες ή να μεταγλωττίζουμε από τον επεξεργαστή κειμένου (παράγραφος «Χωριστή μεταγλώττιση»).
$ FLAGS="-std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined"$ g++ $FLAGS main.cpp geometry.cpp -o main$ ./mainΚύκλος ακτίνας 2: 12.5664Υπάρχει τρίγωνο με πλευρές 3, 4, 5; trueΤο εμβαδόν του: 6Και με πλευρές 1, 2, 3; falseΤο σχήμα επαναλαμβάνει εκείνο του μαθήματος Προγράμματα, μεταγλωττιστές και C++ για αυτό το έργο.
Απόδειξη
Όποιος προγραμματίζει γράφει τρία αρχεία: μια κεφαλίδα και δύο αρχεία
.cpp. Μόνο τα.cppδίνονται στον μεταγλωττιστή.Ο προεπεξεργαστής αντιγράφει το
geometry.hppσε κάθε αρχείο που το συμπεριλαμβάνει. Υπάρχουν, επομένως, δύο αντίγραφα των δηλώσεων, ένα σε κάθε μονάδα μετάφρασης, και αυτό είναι σωστό: μια συνάρτηση μπορεί να δηλωθεί όσες φορές θέλουμε.Κάθε μονάδα μεταγλωττίζεται χωριστά στο αντικειμενικό αρχείο της. Όταν μεταγλωττίζει το
main.cpp, ο μεταγλωττιστής δεν κοιτάζει τοgeometry.cpp: η δήλωση τού αρκεί για να ελέγξει τους τύπους κάθε κλήσης.Ο συνδέτης ενώνει τα δύο αντικειμενικά αρχεία και την πρότυπη βιβλιοθήκη, και αντιστοιχίζει κάθε κλήση του
main.oπρος τηcircleAreaμε τον ορισμό που βρίσκεται στοgeometry.o.
Ένα συχνό σφάλμα είναι να συμπεριλάβουμε το αρχείο υλοποίησης, #include "geometry.cpp". Αν επιπλέον αναφέρεται στην εντολή, οι συναρτήσεις του ορίζονται δύο φορές και η σύνδεση αποτυγχάνει, εξαιτίας του κανόνα που θα εξηγήσει η παράγραφος «Ο κανόνας του ενός ορισμού»· αν δεν αναφέρεται, το main.cpp ξαναγίνεται ένα ενιαίο κείμενο που επαναμεταγλωττίζεται ολόκληρο με κάθε αλλαγή. Τα αρχεία .cpp μεταγλωττίζονται· οι κεφαλίδες συμπεριλαμβάνονται.
Φόρτωση του περιεχομένου…
Ολοκληρώθηκε
Συνδεθείτε για να αποθηκεύσετε την πρόοδό σας