Wann Header-Datei und wann Cpp-Datei



  • Hallo,

    ich bin Anfänger in C++ und bin gerade etwas verwirrt. Folgende Situation:

    Ich habe ein kleines Programm mit zwei Funktionen, die die Fakultät bis 12! berechnen sollen. Eine Funktion ist iterativ, eine rekursiv.

    Ich habe die Funktionen in einer .h-Datei definiert:

    unsigned long fakultaet_rekursiv(unsigned long n) {
          if (n == 0) {
    	return 1;
          } else {
    	return n * fakultaet_rekursiv(n - 1);
          }
        }
    
       unsigned long fakultaet_iterativ(unsigned long n) {
          unsigned long ergebnis = 1;
          for (unsigned long i = 2; i <= n; ++i) {
    	ergebnis *= i;
          }
          return ergebnis;
        }
    

    Diese habe ich in die main-Datei (.cpp) durch #include eingebunden.
    Nun sehe ich aber in der vorgegebenen Übungslösung, dass man nicht .h-Dateien sondern .cpp-Dateien inkludieren soll.

    Meine Frage ist: Wann nehme ich .h-Dateien und wann .cpp-Dateien?

    Ich habe bereits gegoogelt und herausgefunden: .h nur für Deklarationen und .cpp nur für Definitionen.

    Heißt das, ich müsste eigentlich für die beiden Funktionen zwei Dateien inkludieren - also eine .h und eine .cpp?

    Danke für die Hilfe!



  • Hallo lykre,

    dein Google Ergebnis stimmt erstmal. Im Header (.h / .hpp) nur Deklarationen und im Source File (.cpp) die Definition.

    In der Cpp inkludierst du dann den Header.
    Also z.B.

    //foo.h
    #pragma once
    class Foo{
       doSomeStuff();
    //...
    }
    
    //foo.cpp
    #include "foo.h"
    
    Foo::doSomeStuff(){
       //do something
    }
    
    int main(){
       Foo foo;
       foo.doSomeStuff();
    }
    

    Dem Compiler musst du dann sagen, welche Source Dateien er übersetzen sollen. Und in denen ist dann ja der Header inkludiert. Also z.B. mit dem gcc müsste dann so aussehen:

    g++ -Wall foo.cpp -o foo

    Edit: Weil ich's grade gesehen habe und ich kein Blödsinn hier stehen lassen wollte, der Compiler Aufruf muss für C++ natürlich g++ und nicht gcc sein.



  • lykre schrieb:

    Nun sehe ich aber in der vorgegebenen Übungslösung, dass man nicht .h-Dateien sondern .cpp-Dateien inkludieren soll.

    Steht das da wirklich so drin?
    Mit #include includet man nur die .h-Dateien. Die zur .h-Datei zugehörige .cpp-Datei wird nicht mit #include includet. Stattdessen wird sie beim Kompilieren mit angegeben (oder z.B. im Makefile eingetragen)

    Wenn du also fakultaet.h/fakultaet.cpp und main.cpp hast, dann kannst du z.B. mit
    g++ fakultaet.cpp main.cpp -o executable
    beide Dateien zusammen kompilieren. Bei so einem kleinen Test ist das durchaus sinnvoll. Sobald dein Projekt größer ist, ist es klüger, die alle cpp-Dateien einzeln zu kompilieren und anschließend zusammenzulinken. Im Idealfall machst du das mit irgendeinem Buildsystem.



  • Ich vergaß: beim kompilieren am besten -Wall -Wextra -std=c++14 -O3 o.ä. mit angeben.
    Und: bei Fakultät den Parameter unsigned long zu machen, ist schon ambitioniert, vor allem, wenn der Returntyp auch nur unsigned long ist und nicht ein spezieller BigInteger-Typ 😉



  • Hallo lykre,

    was man wie in welche Datei schreibt, sollte viel klarer werden, wenn Du recherchierst, warum man es so macht, wie man es macht. Die Schlüsselwörter dafür sind "one definition rule" und "separate compilation".



  • wob schrieb:

    Ich vergaß: beim kompilieren am besten -Wall -Wextra -std=c++14 -O3 o.ä. mit angeben.

    was soll -O3 hier bringen?



  • zufallswert schrieb:

    wob schrieb:

    Ich vergaß: beim kompilieren am besten -Wall -Wextra -std=c++14 -O3 o.ä. mit angeben.

    was soll -O3 hier bringen?

    Ich optimiere eigentlich immer. Warum auch nicht? Ehrlich gesagt denke ich da nicht so drüber nach und mache fast immer -O3. Die Programme laufen halt schneller mit O3 als ohne... Gut, ob -O2 oder -O3 macht meist wenig Unterschied. Bei meinen Anwendungen ist meist Laufzeit >> Compilezeit.

    Deswegen auch "o.ä.", du kannst auch gerne -Og oder was auch immer machen. Ich weiß ja nicht einmal, ob lykre überhaupt g++ verwendet.



  • Danke für die vielen schnellen Tips!

    Also ich nehme g++ zum erstellen der exe-Datei. Ich habe mir angewöhnt meinen Code in eine Textdatei (in gedit oder kwrite) zu schreiben und dann über das Terminal zu kompilieren.
    Die gängigen Suites (z.B. Visual Studio) haben mich für den Anfang eigentlich nur überfordert, weil sie so viel können und dadurch auch immer gleich ein Haufen Ordner angelegt werden. Das erschien mir für meine kleinen Anfängerprojekte unnötig.

    @wob: Die Lösung ist nur ein Bild eines Abhängigkeits-Diagrammes, welches in Doxygen erstellt wurde. Da habe ich die .cpp gesehen. Du hast also recht, sie sind in dem Sinne nicht inkludiert.

    g++ fakultaet.cpp main.cpp -o executable

    Genau so mache ich es auch, nur eben bisher immer mit einer einzigen .cpp ^^" Danke, jetzt weiß ich wie ich das eingeben kann!

    Ich hätte gleich noch eine Frage zur Headerdatei:

    //foo.h
    #pragma once
    class Foo{
    doSomeStuff();
    //...
    }

    Pragma kannte ich noch nicht und habe mal gegoogelt. Laut Erklärung ist es dasselbe wie indef nur etwas eingeschränkter kompatibel - richtig?

    Ich hatte bisher ifndef kennen gelernt, bin aber unsicher bzgl. der richtigen Syntax. Wäre es so richtig?:

    #ifndef FUNKTION_H
    #define FUNKTION_H
     unsigned long fakultaet_rekursiv(unsigned long n)
     unsigned long fakultaet_iterativ(unsigned long n)
    #endif /*
    

    Mich verwirrt, dass die Präpozessor-Direktiven einen Eigennamen (großgeschrieben) benötigen obwohl ich diesen Namen eigentlich nirgendwo nutze/aufrufe, sondern das Programm nur die "normalen" Funktionen finden soll. Habe ich da einen Denkfehler?



  • lykre schrieb:

    Ich hätte gleich noch eine Frage zur Headerdatei:

    //foo.h
    #pragma once
    class Foo{
    doSomeStuff();
    //...
    }

    Pragma kannte ich noch nicht und habe mal gegoogelt. Laut Erklärung ist es dasselbe wie indef nur etwas eingeschränkter kompatibel - richtig?

    Ja, dass ist richtig. Ist nicht im Standard, aber die meisten Compiler verstehen das.

    Ich hatte bisher ifndef kennen gelernt, bin aber unsicher bzgl. der richtigen Syntax. Wäre es so richtig?:

    #ifndef FUNKTION_H
    #define FUNKTION_H
     unsigned long fakultaet_rekursiv(unsigned long n)
     unsigned long fakultaet_iterativ(unsigned long n)
    #endif /*
    

    Mich verwirrt, dass die Präpozessor-Direktiven einen Eigennamen (großgeschrieben) benötigen obwohl ich diesen Namen eigentlich nirgendwo nutze/aufrufe, sondern das Programm nur die "normalen" Funktionen finden soll. Habe ich da einen Denkfehler?

    Die Syntax von deiner ifndef include guard sieht richtig aus. Auch wenn du die Funktionsweise noch nicht ganz verstanden hast. Ich habe das mal kommentiert und hoffe, dass ich mich verständlich ausgedrückt habe.

    #ifndef FUNKTION_H                  // Test ob der Name FUNKTION_H definiert ist (der Name ist egal), wenn nicht gehe in den "if-body"
    #define FUNKTION_H                  // Definiere FUNKTION_H damit bei einem weiteren Include das ifndef "false" zurück gibt und nicht weiter in den Include gegangen wird.
     unsigned long fakultaet_rekursiv(unsigned long n)
     unsigned long fakultaet_iterativ(unsigned long n)
    #endif  // Präprozessor if zu Ende
    


  • Naja, die Syntax der .h-Datei ist nicht richtig (die Include-Guard Präprozessor-Direktiven schon).

    Was ist falsch? -> Es fehlt jeweils das Semikolon am Ende der Deklaration deiner beiden Funktionen.

    Außerdem ist es unüblich, den Programmcode zwischen den Include-Guard-Direktiven einzurücken.

    Also:

    #ifndef FUNKTION_H
    #define FUNKTION_H
    
    unsigned long fakultaet_rekursiv(unsigned long n);
    unsigned long fakultaet_iterativ(unsigned long n);
    
    #endif
    

    Dass hier "FUNKTION_H" als Name verwendet wird, ist eine Konvention, keine Pflicht. Konvention: Nenne es genauso wie die Datei heißt, aber alles in Großbuchstaben und Punkt durch _ ersetzen. Du könntest auch schreiben:

    #ifndef FeuerEisUndDosenbier
    #define FeuerEisUndDosenbier
    
    unsigned long fakultaet_rekursiv(unsigned long n);
    unsigned long fakultaet_iterativ(unsigned long n);
    
    #endif
    

    😉

    Auf SO findest du auch viele Infos, u.a. zu #pragma once vs. klassische Include-Guards, z.B. hier: https://stackoverflow.com/questions/1143936/pragma-once-vs-include-guards



  • Ich hatte auch gerade entdeckt, dass die Semikolons fehlen, weil Fehlermeldungen beim kompilieren auftauchten. 🙄

    Außerdem ist es unüblich, den Programmcode zwischen den Include-Guard-Direktiven einzurücken.

    Mit Einrückung habe ich noch totale Probleme - also was, wann und wie genau. Ich habe noch nicht gefunden ob meine Texteditoren das unterstützen. Muss ich mit auf meine *Zu-Lernen-Liste* packen.

    Erstmal vielen Dank für die Hilfe, ich bin jetzt definitv schlauer und weniger verwirrt als vorher! 😃


Anmelden zum Antworten