Anfängerfragen: Funktion auslagern, Include

Hallo an alle,

ich habe 3 folgende grundelgende Fragen zu C++:

  1. Welchen Vorteil bringt es die Funktionsdeklaration in eine *.h-Datei auszulagern?

  2. Welchen Unterschied gibt es zwischen der Auslagerung einer Funktion in eine *.h- und *.cpp-Datei? Wie ich verstehe ist beides möglich.

  3. Wenn ich in einer ausgelagerten Funktion z.B. „cout“ verwende, so muss ich in diese Datei auch „IOSTREAM“ einbinden und ggf. den Namensbereich mit „using namespace“ festlegen?
    Verwende ich nun in der Hauptdatei auch „cout“, so muss ich auch hier „IOSTREAM“ einbinden und ggf. den Namensbereich mit „using namespace“ festlegen?
    Lässt sich diese Redundanz vermeiden?

Danke für eure Antworten.

Vitali

Hallo Vitali,

Zuerst einmal musst du verstehen, dss dr Preprocesor eigentlich ein Textprocessor ist und eigentlich nichts mit der Programmiersprache C/C++ zu tun hat.
Er sucht nach Schlüsselwärtern welche mit „#“ anfangen und führt diese aus.
Dabai macht er eigentlich nichts, was mit Cut&amp:stuck_out_tongue_winking_eye:aste nicht auch getan werden könnte.
#include“ setzt einach nur den Textas der angegebenen Datei an dieser STelle im Source-Code ein.

  1. Welchen Vorteil bringt es die Funktionsdeklaration in eine
    *.h-Datei auszulagern?

Das merkst du erst dann, wenn du ein grösseres Projek, bzw Bibliotheken hast.
Du hast z.B. in einem Modul die Funktion „void f(int i)“. Um diese aus aderen Modulen aufrufen zu können, musst du diese in jedem Modul decklarieren, mit „void f(int i);“. Die deklaration braucht der Compiler um die Aufrufparamter prüfen zu können, auch weren vom Compiler automatisch die Typenconvertierungen vorgenommen.

Jetzt musst du „void f( int i)“ ind „void f( long i)“ ändern.
Ist kein Problem, die Typenconvertierung von int in long kann der Compiler automatisch machen.

Bei Verwendung einer .h musst du genau 2 Stellen ändern, einmal im Code, bei der Definition der Funktion, und einmal in der .h.
Adernfalls musst du alle Dateien durchsuchen …

Mit eine .h macht man eine Art Modul-Kapselung. Alles Aufrufe, Konstanten und Datentypen, welche für die Verwendung benötigt werden, pakt man in eine.h, diese wird auch im Modul mit eingebunden, damit alle Deklarationen nur einmal getippt werden müssen. Zudem hat man dann auch durch den Compiler einr Prüfung auf Konsistenz.

  1. Welchen Unterschied gibt es zwischen der Auslagerung einer
    Funktion in eine *.h- und *.cpp-Datei? Wie ich verstehe ist
    beides möglich.

Das ist eigentlich nur eine Konvention, du kannst technisch auch eine .xx-Datei vewenden.
In einer .h befinden sich Deklarationen, Konstanten usw.
In einer .c oder .cpp befindet sich Code.

  1. Wenn ich in einer ausgelagerten Funktion z.B. „cout“
    verwende, so muss ich in diese Datei auch „IOSTREAM“ einbinden
    und ggf. den Namensbereich mit „using namespace“ festlegen?
    Verwende ich nun in der Hauptdatei auch „cout“, so muss ich
    auch hier „IOSTREAM“ einbinden und ggf. den Namensbereich mit
    „using namespace“ festlegen?
    Lässt sich diese Redundanz vermeiden?

Nein.
C/C++ hat eigentlich einen ganz kleinen Funktionsumfang.
z.B. ist printf() gar nicht ein Bestandteil von C/C++ snder Teil einer, genormten, Bibliothek.
Dies hat den Vorteil, dass, im Prinzip, jeder seine eigene printf() schreiben kann, ohne, wenner sich an die Aufrufkonventionen hält, am restlichen Code etwas änden zu müssen.
Bei z.B. BASIC ist „PRINT“ und bei Pascal „WRITE“, sind dies Bestanteile der Sprache und es gibt keine Möglichkeit a diesen Funktionen etwas zu verändern. Bei Pascal kommt noch erschewrend hinzu, dass WRITE eine inkonsistenz der Sprache hat: variable Parateranzahln, sind in Standard-Pascal gar nicht möglich, aber WRITE kennt sie.

Vieles wird sich dir noch nicht erschliessen, wenn du aber ma in den Bereich kommst, wo deine Programme einige 1’000 Codezeilen erreichen oder überschreiten, wirst du die Module brauchen und schätzen lernen oder kläglich untergehen!

So lange du ein Betriebssystm verwendet, wirst du auch nie in die Verlegenheit kommen printf() selber schreiben zu müssen. Aber wenn du Programme für einen Microcontroller ohne Betriebssystem schreiben musst, sieht es anders aus.
Noch lustiger wird es, enn Programmteile auf einem PC und auf eiem Microcontroller benötogt werde, wi z.B. bei einem Übertragungsprotokoll.

MfG Peter(TOO)

Danke, Peter.

1 und 2 leuchten jetzt ein.

