Διαδρομές
/
C++
C++
/
Ύλη
/
Αρχεία κεφαλίδας
Αρ

Αρχεία κεφαλίδας σε C++

2 ασκήσεις

Σχετικά με την έννοια Αρχεία κεφαλίδας

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

Κεφαλίδα και πηγαίος κώδικας

Η πιο συνηθισμένη επέκταση αρχείου για τα αρχεία κεφαλίδας είναι το .h. Κάποια έργα χρησιμοποιούν .hpp ή παραλείπουν εντελώς την επέκταση.

Οι ορισμοί βρίσκονται σε ένα ξεχωριστό αρχείο .cpp. Για να επανενωθούν τα μέρη, το αρχείο πηγαίου κώδικα ξεκινά συμπεριλαμβάνοντας το αντίστοιχο αρχείο κεφαλίδας.

Αν θέλεις να γράψεις μια βιβλιοθήκη που ονομάζεται "quick_math" και προσφέρει μια συνάρτηση "super_root" που θέλεις να χρησιμοποιείς συχνά, τα αρχεία θα έμοιαζαν κάπως έτσι:

// A file named quick_math.h
#pragma once

namespace quick_math {

double super_root(double x, int n);

}
// A file named quick_math.cpp
#include <cmath>

#include "quick_math.h"

double quick_math::super_root(double x, int n) {
    while (n) {
        x = std::sqrt(x), --n;
    }
    return x;
}

Αν χρειάζεται να συμπεριλάβεις μια κεφαλίδα που την απαιτεί μόνο η υλοποίηση, η αντίστοιχη γραμμή #include χρειάζεται μόνο στο αρχείο πηγαίου κώδικα. Ό,τι συμπεριλαμβάνεται στην κεφαλίδα είναι επίσης διαθέσιμο στο αρχείο .cpp, όπως η βιβλιοθήκη string στο παρακάτω παράδειγμα. Προσοχή: το ; χρειάζεται μετά τη δήλωση στο αρχείο κεφαλίδας, αλλά όχι μετά τον ορισμό στο αρχείο πηγαίου κώδικα.

Note

Πολλές ασκήσεις C++ στο Exercism ξεκινούν με δύο σχεδόν άδεια αρχεία: μια κεφαλίδα και ένα αρχείο πηγαίου κώδικα. Πρέπει να ελέγξεις το αρχείο *_test.cpp για να δεις τα ονόματα και τα namespaces των αναμενόμενων συναρτήσεων, ώστε να λύσεις την άσκηση.

Κλάσεις και κεφαλίδες

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

// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H

#include <string>

namespace robots {

class Flower {
   private:
    bool needs_water{};
    int size{};
    std::string name{};

   public:
    Flower(std::string name, int size = 0);
    void give_water();
    std::string get_name();
    int get_size();
    void start_next_day();
};
}  // namespace robots
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"

robots::Flower::Flower(std::string name, int size) {
    this->name = "Robotica " + name;
    this->size = size;
}

void robots::Flower::start_next_day() {
    if (!needs_water) ++size;
    needs_water = true;
}

std::string robots::Flower::get_name() { return name; }

int robots::Flower::get_size() { return size; }

Όταν η κεφαλίδα χρησιμοποιείται ως επισκόπηση του API, εκεί θα κοιτάξει κάποιος για πληροφορίες όπως οι προεπιλεγμένες τιμές. Η προεπιλεγμένη τιμή της παραμέτρου size του κατασκευαστή ορίζεται επομένως στην κεφαλίδα και όχι στην υλοποίηση. Οι ορισμοί στο αρχείο πηγαίου κώδικα έχουν ως πρόθεμα το namespace robots και τον τύπο της κλάσης Flower.

Μια άλλη επιλογή διάταξης είναι μια βιβλιοθήκη μόνο με κεφαλίδα, που δεν έχει καθόλου αρχείο .cpp:

// A file named robot_flower.h
#pragma once

#include <string>

namespace robots {

class Flower {
   private:
    bool needs_water{};
    int size{};
    std::string name{};

   public:
    Flower(std::string name, int size = 0) {
        this->name = "Robotica " + name;
        this->size = size;
    }
    void give_water() { needs_water = false; }
    std::string get_name() { return name; }
    int get_size() { return size; }
    void start_next_day() {
        if (!needs_water) ++size;
        needs_water = true;
    }
};
}  // namespace robots

