Deklaration von Funktionen in Klassen

  • Die Übergabe das Char-Arrays erfolgt als Zeiger, weil die Array-Variable selber schon ein Zeiger ist. Das ist bei dem String Objekt nicht der Fall. Hier wird es als Referenz übergeben. Aus den schon genannten Gründen.


    Bei const char* string kann der Inhalt (der String) des Arrays nicht verändert werden, jedoch die Variable (der Zeiger) selbst.

    Bei char* const string kann der Inhalt, jedoch nicht der Zeiger verändert werden.


    Eine Referenz muss immer initialisiert sein und zeigt somit immer auf eine Variable oder ein Objekt. Das ist bei einem Zeiger nicht unbedingt der Fall. Darum ist der Umgang mit Referenzen sicherer, auch wenn es im „Prinzip“ Zeiger sind.

    Das Reh springt hoch. Das Reh springt weit. Warum auch nicht? Es hat ja Zeit. 8o

  • Hallo Sigi


    Deine Fragen sind nicht so auf die Schnelle zu beantworten, weil der Teufel im Detail steckt. Vorausgeschickt, ich bin kein Arduino Fachmann und benutze auf 8-Bit Prozessoren (bis jetzt) ausschliesslich C.


    Grundsätzlich arbeitet auch C++ mit Zeigern korrekt. Aber es ist richtig was René sagt, dass man in C++ möglichst auf Referenzen zurückgreifen sollte, auch wenn eine Referenz intern auch nur ein Zeiger ist. Der Unterschied liegt darin, dass bei einer Referenz keine Prüfung auf NULL erfolgen muss (ein Zeiger alleine kann immer NULL sein) und dass eine Referenz auf ein Objekt nicht nur den Inhalt auf irgendwelche Daten zugänglich macht, sondern auch die zugehörenden Methoden. Der C++ Compiler kann bei Referenzen mehr Prüfungen vornehmen, kann zugehörende Konstruktoren suchen und gegebenenfalls den entsprechenden Aufruf selber realisieren, indem er ein neue Instanz einer Klasse auf dem Stack erzeugt, um den Methodenaufruf sicher zu stellen.


    drawString(const char *string ... ) // Übergabe eines Zeigers auf auf ein char[]


    Die erste Deklaration ist mir klar. Es wird eben KEINE Kopie des C-Strings übergeben sondern nur ein Pointer auf das erste Element eines NUL-terminierten C-Strings



    drawString(const String& string ... ) // Übergabe einer Referenz auf eine Instanz der Klasse String // hier wird auch KEINE Kopie auf dem Stack angelegt.


    drawString(const String *string ... ) // geht auch aber ich würde es vermeiden
    drawString(const String *string ... ) {
    if (string == NULL)
    // was mache ich hier ?? THROW …
    ….

    }


    Bei diesem Methodenaufruf wird eine Referenz auf eine Klasse String übergeben, was ein grosser Unterschied darstellt. Benötigt die Methode drawString() weitere Informationen vom Parameter, beispielsweise die Anzahl Zeichen, dann kann die Methode wiederum Methoden in der String Klasse benutzen und muss die Anzahl Zeichen nicht versuchen selber zu bestimmen. Besser noch, wenn die Klasse String von einer anderen Klasse abgeleitet wurde, dann kann es sein, dass Methoden in der abgeleiteten Klasse mit einer neuen Methode überschrieben wurde. Wer sicher gehen will, benutzt in C++ Referenzen, wer weiss was er in jedem Fall macht wird in Ausnahmefällen Zeiger benutzen. Die Klasse selbst muss die Zeichenkette nicht nach der gängigen Vorstellung eines NuLL-Terminierten Arrays ablegen und der Benutzer sollte sich auch nicht darum kümmern müssen. So kann die Klasse die Zeichenkette mit der Länge zusammen verwalten, könnte eine andere Kodierung für die Zeichen benutzen, was imme rfür den Entwickler der Klasse Sinn ergab.


    In meinen Anfängen in C++ strauchelte ich oft am Umstand dass ein Programm zwar funktionierte, dies aber (zeitlich) nicht sehr effizient erledigen konnte. Sehr oft waren solche Dinge nur mit dem Debugger zu finden, wenn bei Parameterübergaben Objekte auf dem Stack temporär instanziiert wurden um kurz darauf wieder zerstört zu werden. Wenn Objekte da einen etwas aufwändigeren Konstruktor benutzten und dann vielleicht auch der Destruktor noch mit Aufräumen beschäftigt wird, dann kann es sehr schnell zu einem enormen Verlust von Prozessorzeit führen.


    Ein ganz wichtiges Konzept bei allen objektorientierten Sprachen ist die Verkapselung von Code in einer Klasse. Der Benutzer soll sich nicht mit allen Internas der Klasse beschäftigen müssen, um die Klasse benutzen zu können. Deshalb ist der Entwickler der Klasse angehalten, die Methoden „sicher“ zu gestalten, was immer ein Abwägen der Vor- und Nachteile bedeutet. Eine Methode, die selbst am Objekt keine Veränderung durchführt, sollte immer als const deklariert werden „Obj.foo() const“. Wird ein Methodenparameter von der Methode nicht verändert, dann sollte er als const deklariert werden. Sollen beide Möglichkeiten erlaubt sein, dann wird die Methode in beiden Varianten implementiert.


    Programme in C++ haben grosse Vorteile: Der Code wird wesentlich übersichtlicher und wenn Klassen Konstrukte „richtig“ zusammenstellt, wird einiges an Doppelspurigkeiten von Code reduziert. Als Nachteil erkauft man sich einen grösseren Bedarf an RAM und Code Bedarf, vor allem auf einer 8-Bit CPU. Jeder Zeiger belegt da bereits 2 Register und für den Zugriff auf den Zeiger wird wieder etwas mehr Code benutzt. Aber die Entwicklung geht ja unweigerlich weiter, und die Preise von 16- oder 32 Bit CPU’s sind bereits heute teils günstiger als die der 8 Bit Typen.


    Als Buchempfehlung hier wieder einmal: C++ Programmiersprache von Bjarne Stroustrup

    oder Effevtive C++ von Scott Meyers. Dieses Buch führt den treffenden Untertitel 50 Specific Ways to Improve Your Programs and Designs


    Sorry, dass meine Antwort Deine Fragen nicht vollständig beantworten kann aber vielleicht hilft es Dir trotzdem weiter.
    Pius

  • Hallo Sigi,


    leider kann ich das auch nicht sagen. C/C++ ist nicht meine 'Muttersprache'.

    Im Hintergrund wird dabei mehr oder weniger derselbe Code erzeugt.


    Die Übergabe als Pointer ist ein typisches C - Konstrukt. Die Funktion weiss dann nur, dass das erste Zeichen ein char ist. Ob das jetzt nur ein einzelnen Zeichen ist oder ein nullterminierter String ist daraus nicht ersichtlich.


    In C++ versucht man Pointer möglichst zu vermeiden. Daher übergibt man eine Referenz (&) auf die Instanz einer Klasse. Die Funktion weiss dann, dass es sich dabei um ein String - Objekt handelt. Wieso es nicht möglich ist, mit String* string einen pointer zu übergeben, weiss ich leider auch nicht.


    Vielleicht weiss einer der im Forum anwesenden C++ - Spezialisten etwas mehr dazu.


    Gruss

    René

  • Hallo René,


    ich habe da ein Verständnisproblem. In den übernommenen Funktionen aus der ursprünglichen Klasse TFT_eSPI findet sich auch zweimal die Funktion drawString(). Einmal als drawString(const char *string ... ) und einmal als drawString(const String& string ... ). Die erste Deklaration ist mir klar. Es wird eben KEINE Kopie des C-Strings übergeben sondern nur ein Pointer auf das erste Element eines NUL-terminierten C-Strings (Eine Reihe (array) von Buchstaben (bytes), auf deren letztes gültiges Element eben NUL folgt). Es wird also die Adresse dieses besagten Elemets (Bytes) übergeben.


    Im zweiten Fall wird die Adresse des String-Objektes übergeben. Das ist aus meiner Sicht doch das gleiche. In beiden Fällen wird die Adresse des ersten Bytes einer Datenstruktur übergeben.


    Warum also wird der zweite Fall nicht wie folgt deklariert: drawString(const String *string ... )? Oder ginge das etwa auch?


    Ich habe wirklich versucht, Informationen dazu zu finden, war aber leider nicht erfolgreich. (Wahrscheinlich habe ich falsch gesucht. Aber um die richtige Frage zu stellen, muss man die Antwort schon zur Hälfte kennen.) Könntest Du etwas Licht in dieses Dunkel bringen?


    Vorab schon mal vielen Dank!


    Vielen Dank auch für die viele Mühe und Arbeit, die Du in Deine Videos steckst. Ich finde sie ausgesprochen lehrreich. Du hast sie immer sehr gut vorbereitet und bringst die Thematiken in einem (für mich :)) sehr angenehmen Tempo herüber. Leider bin ich berufstätig und habe daher immer nur maximal am Wochenende etwas Zeit für mein Hobby.


    Viele Grüße in die schöne Schweiz!

    Sigi