Dass es im Fall von 3 so ist wie Du schilderst akzeptiere ich, aber gefallen tut es mir nicht :smile:
Verstehe nicht warum die Sprache sowas zulässt und kein Konstrukt bietet um es zu vermeiden. Da ist es realistisch, dass in größeren Projekten, durch x-fache Einbindung einer Datei, der Code sich x-fach wiederholt.

Howdy Vitali,

Verstehe nicht warum die Sprache sowas zulässt und kein Konstrukt
bietet um es zu vermeiden. …

nein, es wiederholt sich hier kein Code, denn jeder schuetzt seine Headerdatei mit einer Präprozessorfolge, z.B.

#ifndef X\_INCL\_H
#define X\_INCL\_H
...

#endif

damit doppeltes Inkludieren vermieden wird. Zudem ist er meiste Code (die Implementierung) in den .cpp Dateien zu finden. In Headerdateien findet man idR keine Implementierungen oder nur die von Inline Methoden innerhalb von Klassen.

Zum anderen solltest du sehr gut schauen, was du wie bezeichnest.

cout ist keine Funktion
IOSTREAM gibt es nicht, lediglich iostream
Und die Verwendung von using namespace Anweisungen, so wirst du spaeter sehen, ist etwas, was man sich sehr schnell abgewöhnen sollte. In den meisten professionalen Coding Standards ist es fuer die allermeisten Anwendungsfaelle sogar unerwuenscht (bzw. untersagt). Vollstaendige und klare Namen sind dort angesagt.

Gruss
norsemanna

Hallo Vitali,

der Sinn der Aufteilung liegt darin begründet, wie ausführbare Programme erzeugt und ausgeführt werden: Compiler, Linker, Bibliotheken, Laufzeitumgebung. Wenn man das mal wirklich verstanden hat, lösen sich Deine Bedenken in Wohlgefallen auf.

In der Entwicklung kommen immer fertige Teile zusammen, die von anderen genutzt werden. Und um den anderen Teilen mitzuteilen, wie die Nutzung funktioniert, wird diese Information über Header für andere bereitgestellt.

Ordentliche Header erzeugen dabei niemals **Code oder Speicher.
Nebenbei gibt es noch ein paar hilfreiche Konstanten und Makros.

Ciao, Allesquatsch**

keine Sorge um den Zwischencode
Hallo Vitali

Verstehe nicht warum die Sprache sowas zulässt und kein
Konstrukt bietet um es zu vermeiden. Da ist es realistisch,
dass in größeren Projekten, durch x-fache Einbindung einer
Datei, der Code sich x-fach wiederholt.

Die Präprozessor muss zwar alle zeilen Lesen, teils auch mehrfach, doch das hat u.a. folgende Vorteile:

  • es muss je nur einmal gelesen werden.
  • aller Quelltext (einer cpp-Datei) für den Compiler steht in einer einzigen Datei (kein implizites Wissen, wie nun diese Klasse oder jene Funktion einer anderen Cpp-Datei aussieht)
  • Man kann den Output des Präprozessors anschauen oder für eigene Zwecke verwenden. Z.B. zur Fehlersuche oder um die Platform zu wechseln.

Da jeweils nur eine cpp-Datei an den Compiler geht, und der PC-Arbeitsspeicher riesig ist, ist es quasi egal, ob 100, 1000 oder 10 Millonen Zeilen gelesen werden müssen. Wichtig ist, dass der Quelltext (auch Deine Header) gut lesbar sind. Sinnvoll sind dazu oft zusätzliche Header-Dateien, die alle häufig im Projekt benutzten Header includieren. So braucht man in der .cpp dann nur diese includen.

Viel Spaß

wilbert

Präcompilierte Header
Hallo Vitali & wilbert,

Da jeweils nur eine cpp-Datei an den Compiler geht, und der
PC-Arbeitsspeicher riesig ist, ist es quasi egal, ob 100, 1000
oder 10 Millonen Zeilen gelesen werden müssen. Wichtig ist,
dass der Quelltext (auch Deine Header) gut lesbar sind.
Sinnvoll sind dazu oft zusätzliche Header-Dateien, die alle
häufig im Projekt benutzten Header includieren. So braucht man
in der .cpp dann nur diese includen.

Moderne Compiler unterstützen auch vorcompilierte Header.

Die Header werden dabei in ein internes Format umgewandelt, welches nur noch in den Speicher geladen werden muss um die eigentliche .c oder .cpp zu übersetzen.
Dazu gehört dann auch noch eim Mechanismus welcher automatisch erkenn, wenn eine Datei geändert wurde.

Bei grösseren Projekten werden sowieso MAKE-Files verwendet.
Diese kennen die Abhängigkeiten der ganzen dateies, sodass dann nur diejenigen Module neu compiliert werden müssen, welche ein Änderung haben. Was Übersetzungszeit spart-

Desweiteren gibt es auch noch incrementelle Linker.
Diese ersetzen dann nur die neu erzeugten Module in der ausführbaren Datei.
Das ergibt zwar grössere Dateien, erspart aber weitere Zeit. Zudem sind während der Entwicklung, normalerweise, sowieso noch die ganzen Debuginformationen in der audführbaren Datei enthalten, wodurch das keine Rolle spielt.
Da viele Optimierungen beim Debuggen sowieso nicht funktionieren, bzw. unübersichtlich sind, wird für die Produktion sowieso ein komplett neuer Compilerlauf nötig.

MfG Peter(TOO)

Danke!
Ich danke euch für die ausführliche Antworten auf meine Anfängerfragen. Auch für die Tipps zur Arbeitsweise.

MfG
Vitali