Τα έργα μπορεί να χρησιμοποιούν συνδυασμούς αυτών των διατάξεων και γίνεται μεγάλη συζήτηση για το ποια ταιριάζει καλύτερα σε κάθε περίπτωση χρήσης.

Φρουροί συμπερίληψης

Ίσως πρόσεξες τη γραμμή #pragma once στο παράδειγμα αρχείου κεφαλίδας παραπάνω. Αυτό ονομάζεται φρουρός συμπερίληψης και εξασφαλίζει ότι το περιεχόμενο του αρχείου συμπεριλαμβάνεται μόνο μία φορά κατά τη μεταγλώττιση, ώστε να αποφεύγονται σφάλματα. Υπάρχει μια άλλη, πιο περίπλοκη παραλλαγή του φρουρού συμπερίληψης που ξεκινά με #ifndef και τελειώνει με #endif, η οποία περιγράφεται αναλυτικά παρακάτω.

Advanced

Προδηλώσεις

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

int myFunction(int n) {
    if (n < 10) {
        return n;
    } else {
        return myOtherFunction(n / 10);
    }
}

int myOtherFunction(int m) { return myFunction(m / 2); }

Όταν ορίζεται η myFunction, ο μεταγλωττιστής δεν γνωρίζει ακόμη την myOtherFunction. Δυστυχώς, το πρόβλημα της κυκλικής αναφοράς δεν λύνεται αλλάζοντας τη σειρά.

Η C++ προσφέρει προδηλώσεις, ώστε να γνωρίζει ο μεταγλωττιστής τις myFunction και myOtherFunction πριν αυτές οριστούν. Ο μεταγλωττιστής υποθέτει ότι ο ορισμός θα ακολουθήσει κάποια στιγμή μετά τη δήλωση. Το επόμενο παράδειγμα δείχνει πώς χρησιμοποιείται μια προδήλωση για συναρτήσεις.

int myFunction(int n);       // Forward declaration of myFunction
int myOtherFunction(int m);  // Forward declaration of myOtherFunction

// Definition of myFunction
int myFunction(int n) {
    if (n < 0) {
        return 0;
    } else {
        return myOtherFunction(n - 2);
    }
}

// Definition of myOtherFunction
int myOtherFunction(int m) { return myFunction(m / 2); }

Φρουροί συμπερίληψης μέσω ifndef

Δεν πειράζει αν το ίδιο αρχείο συμπεριληφθεί πολλές φορές μέσα σε ένα έργο. Τα αρχεία κεφαλίδας δεν πρέπει να περιέχουν ορισμούς. Το έργο στο σύνολό του δεν μπορεί να έχει τον ίδιο ορισμό πάνω από μία φορά. Αυτό ονομάζεται "κανόνας του ενός ορισμού". Ο μεταγλωττιστής θα το επιβάλει.

Είναι εύκολο να αποφύγεις πολλαπλές ακούσιες συμπεριλήψεις του ίδιου ορισμού με τους φρουρούς συμπερίληψης. Δημιουργούνται με μια ειδική διαδικασία κατά τα στάδια της μεταγλώττισης. Ο στόχος είναι να συμπεριληφθεί ένα αρχείο μόνο αν μια συγκεκριμένη μεταβλητή δεν έχει οριστεί, και μετά να οριστεί αυτή μόλις συμπεριληφθεί το αρχείο. Συχνά το όνομα αυτής της μεταβλητής επιλέγεται ως παραλλαγή του ονόματος του αρχείου. Μια άλλη μέθοδος είναι η δημιουργία ενός UUID, ώστε να μειωθεί ο κίνδυνος να χρησιμοποιηθεί κατά λάθος δύο φορές το ίδιο όνομα. Η σύνταξη φαίνεται παρακάτω, με το MY_HEADER_FILE_H ως μεταβλητή.

#ifndef MY_HEADER_FILE_H /* any name uniquely mapped to file name */
#define MY_HEADER_FILE_H

// file content

#endif

Το πρόβλημα με το #pragma once είναι ότι τα pragmas δεν αποτελούν επίσημο μέρος της γλώσσας C++ και η υλοποίησή τους διαφέρει από μεταγλωττιστή σε μεταγλωττιστή. Πολλά μεγάλα έργα έχουν περάσει στην απλούστερη μέθοδο με το pragma, αλλά λίγα παραμένουν επιφυλακτικά.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Αρχεία κεφαλίδας