Mit dieser Anleitung (Tutorial) lernst du, wie man Computer programmiert, und zwar in der Sprache C++ mit dem Qt Framework.
Warum braucht die Welt Programmierer?
Versuche mal, irgend einer Sprachassistentin den Satz "Brate mir einen Storch!" zu befehlen. Während drei Assistenten völlig unpassende Antworten liefern, reagiert Siri einfach gar nicht (Stand Feb. 2020). Fragen nach dem Wetterbericht können alle drei jedoch sehr gut beantworten. Wie kommt das?
Der Knackpunkt ist, dass Computer nur das tun können, was ihnen vorher einprogrammiert wurde. Sie sind nicht wirklich intelligent. Auf der anderen Seite arbeiten unsere Computer mit mathematischer Präzision, schnell und unermüdlich. Sie nehmen uns langweilige Arbeiten ab, und sie unterhalten uns. Wir stehen so sehr auf diese kleinen Helfer, dass es bereits Zahnbürsten und Kochtöpfe mit Internet Anbindung gibt. Um all diese Maschinen zu entwickeln, werden auf der ganzen Welt ständig gute verantwortungsbewusste Programmierer gesucht. Programmierer haben Spaß am Job, weil sie etwas sinnvolles schaffen. Außerdem werden sie gut bezahlt.
Programme bestehen aus einer großen Menge von numerischen Anweisungen, die von der CPU (dem "Gehirn" des Computers) ausgeführt werden:
1037000 7878 3131 3231 6162 6973 5f63 7473 6972 1037020 676e 7749 7453 3131 6863 7261 745f 6172 1037040 7469 4973 4577 6153 7749 4545 4c70 5245 1037060 534b 5f34 5f00 4e5a 3031 5351 7274 6e69 1037100 5267 6665 5361 5045 374b 5351 7274 6e69 1037120 0067 5a5f 534e 3774 5f5f 7863 3178 3131 1037140 6232 7361 6369 735f 7274 6e69 4967 7344 1037160 7453 3131 6863 7261 745f 6172 7469 4973 1037200 7344 5345 4961 7344 4545 5f39 5f4d 756d 1037220 6174 6574 6d45 506d 444b 6d73 5f00 4e5a 1037240 5137 7453 6972 676e 3331 6873 6972 6b6e ...
So eine "Matrix" können nur Helden aus Phantasie-Filmen lesen. Deswegen schreiben Programmierer ihre Anweisungen in einer Programmiersprache auf, die sowohl Maschine als auch Mensch verstehen können:
if (age < 16) showMessage("Du darfst Whatsapp noch nicht benutzen"); else registerAccount(username, password);
Diesen Text nennt man Quelltext. Zu den Arbeitsmitteln der Programmierer gehört ein Compiler, welcher solche Quelltexte in numerischen Maschinencode übersetzt.
In dieser Anleitung wirst du die Programmiersprache C++ kennen lernen. Die Stärke dieser Programmiersprache ist, dass sie auf allen Plattformen zur Verfügung steht: Von winzig kleinen 8-Bit Mikrocontrollern bis hin zu Mainframe Rechnern.
Da Programmierer immer wieder ähnliche Aufgaben bekommen, haben sie in den 90er Jahren damit begonnen, sogenannte Frameworks (Rahmenwerke) aufzubauen. Das sind sehr umfangreiche Sammlungen nützlicher Bausteine aus denen man Programme zusammen setzt. Ein Beispiel ist der Drucker-Dialog, der in zahlreichen Programmen wieder verwendet wird:
Jedes Framework bringt hier seine eigene Variante mit sich. Qt ist so ein plattform-unabhängiges Framework. Mit Qt kannst du Programme für zahlreiche Betriebssysteme schreiben, zum Beispiel: Windows, Linux, Android, macOS, iOS.
Bevor du deinen Computer programmieren kannst, musst du eine Entwicklungsumgebung (englische Abkürzung: IDE) installieren.
Lade den Online-Installer von der Entwicklungsumgebung "Qt Creator" herunter. Unter Windows bekommst du beim Download eine .exe Datei, die du direkt starten kannst. Sollte das Installtionsprogramm nicht starten und dabei melden, dass eine DLL Datei mit "140" im Namen fehlt, dann installiere das "C++ Redistributable für Visual Studio 2015" Paket von Microsoft, und zwar beide Versionen für x64 und x86.
Das Installationsprogramm wird dich als erstes darum bitten, einen "Qt Account" anzulegen, ohne den man nicht weiter kommt. Danach muss man die Vertragsbedingungen akzeptieren. Schließlich kommst du zur Auswahl der Qt Komponenten. Die Standardinstallation enthält
Mehr brauchst du nicht. Bringe die Installation zu Ende, dann kannst du die Entwicklungsumgebung Qt Creator starten.
Lade den Online-Installer von der Entwicklungsumgebung "Qt Creator" herunter. Unter Linux bekommst du beim Download eine .run Datei, die du im Dateimanager (mit der rechten Maustaste, dann Eigenschaften) als ausführbar kennzeichnen musst, bevor du sie starten kannst.
Das Installationsprogramm wird dich als erstes darum bitten, einen "Qt Account" anzulegen, ohne den man nicht weiter kommt. Danach muss man die Vertragsbedingungen akzeptieren. Schließlich kommst du zur Auswahl der Qt Komponenten. Die Standardinstallation enthält
Zusätzlich musst du folgendes von deiner Linux Distribution installieren:
Debian, Ubuntu, Mint | sudo apt install build-essential gdb libgl1-mesa-dev libxcb-cursor0 |
---|---|
Fedora, RedHat, CentOS | sudo yum groupinstall "C Development Tools and Libraries" sudo yum install mesa-libGL-devel |
SUSE | sudo zypper install -t pattern devel_basis |
Nun kannst du die Entwicklungsumgebung Qt Creator starten.
Für den Raspberry Pi gibt es keinen geeigneten Download auf der offiziellen Webseite von Qt. Gebe auf diesen Geräten stattdessen den Befehl "sudo apt install qtcreator" ein.
Ich glaube, man lernt eine Programmiersprache am besten kennen, indem man sie benutzt. Falls du meine Erklärungen noch nicht komplett verstehst, mache dir keine Sorgen. Das Verständnis kommt mit der Zeit. Wichtig ist zunächst nur, dass du die Beispiele auf deinem Computer ans Laufen bringst.
Eine alte Tradition besagt, daß das erste Programm "Hallo Welt!" anzeigen soll.
Um so ein Programm zu schreiben, gehe in der Entwicklungsumgebung ins Menü Datei/Neu...
Stelle in diesem Dialog ein, dass du eine "Qt Konsolenanwendung" erstellen willst. Der Begriff Konsole bezieht sich auf das oben gezeigte Fenster, welches für Text-Basierte Ein- und Ausgaben vorgesehen ist. Man nennt es Konsole oder Terminal-Fenster.
Es erscheint ein weiterer Dialog, wo du deinem Projekt einen Namen geben sollst und festlegst, in welchem Ordner das Projekt abgespeichert werden soll.
Klicke danach ein paar mal auf "weiter" bis der Text-Editor für den Quelltext erscheint.
Ersetze den dort vorgegebenen Quelltext durch den folgenden, und zwar ganz genau so, mit all den komischen Sonderzeichen:
#include <QTextStream> int main() { QTextStream out(stdout); out << "Hallo Welt!" << Qt::endl; QTextStream in(stdin); in.readLine(); }
Dann klicke links unten auf das grüne Dreieck, um das Programm auszuführen.
In Qt Versionen vor 5.14 musste man "endl" anstatt "Qt::endl" schreiben.
Das Ergebnis sollte ungefähr so aussehen:
Falls bei dir die Ausgabe stattdessen innerhalb der IDE im Bereich "Ausgabe der Anwendung" erscheint, gehe am linken Rand auf "Projekte". Dort kannst du unter "Ausführen" ein Häkchen bei "Im Terminal Ausführen" setzen. Gehe danach am linken Rand zurück auf "Editieren" um zur vorherigen Ansicht zurück zu gelangen.
Die erste Zeile im Quelltext
#include <QTextStream>
sagt dem Compiler, dass er den Inhalt der genannten Textdatei hier einfügen soll. Diese befindet sich irgendwo im Installationsverzeichnis von Qt. Für jede Komponente des Frameworks gibt es eine entsprechende Datei.
Die Komponente QTextStream stellt Funktionen zum Ausgeben und Einlesen von Text bereit. In der Fachsprache werden solche Komponenten Klassen (englisch: class) genannt.
Dein Hallo Welt! Programm benutzt zwei Objekte von der Klasse QTextStream:
QTextStream out(stdout); out << "Hallo Welt!" << Qt::endl; QTextStream in(stdin); in.readLine();
Das obere Objekt hat den willkürlich gewählten Namen "out" und wird zur Ausgabe des Textes "Hallo Welt!" benutzt.
Das untere Objekt hat den Namen "in" und wird benutzt, um eine Zeile zu lesen. Wirklich? Probiere es aus!
Falls du das Programmfenster inzwischen geschlossen hast, starte es nochmal. Du siehst den Text "Hallo Welt!" und einen blinkenden Cursor. Der Cursor signalisiert, dass das Programm auf eine Eingabe wartet. Versuche es, gebe irgend etwas ein:
Aha, der Computer hat tatsächlich auf die Eingabe reagiert. Die Meldung "Betätigen Sie die <RETURN> Taste, um das Fenster zu schließen..." kommt übrigens nicht von deinem Programm, sondern von der Entwicklungsumgebung. Sie wurde als Komfort-Faktor hinzugefügt, damit sich das Fenster nicht von alleine schließt. Dein Programm ist zu diesem Zeitpunkt bereits beendet.
Dein Programm benötigt zwei individuelle Objekte von der QTextStream Klasse, weil sie mit unterschiedlichen Parametern initialisiert werden - die markierten Teile:
QTextStream out(stdout); out << "Hallo Welt!" << Qt::endl; QTextStream in(stdin); in.readLine();
Das obere Objekt ist mit dem Kanal "stdout" initialisiert. Dieser Kanal wird von deinem Betriebssystem bereitgestellt, damit das Programm Text ausgeben kann. Das untere Objekt ist mit dem Kanal "stdin" initialisiert, den dein Betriebssystem ebenfalls bereit stellt. Deine Tastatur ist mit diesem Kanal verbunden.
Versuche mal, den Ausgabe-Text zu verändern. Achte darauf, nur den Text innerhalb der doppelten Anführungsstriche zu verändern. Zum Beispiel:
QTextStream out(stdout); out << "Jo, wir Schaffen das!" << Qt::endl;
Der Operator << schiebt den angegebenen Text in das Objekt "out". Da dieses Objekt mit dem Kanal "stdout" verbunden ist, der wiederum mit deinem Terminal-Fenster verbunden ist, erscheint der Text letztendlich im Terminal-Fenster.
Nach der Ausgabe des Textes wird noch "Qt::endl" nachgeschoben. Dieses spezielles Schlüsselwort löst einen Zeilenumbruch aus. Der Blinkende Cursor geht runter in die nächste Zeile.
In diesem Kapitel schauen wir mal unter die "Motorhaube" wie so ein C++ Programm aussieht. Dabei wirst du auch Teile vom Qt Framework kennen lernen. Wir fangen ganz einfach an.
Kopiere folgendes Programm in den Text-Editor der Entwicklungsumgebung:
#include <QTextStream> /* Dieses Programm zeigt etwas auf dem Bildschirm an. */ int main() { QTextStream out(stdout); out << "Die Polizei" << Qt::endl << "dein Freund" << Qt::endl << "und Helfer"; QTextStream in(stdin); in.readLine(); // auf Eingabe warten }
Der Quelltext enthält Kommentare vom Programmierer, die der Compiler ignoriert. Mehrzeilige Kommentare beginnen mit /* und enden mit */. Einzeilige Kommentare beginnen mit //.
Die Ausgabe entspricht allerdings nicht der Erwartung:
Da fehlt die letzte Zeile, weil im Quelltext ein "Qt::endl" für den abschließenden Zeilenumbruch fehlt. Textausgaben werden immer so lange zurück gehalten, bis ein Zeilenumbruch oder ein "flush" kommt. Probiere beides ("Qt::endl" und "Qt::flush") aus, um den Unterschied zu sehen.
Hier ein Beispiel mit flush:
#include <QTextStream> int main() { QTextStream out(stdout); out << "Die Polizei" << Qt::endl << "dein Freud" << Qt::endl << "und Helfer" << Qt::flush; QTextStream in(stdin); in.readLine(); }
So sieht die Ausgabe aus:
Du siehst, dass der blinkende Cursor hinter der dritten Zeile stehen geblieben ist, anstatt in die nächste Zeile zu springen. Sinnvoll wird das zum Beispiel in so einem Fall:
#include <QTextStream> int main() { QTextStream out(stdout); out << "Name: " << flush; QTextStream in(stdin); QString eingabe=in.readLine(); out << "Danke " << eingabe << "." << Qt::endl; }
Die Ausgabe sieht so aus:
Dort kannst du jetzt deinen Namen eingeben:
Bei dieser Gelegenheit habe ich dir ein Beispiel unter geschoben, wie man die Eingabe von stdin weiter verarbeiten kann.
QTextStream in(stdin); QString eingabe=in.readLine();
Mit der Methode in.readLine() wird eine Zeile vom Eingabekanal gelesen (read line = lese Zeile). Das Ergebnis dieser Methode wird in eine Variable gespeichert, die ich willkürlich "eingabe" genannt habe. Diese Variable speichert ein Objekt von der Klasse QString, das ist einfach gesagt ein Text.
Oder anders herum von links nach rechts gelesen: Es gibt da eine Variable von der Klasse QString mit Namen "eingabe", und sie wird mit dem Ergebnis der Methode in.readLine() befüllt.
Eine Variable ist ein Platzhalter für irgendwelche Daten, so wie das "X" in mathematischen Formeln. In der Programmiersprache C++ hat jede Variable immer einen definierten Typ, der ganz genau festlegt, was man darin speichern kann. In diesem Fall ist es eben eine Zeichenkette (string).
Schau Dir die letzte Zeile an:
out << "Danke " << eingabe << "." << Qt::endl;
Dort habe ich die Variable "eingabe" in den Ausgabe-Text eingefügt. Deswegen wird an dieser Stelle die Zeichenkette aus der Variable "eingabe" angezeigt.
Jetzt haben wir nur noch eine Zeile übrig, die nicht erklärt wurde - genauer gesagt ein Block:
int main() { ... }
Das ist schlicht und ergreifend die Funktion, die dein Betriebssystem beim Start des Programms ausführt - der sogenannte Einsprungpunkt. Dein Programm kann viele Funktionen enthalten, aber die mit dem festgelegten Namen main() wird automatisch gestartet.
Zwischen den geschweiften Klammern schreibt man die Befehle (=Anweisungen), die ausgeführt werden sollen.
Es ist festgelegt, dass die main() Funktion einen Rückgabewert als integer-Zahl liefern muss. Das sind positive oder negative Zahlen ohne Nachkommastellen. Manche Programme nutzen den Rückgabewert, um Fehler zu melden:
int main() { ... return 99; }
Nur die Zahl 0 hat eine festgelegte Bedeutung, nämlich "Alles OK". Bei allen anderen Zahlen darf sich Programmierer selber ausdenken, was sie bedeuten sollen. Wenn man die "return" Anweisung weg lässt, wird automatisch eine 0 zurück gegeben.
Zeichenketten (englisch: strings) muss man zwischen Anführungsstriche schreiben. Zahlen schreibt man ohne Anführungsstriche.
Achte darauf, dass deine Zahlen nicht mit einer führenden 0 beginnen, weil der Compiler diese sonst als Oktal-Zahl interpretiert.
In diesem Abschnitt zeige ich Dir, wozu Funktionen gut sind.
Verändere das "Hallo Welt!" Programm so, das es Name, Geburtsdatum, Schulklasse und Rufnummer von drei Schülern auf dem Bildschirm ausgibt.
Bevor du weiter liest, versuche die Aufgabe alleine zu lösen. Die nötigen Befehle kennst du bereits. Das Ergebnis soll so aussehen:
Vergleiche deinen Lösungsansatz danach mit meinem:
#include <QTextStream> int main() { QTextStream out(stdout); out << "Name: Lisa Lob" << Qt::endl; out << "Geburtdatum: 01.05.2007" << Qt::endl; out << "Klasse: 8a" << Qt::endl; out << "Rufnummer: 0211/1234567" << Qt::endl; out << Qt::endl; out << "Name: Hanna Kornblut" << Qt::endl; out << "Geburtdatum: 12.08.2007" << Qt::endl; out << "Klasse: 8b" << Qt::endl; out << "Rufnummer: 0211/234567" << Qt::endl; out << Qt::endl; out << "Name: Max Robinson" << Qt::endl; out << "Geburtdatum: 20.11.2006" << Qt::endl; out << "Klasse: 8a" << Qt::endl; out << "Rufnummer: 0211/345678" << Qt::endl; out << Qt::endl; }
Es kann gut sein, dass dein Lösungsansatz etwas anders aussieht. Solange es funktioniert, ist das völlig OK. In der Programmierung gibt es immer mehrere Möglichkeiten, eine Aufgabe zu erledigen. Es kann nie schaden, unterschiedliche Lösungsansätze mit anderen Programmierern zu diskutieren.
Wenn du das so für die ganze Schule fortsetzt, wirst du dir einen Wolf tippen. Besonders fragwürdig wäre dabei die wiederholte Eingabe der Beschriftungen. Kann dein Computer das nicht automatisieren? Sicher doch, du musst es nur programmieren.
Damit kommen wir zum Konzept der Funktionen. Eine Funktion ist ein Stück wiederverwendbarer Programmcode. Ich zeige Dir ein Beispiel:
#include <QTextStream> static QTextStream out(stdout); // Ausgabefunktion: void ausgeben(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer) { out << "Name: " << name << Qt::endl; out << "Geburtdatum: " << geburtsdatum << Qt::endl; out << "Klasse: " << schulklasse << Qt::endl; out << "Rufnummer: " << rufnummer << Qt::endl; out << Qt::endl; } int main() { ausgeben("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); ausgeben("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); ausgeben("Max Robinson", "20.11.2006", "8a", "0211/345678" ); }
Das kann man schon viel besser für die ganze Schule erweitern. Die Funktion ausgeben() erspart Dir eine Menge Tipparbeit.
Die erste Zeile der Funktion definiert eine Liste von Parametern, welche von außen an die Funktion übergeben werden und dann innerhalb der Funktion benutzt werden. Jeder Parameter hat einen bestimmten Typ (in diesem Fall QString) und einen Namen, den du dir selbst ausdenken kannst. In der main() Funktion wird ausgeben() drei mal aufgerufen, jedoch jedes mal mit unterschiedlichen Werten für die Parameter.
Ich habe mir hier die Freiheit genommen, den Quelltext durch Einrückungen mit Leerzeichen übersichtlich zu gestalten. Für den C++ Compiler haben diese zusätzlichen Leerzeichen keine Bedeutung. Theoretisch könnte man sogar das ganze Programm in eine einzige Zeile schreiben, aber davon kann ich nur dringend abraten. Quelltexte sollen so weit wie möglich übersichtlich gestaltet werden, damit man sie gut lesen kann.
Links vom Funktionsnamen ausgeben() steht das komische Wort "void". Es bedeutet, dass diese Funktion keinen Rückgabewert hat. Weiter unten werden wir Funktionen mit Rückgabewert schreiben.
Beachte, dass ich das Objekt "out" weiter nach oben verschoben habe. Wenn "out" immer noch innerhalb von main() stehen würde, könnte man es nur innerhalb der main() Funktion nutzen. Jetzt befindet es sich aber außerhalb der main() Funktion und ist dadurch für beide Funktionen erreichbar.
Das Schlüsselwort "static" sagt dem C++ Compiler, dass dieses Objekt nur in dieser einen Quelltext-Datei erreichbar sein soll. Größere Programme bestehen aus vielen Dateien, da kann diese Beschränkung hilfreich sein, um Konflikte mit gleich benannten Objekten zu vermeiden. Dementsprechend zeigt die IDE einen Hinweis an, wenn du das Wort "static" weg lässt. Probiere es aus.
C++ ist eine objektorientierte Programmiersprache. Objekte kombinieren Funktionen und die dazugehörigen Daten zu einer Einheit. Dieses Konzept ist sehr hilfreich, um große Programme übersichtlich zu gestalten.
Das Foto zeigt drei Schüler in einer Bibliothek. In der Informatik würde man sagen: Wir haben drei Objekte von der Klasse "Schüler". Außerdem sehen wir im Hintergrund viele Objekte von der Klasse "Buch".
Die folgende C++ Klasse beschreibt die für unser Programm relevanten Eigenschaften eines Schülers. Darunter werden drei Schüler-Objekte mit individuellen Eigenschaften angelegt:
#include <QTextStream> static QTextStream out(stdout); class Schueler { public: // Eigenschaften: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; // Funktion: void ausgeben() { out << "Name: " << name << Qt::endl; out << "Geburtdatum: " << geburtsdatum << Qt::endl; out << "Klasse: " << schulklasse << Qt::endl; out << "Rufnummer: " << rufnummer << Qt::endl; out << Qt::endl; } }; int main() { Schueler a; a.name="Lisa Lob"; a.geburtsdatum="01.05.2007"; a.schulklasse="8a"; a.rufnummer="0211/1234567"; Schueler b; b.name="Hanna Kornblut"; b.geburtsdatum="12.08.2007"; b.schulklasse="8b"; b.rufnummer="0211/234567"; Schueler c; c.name="Max Robinson"; c.geburtsdatum="20.11.2006"; c.schulklasse="8a"; c.rufnummer="0211/345678"; a.ausgeben(); b.ausgeben(); c.ausgeben(); }
Probiere das Programm aus. Die Ausgabe auf dem Bildschirm sieht genau so aus, wie vorher.
Die C++ Klasse "Schueler" legt fest, welche Eigenschaften und Funktionen ein Schüler haben kann:
class Schueler { public: // Eigenschaften: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; // Funktion: void ausgeben() { ... } };
jedoch ohne konkrete Werte festzulegen.
Das Schlüsselwort "public" bestimmt, dass die Sachen darunter außerhalb der C++ Klasse sichtbar sein sollen.
Von dieser Klasse werden mehrere Objekte erstellt, die konkrete individuelle Eingenschafts-Werte erhalten:
int main() { Schueler a; a.name="Lisa Lob"; a.geburtsdatum="01.05.2007"; a.schulklasse="8a"; a.rufnummer="0211/1234567"; Schueler b; b.name="Hanna Kornblut"; b.geburtsdatum="12.08.2007"; b.schulklasse="8b"; b.rufnummer="0211/234567"; Schueler c; c.name="Max Robinson"; c.geburtsdatum="20.11.2006"; c.schulklasse="8a"; c.rufnummer="0211/345678"; ... }
Die drei Schueler Objekte geben sich selbst auf dem Bildschirm aus, wenn ihre ausgeben() Methode aufgerufen wird:
int main() { ... a.ausgeben(); b.ausgeben(); c.ausgeben(); }
Klassen definieren die Eigenschaften von Objekten ohne konkrete Werte. Danach erstellt man Objekte von der Klasse, die individuelle Eigenschafts-Werte haben. Danach kann man die Funktionen der Objekte benutzen, um mit den Werten etwas anzustellen.
Das ist das Grundprinzip der objektorientierten Programmierung.
Die Funktionen von Klassen heissen in der Fachsprache "Methoden".
Jetzt ist die main() Funktion allerdings deutlich länger geworden, als zuvor. Muss das so sein? Natürlich nicht. Man kann die Schüler-Objekte auch mit kompakten Einzeilern erstellen. Dazu muss man zu der C++ Klasse einen Konstruktor hinzufügen. Konstruktoren erzeugen Objekte und legen ihre anfänglichen Eigenschaften fest.
Das geht so:
#include <QTextStream> static QTextStream out(stdout); // speichert die Daten eines Schülers class Schueler { public: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; Schueler(QString n, QString g, QString k, QString r) { name=n; geburtsdatum=g; schulklasse=k; rufnummer=r; } void ausgeben() { out << "Name: " << name << Qt::endl; out << "Geburtdatum: " << geburtsdatum << Qt::endl; out << "Klasse: " << schulklasse << Qt::endl; out << "Rufnummer: " << rufnummer << Qt::endl; out << Qt::endl; } }; int main() { Schueler a("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); Schueler b("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); Schueler c("Max Robinson", "20.11.2006", "8a", "0211/345678" ); a.ausgeben(); b.ausgeben(); c.ausgeben(); }
Die main() Funktion kann nun den Konstruktor der C++ Klasse benutzen, um Schüler-Objekte zu erzeugen und zugleich mit konkreten Daten zu initialisieren.
Konstruktoren sehen so ähnlich aus, wie Methoden. Sie müssen aber genau so heißen, wie die C++ Klasse und sie haben keinen Rückgabewert - nicht einmal void.
Die Ausgabe hat sich nicht verändert:
Wie gefallen Dir die Namen der Parameter (n, g, k und r) des Konstruktors?:
Schueler(QString n, QString g, QString k, QString r) { name=n; geburtsdatum=g; schulklasse=k; rufnummer=r; }
Sie sind nicht aussagekräftig. Das sollten wir ganz schnell verbessern. Du kannst sie aber nicht einfach komplett ausschreiben, weil das sonst Ausdrücke wie "name=name" ergeben würde, was dem C++ Compiler nicht eindeutig genug wäre. Welcher Name ist dann das Ziel und welcher die Quelle? Man weiß es nicht.
Das Schlüsselwort "this" löst dieses Problem:
#include <QTextStream> static QTextStream out(stdout); // speichert die Daten eines Schülers class Schueler { public: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; Schueler(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer) { this->name=name; this->geburtsdatum=geburtsdatum; this->schulklasse=schulklasse; this->rufnummer=rufnummer; } void ausgeben() { out << "Name: " << name << Qt::endl; out << "Geburtdatum: " << geburtsdatum << Qt::endl; out << "Klasse: " << schulklasse << Qt::endl; out << "Rufnummer: " << rufnummer << Qt::endl; out << Qt::endl; } }; int main() { Schueler a("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); Schueler b("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); Schueler c("Max Robinson", "20.11.2006", "8a", "0211/345678" ); a.ausgeben(); b.ausgeben(); c.ausgeben(); }
Die C++ Klasse wäre etwas flexibler, wenn sie ihre Ausgaben nicht zwangsweise auf den Bildschirm schreiben würde. Was ist, wenn du die Ausgabe zum Beispiel in eine Text-Datei schreiben willst? Zu diesem Zweck ergänzen wir die Methode ausgeben() um einen Parameter, der bestimmt, wohin die Ausgabe geschrieben werden soll.
Später werden wir das ausnutzen, um den Text woanders hin auszugeben - nämlich in eine Datei.
#include <QTextStream> static QTextStream out(stdout); // speichert die Daten eines Schülers class Schueler { public: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; Schueler(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer) { this->name=name; this->geburtsdatum=geburtsdatum; this->schulklasse=schulklasse; this->rufnummer=rufnummer; } void ausgeben(QTextStream& wohin) { wohin << "Name: " << name << Qt::endl; wohin << "Geburtdatum: " << geburtsdatum << Qt::endl; wohin << "Klasse: " << schulklasse << Qt::endl; wohin << "Rufnummer: " << rufnummer << Qt::endl; wohin << Qt::endl; } }; int main() { Schueler a("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); Schueler b("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); Schueler c("Max Robinson", "20.11.2006", "8a", "0211/345678" ); a.ausgeben(out); b.ausgeben(out); c.ausgeben(out); }
Die Methode ausgeben() hat nun einen neuen Parameter, durch den ihr Aufrufer bestimmen kann, wohin die Ausgabe geschrieben werden soll.
Die nächste Aufgabe lautet: Schreibe das Programm so um, dass es die Schüler nicht nur auf auf dem Bildschirm auflistet, sondern auch in eine Textdatei schreibt. Das ist einfach, du musst nur ein paar Zeilen in die Datei main.cpp einfügen:
#include <QTextStream> #include <QFile> static QTextStream out(stdout); // speichert die Daten eines Schülers class Schueler { public: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; Schueler(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer) { this->name=name; this->geburtsdatum=geburtsdatum; this->schulklasse=schulklasse; this->rufnummer=rufnummer; } void ausgeben(QTextStream& wohin) { wohin << "Name: " << name << Qt::endl; wohin << "Geburtdatum: " << geburtsdatum << Qt::endl; wohin << "Klasse: " << schulklasse << Qt::endl; wohin << "Rufnummer: " << rufnummer << Qt::endl; wohin << Qt::endl; } }; int main() { Schueler a("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); Schueler b("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); Schueler c("Max Robinson", "20.11.2006", "8a", "0211/345678" ); a.ausgeben(out); b.ausgeben(out); c.ausgeben(out); QFile datei("test.txt"); if ( datei.open(QIODevice::WriteOnly) ) { QTextStream strom(&datei); a.ausgeben(strom); b.ausgeben(strom); c.ausgeben(strom); datei.close(); } }
Ganz oben kommt eine neue #include Zeile dazu, weil wir die Klasse QFile benutzen. Diese Klasse stellt Methoden zum Zugriff auf Dateien bereit.
Weiter unten erstellen wir ein Objekt von dieser Klasse, welches mit dem Dateinamen "test.txt" initialisiert wird. Dann wird die Datei zum schreiben (WriteOnly) geöffnet.
Wenn (if) das geklappt hat, erzeugen wir eine zweites Objekt von der Klasse QTextStream mit dem Namen "strom" und geben alle drei Schüler damit aus. Mit der if-Anweisung beschäftigen wir uns gleich noch mehr.
Zum Schluss wird die Datei mit close() geschlossen. Spätestens danach kannst du sie mit einem Text-Editor anschauen.
Mache das mal. Starte das Programm und suche dann die Ausgabe-Datei text.txt im build Verzeichnis. Es befindet sich dort, wo auch dein Projektverzeichnis liegt. Der Name des Verzeichnisses ist ziemlich lang und fängt mit "build" an:
Öffne die Datei test.txt, um sie zu kontrollieren:
Sieht gut aus, oder?
In dem build Verzeichnis befinden sich noch weitere Dateien:
Kommen wir zur Erklärung der if-Anweisung. Man benutzt sie, um einen einzelnen Befehl oder einen Block von Befehlen nur dann auszuführen, wenn eine bestimmte Bedingung zutrifft. Ein paar Beispiele:
#include <QTextStream> static QTextStream out(stdout); int main() { if (4+5 == 9) { out << "4 plus 5 ist 9" << Qt::endl; } if (7 > 5) { out << "7 ist groesser als 5" << Qt::endl; } if (3 == 4) { out << "3 ist gleich 4" << Qt::endl; // nein! } if (3 != 4) { out << "3 ist ungleich 4" << Qt::endl; } if (1) { out << "1 ist wahr" << Qt::endl; } if (0) { out << "0 ist wahr" << Qt::endl; // nein! } }
Die Ausgabe ist:
Wie du siehst, wurden die beiden falschen Aussagen übersprungen. Genau dazu dient die if-Anweisung.
Beachte, dass man zum Vergleichen von Zahlen das doppelte "==" Zeichen braucht. Das einfache "=" ist nämlich dazu reserviert, einer Variable etwas zuzuweisen, wie wir das oben schon ein paar mal gemacht haben.
Beachte auch, dass man anstelle eines Vergleichs-Ausdruckes auch immer einfach nur eine simple Zahl benutzen kann. Wobei 0 immer wie "unwahr" behandelt wird und alle anderen Zahlen "wahr" sind.
Jetzt gucken wir mal in die Dokumentation, was eigentlich QFile::open() zurück liefert:
Ach so, bei Erfolg liefert die Methode true (wahr) zurück. Dann ist unser if-Ausdruck in main.cpp ja richtig:
if ( datei.open(QIODevice::WriteOnly) ) { QTextStream strom(&datei); a.ausgeben(strom); b.ausgeben(strom); c.ausgeben(strom); datei.close(); }
Das heißt nämlich: Wenn das Öffnen der Datei erfolgreich war, dann gebe etwas aus und schließe sie wieder. Ansonsten eben nicht.
Du könntest auch Befehlen, das der Computer ansonsten etwas anderes tun soll:
QFile datei(""); if ( datei.open(QIODevice::WriteOnly) ) { QTextStream strom(&datei); a.ausgeben(strom); b.ausgeben(strom); c.ausgeben(strom); datei.close(); } else { out << "Die Datei kann nicht geöffnet werden" << Qt::endl; }
Der Fehlerfall wird dieses mal absichtlich provoziert, indem wir einen leeren Dateinamen benutzen:
Beachte, das "&" Zeichen an der markierten Stelle:
void ausgeben(QTextStream& wohin) { wohin << "Name: " << name << Qt::endl; wohin << "Geburtdatum: " << geburtsdatum << Qt::endl; wohin << "Klasse: " << schulklasse << Qt::endl; wohin << "Rufnummer: " << rufnummer << Qt::endl; wohin << Qt::endl; }
Das ist neu.
Ohne "&" Zeichen würde eine Kopie des QTextStream an die Methode übergeben werden. Du könntest dann die Kopie innerhalb der Methode verändern, ohne das Original zu betreffen.
Mit "&" Zeichen wird hingegen eine Referenz (=Verweis) auf das bereits existierende Objekt übergeben. Die Referenz belegt keinen zusätzlichen Speicher.
Es ist egal, ob das Leezeichen vor oder hinter das "&" Zeichen geschrieben wird.
Beim QTextStream macht eine Kopie keinen Sinn, denn Kanäle wie "stdout" können nicht mehrfach benutzt werden. Es darf nur ein einziges Objekt geben, dass diesen "stdout" Kanal benutzt. Damit hast du nun auch die Begründung, warum hier eine Referenz auf das bereits bestehende Objekt übergeben werden muss.
Versuche den gleichen Quelltext mal ohne "&" zu compilieren, und schaue Dir die Fehlermeldungen an, die dadurch entstehen:
Das ist ein perfektes Beispiel für eine ganz fiese schwer verständliche Fehlermeldung 🙁
Zunächst solltest du wissen, dass der Compiler trotz Fehler immer versucht, irgendwie weiter zu arbeiten. Deswegen ist die erste Fehlermeldung die wichtigste, alle weiteren könnten Folgefehler sein. Konzentriere dich bei solchen Ketten von Fehlermeldungen immer auf die erste.
Des Weiteren besteht eine sehr große Chance, Hilfe im Internet zu finden, weil irgend jemand das gleiche Problem in einem Diskussionsforum thematisiert hat. Suche also mit Google nach dem Text der Fehlermeldung. Wenn Google nichts findet, dann lasse alle Namen weg. In diesem Fall, suche nach "call to deleted constructor of 'QTextStream'" oder nach "call to deleted constructor", falls der erste Versuch nichts bringt.
Ich habe das am 26.2.2020 mal mit Google ausprobiert: das erste Ergebnis liefert eine passende Erklärung.
Die Programmiersprache C++ erzeugt automatisch für jedes Objekt einen sogenannten Kopier-Konstruktor, der unter der Haube benutzt wird, wenn das Objekt kopiert wird. Also genau dein Fall, weil das "&" Zeichen fehlt. Nun bietet das Qt Framework aber auch die Möglichkeit, solche Kopier-Konstruktoren mit dem Makro Q_DISABLE_COPY zu löschen.
Das wird immer dann gemacht, wenn Kopien von dem Objekt nicht sinnvoll funktionieren würden, was auf QTextStream zutrifft. Darauf bezieht sich der Text "call to deleted constructor".
Das Programm fängt langsam an, groß zu werden. Nun ist ein guter Zeitpunkt gekommen, es auf mehrere Dateien aufzuteilen. Klicke dazu mit der rechten Maustaste auf "Quelldateien" und wähle dann den Befehl "Hinzufügen...".
Wähle im folgenden Dialog die Vorlage "C++ Klasse" aus. Gebe als Klassenname "Schueler" ein und klicke dann auf "weiter" und dann auf "abschließen". Deine IDE erstellt dann zwei neue Dateien (schueler.h und schueler.cpp), die links in der Projekt-Struktur erscheinen.
Eine C++ Klasse, aber zwei Dateien, warum das?
In der Programmiersprache C++ kann man man Bibliotheken ohne ihren Quelltext veröffentlichen. In Windows sind das die zahlreichen .dll Dateien, unter Linux haben sie die Endung .so.
Damit Programmierer diese Bibliotheken richtig benutzen können, brauchen sie eine eindeutige Auflistung der darin befindlichen Klasse(n) samt ihrer Attribute. Der Compiler braucht diese Information ebenfalls. Die Header-Datei mit der Endung .h ist für diese Beschreibung vorgesehen.
Der Programmcode von den Methoden befindet sich dann entweder als numerischer Maschinencode in der Bibliothek, oder als Quelltext in den .cpp Dateien. Diese Aufteilung auf zwei Dateien wird üblicherweise auch bei selbst geschriebenen C++ Klassen angewendet.
Befülle also nun die Datei schueler.cpp mit den Methoden der C++ Klasse. Der Konstruktor gehört auch dazu:
#include "schueler.h" Schueler::Schueler(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer) { this->name=name; this->geburtsdatum=geburtsdatum; this->schulklasse=schulklasse; this->rufnummer=rufnummer; } void Schueler::ausgeben(QTextStream& wohin) { wohin << "Name: " << name << Qt::endl; wohin << "Geburtdatum: " << geburtsdatum << Qt::endl; wohin << "Klasse: " << schulklasse << Qt::endl; wohin << "Rufnummer: " << rufnummer << Qt::endl; wohin << Qt::endl; }
Da so eine .cpp Datei theoretisch mehrere C++ Klassen enthalten kann (macht man aber selten), verlangt der Compiler, dass man den Namen der jeweiligen Klasse vor die Methoden schreibt. Ich habe das hier markiert.
Die Entwicklungsumgebung zeigt momentan ganz viele rote Hinweise an, weil die zugehörige Header-Datei schueler.h noch leer ist:
Als nächstes widmen wir uns daher der Header-Datei, kopiere folgenden Text dort hinein:
#ifndef SCHUELER_H #define SCHUELER_H #include <QTextStream> // speichert die Daten eines Schülers class Schueler { public: QString name; QString geburtsdatum; QString schulklasse; QString rufnummer; Schueler(QString name, QString geburtsdatum, QString schulklasse, QString rufnummer); void ausgeben(QTextStream& wohin); }; #endif // SCHUELER_H
Und schwupps - verschwinden die roten Meldungen in der anderen .cpp Datei.
Wir schauen uns den Inhalt der beiden Dateien gleich noch genauer an. Lass uns erst einmal das Hauptprogramm main.cpp so umschreiben, dass es diese beiden neuen Dateien benutzt.
#include <TextStream> #include "schueler.h" static QTextStream out(stdout); int main() { Schueler a("Lisa Lob", "01.05.2007", "8a", "0211/1234567"); Schueler b("Hanna Kornblut", "12.08.2007", "8b", "0211/234567" ); Schueler c("Max Robinson", "20.11.2006", "8a", "0211/345678" ); a.ausgeben(out); b.ausgeben(out); c.ausgeben(out); }
Fertig. So ist das ganze jetzt wieder ausführbar. Teste das Programm. Die Ausgabe ist wie gehabt immer noch:
Schau Dir die markierte Änderung in main.cpp an. Wir haben den ganzen Quelltext der C++ Klasse "Schueler" aus dem Hauptprogramm entfernt und durch die Anweisung
#include "schueler.h"
ersetzt. An dieser Stelle fügt der Compiler gedanklich den Inhalt der genannten Header-Datei ein. In dieser Header-Datei ist die C++ Klasse mit ihren Methoden und Attributen beschrieben, jedoch ohne den konkreten Quelltext der Methoden. Der wiederum befindet sich in der dazugehörigen .cpp Datei.
Bei der #include Anweisung ist der Dateiname dieses mal nicht in spitzen Klammern ("<>") eingeschlossen worden, sondern in doppelte Anführungszeichen. Dadurch weiß der Compiler, dass er die Datei in deinem Projektverzeichnis suchen muss. Dateien in spitzen Klammern sucht er hingegen im Installationsverzeichnis von Qt.
Die Header-Dateien von Qt Klassen heißen immer genau so wie die darin befindlichen Klassen, und zwar ohne Endung. Deine eigenen Header-Dateien haben hingegen klein geschriebene Dateinamen mit der Endung .h.
Die Header Datei schueler.h enthält ein paar interessante Zeilen, die von der IDE vorgegeben wurden:
#ifndef SCHUELER_H #define SCHUELER_H ... #endif // SCHUELER_H
Dieses Konstrukt verhindert Fehlermeldungen für den Fall, dass die Datei schueler.h mehrfach inkludiert (eingebunden) wird. Das sind sogenannte Präprozessor Makros. Ich empfehle Dir, das zu diesem Zeitpunkt einfach mal so hinzunehmen. Später kannst du dir mal den verlinkten Wikipedia Artikel durchlesen, um das Thema zu vertiefen.
Beachte, dass in der Datei schueler.h die Datei QTextStream inkludiert wird.
Wenn schon QTextStream inkludiert wird, weil die C++ Klasse "QTextStream" benutzt wird, müsste man dann nicht auch QString inkludieren, weil die Klasse "QString" verwendet wird?
Ja, kann man machen. Du darfst das Inkludieren von QString aber auch weg lassen, weil sich genau so eine #include Anweisung bereits in der Header-Datei von QTextStream befindet. Schau es dir an! Klicke mit gedrückter Strg-Taste auf den Dateinamen:
Hoppla, da steht aber wenig drin! Lustigerweise wird hier wiederum eine Header-Datei inkludiert. Klicke auch darauf mit gedrückter Strg-Taste, um hinein zu schauen:
Aha, da wird also die Datei Header-Datei qstring.h inkludiert. Das ist der Grund, warum du in deinem Programm die Zeile "#include <QString>" weg lassen darfst. Klicke oben auf das kleine "x" um die Dateien wieder zu schließen.
Ich glaube, wir sollten uns endlich mal mit Umlauten beschäftigen. Du hast bestimmt bemerkt, dass ich Umlaute bisher konsequent vermieden habe. Das ändert sich ab jetzt.
Vor vielen Jahren begann die schrittweise Umstellung unserer Computersysteme auf Unicode, um die Schriftzeichen aller Sprachen weltweit darstellen zu können. Diese Umstellung ist praktisch abgeschlossen, aber der C++ Compiler erwartet immer noch, dass einfache Zeichenketten in 8-Bit kodiert sind, um alte Programme nicht kaputt zu machen.
Du kannst allerdings die fragliche Zeile so umschreiben, damit der ganze Unicode Zeichensatz funktioniert:
out << QStringLiteral("Die Datei kann nicht geöffnet werden") << Qt::endl;
QStringLiteral() akzeptiert Quelltext im Unicode Format. Das solltest du dir gut merken, denn es löst das Problem mit den Umlauten - in diesem Fall mit dem "ö".
Wenn da nicht die lästigen Altlasten von Windows wären ...
Obwohl auch Windows schon lange auf Unicode umgestellt ist, benutzt die Konsole weiterhin einen alten 8-Bit Zeichensatz, und zwar die Codepage CP850. Qt kann automatisch von Unicode auf diese Codepage konvertieren, allerdings musst du das manuell angeben:
out.setCodec("CP850"); out << QStringLiteral("Die Datei kann nicht geöffnet werden") << Qt::endl;
Jetzt enthält dein Quelltext UTF-8 und Qt konvertiert das zu CP850, damit die Umlaute in der Konsole richtig erscheinen.
Wenn du die folgenden Zeilen hinzufügst, dann wird setCode() bei allen anderen Betriebssystemen automatisch weg gelassen:
#ifdef Q_OS_WIN out.setCodec("CP850"); #endif out << QStringLiteral("Die Datei kann nicht geöffnet werden") << Qt::endl;
Dieser Quelltext funktioniert nun sowohl unter Linux als auch unter Windows richtig. Bei Windows wird der Codec gesetzt, bei Linux nicht.
Die nächste Aufgabe besteht darin, die Adressen aus einer separaten Textdatei zu laden anstatt sie direkt in den Quelltext zu schreiben. Klicke mit der rechten Maustaste auf den Projektnamen, dann auf "Hinzufügen...":
Wähle im nächsten Dialog die Vorlage Allgemein/Leere Datei aus. Die Datei soll den Namen schueler.csv bekommen. Der Inhalt soll sein:
Lisa Lob, 01.05.2007, 8a, 0211/1234567 Hanna Kornblut, 12.08.2007, 8b, 0211/2345678 Max Robinson, 20.11.2006, 8a, 0211/3456789 Natalia Safran, 03.04.2007, 8a, 0211/4567890 Murat Amir, 24.06.2007, 8c, 0211/5678901 Sasha Nimrod, 03.02.2007, 8c, 0211/6789012 Melina Grohe, 11.07.2007, 8a, 0211/7890123 Robert Schmitz, 14.12.2006, 8b, 0211/8901234 Thomas Hörner, 04.01.2007, 8a, 0211/9012345
Das folgende Programm öffnet diese Datei mit Hilfe der QFile Klasse, und liest sie dann mit Hilfe der QTextStream Klasse zeilenweise ein. Lass uns erst einmal damit anfangen:
#include <QFile> #include <QTextStream> #include <QTextCodec> int main() { QTextStream out(stdout); #ifdef Q_OS_WIN out.setCodec("CP850"); #endif QFile datei("../test/schueler.csv"); if (datei.open(QIODevice::ReadOnly)) { QTextStream strom(&datei); strom.setCodec("UTF-8"); // weil die Datei ein "ö" in UTF-8 enthält QString zeile=strom.readLine(); out << zeile << Qt::endl; datei.close(); } }
Alle verwendeten Klassen und Anweisungen kennst du bereits. Der Dateiname ist relativ zum build
Verzeichnis, wo dein ausführbares Programm liegt. Wenn du möchtest kannst du auch den vollständigen Pfadnamen angeben, zum Beispiel:
C:/Users/Stefan/Documents/Programmierung
Im Fall von Windows gibt es hier zwei Besonderheiten zu beachten: Erstens haben einige Ordner (Users
und Documents) englische Namen, aber der Dateimanager (Explorer) zeigt sie auf deutsch an. Zweitens
musst du als Trennzeichen entweder die gezeigten Schrägstriche "/" (slashes) verwenden, oder
doppelte "\\" (backslashes). Denn einfache Backslashes leiten Escape
Sequenzen ein.
Die Ausgabe sieht so aus:
Leider liest das Programm nur eine Zeile aus der Datei. Du brauchst eine Wiederhol-Schleife, die einen
Teil des Programms so oft wiederholt, bis die ganze .csv Datei eingelesen wurde.
Die while() Anweisung sorgt dafür, dass folgende Block wiederholt ausgeführt wird, und zwar solange die
angegebene Bedingung wahr ist. Die Methode atEnd() von der QTextStream Klasse meldet, ob das Ende des
Datenstromes erreicht wurde. Dieser Ausdruck bedeutet daher so viel wie "wiederhole, solange das Ende
des Datenstromes nicht erreicht wurde".
Eine andere Variante mit exakt dem geichen Ergebnis wäre die Negation:
Dieser Ausdruck bedeutet so viel wie "wiederhole, solange nicht das Ende des Datenstromes erreicht
wurde". Also eigentlich das Gleiche, nur anders geschrieben.
Jetzt wollen wir diese Zeilen in ihre Bestandteile zerlegen, um mit den einzelnen Werten viele Objekte
der C++ Klasse "Schueler" zu befüllen. Diesen Vorgang nennt man "parsen" - wir parsen die Datei.
Hier benutzen wir die Methode split() von der
QString Klasse, um die Zeile in ihre Bestandteile zu zerlegen. Die split() Methode liefert eine Liste
von Zeichenketten, in Form der Klasse QStringList.
Zur Kontrolle gibt das Programm die Teile aus. Beachte, wie wir auf die einzelnen Elemente (Teile) der
Liste zugreifen: Indem wir in eckigen Klammern angeben, welchen Teil wird haben wollen. Wobei [0] das
erste Element liefert.
Die Ausgabe sieht so aus:
Auffällig ist hier, dass da noch ein paar Leerzeichen zu viel sind. Die kommen aus der .csv Datei. Als
Nächstes schneiden wir also noch die Leerzeichen weg, und dann können wir die Objekte der C++ Klasse
"Schueler" befüllen.
Der Ausdruck teile[n] liefert einen Teil der Zeile als QString. Darauf wenden wir die Methode QString::trimmed() an, um Leerzeichen (vor
und hinter dem Text) abzuschneiden. Damit initialisieren wir ein Objekt der C++ Klasse "Schueler", deren
Methode Schueler::ausgeben() für eine schöne Anzeige auf dem Bildschirm sorgt.
Als wir weiter oben die Schüler-Daten direkt im Quelltext hatten, da hatte jeder Schüler eine eigene
Objekt-Instanz der C++ Klasse "Schueler". Wir hatten ihnen die Namen a, b, und c gegeben. Wir hatten
exakt so viele Variablen, wie Schüler. Wie würdest du die Variablen nennen wollen, wenn du 2000 Schüler
in der .csv Datei hättest?
Eine fortlaufende Nummerierung ist hier wohl naheliegend, vielleicht so ähnlich, wie das oben schon mit
den Teilen der Zeilen gemacht wurde. Das geht so:
Die Ausgabe sieht so aus:
Als wir die Zeilen aus der .csv Datei in Teile zerlegten, lieferte uns die Methode QString::split() ein
QStringList Objekt zurück, also eine Liste von QStrings.
Um viele Schüler im Speicher abzulegen, brauchst du eine Liste von Schülern. Praktischerweise enthält
das Qt Framework eine universelle Listen-Klasse, die beliebige Objekte aufnehmen kann.
QList ist eine Template-Klasse. Bei Templates schreibt
man in spitze Klammern, welche Klasse sie aufnehmen sollen. Wir haben jetzt also eine Liste von
Schülern, die wir durch wiederholte Aufrufe ihrer append() Methode befüllen.
Zur Ausgabe benutzen wir die for-Schleife.
For-Schleifen wiederholen einen Block, während sie die Durchläufe zählen:
Die Syntax (Schreibweise) der for-Schleife sieht kompliziert aus, aber man gewöhnt sich dran. Was
passiert hier?
Die Liste nummeriert ihre 9 Elemente beginnend mit der 0 durch, also: [0], [1], [2], [3], [4], [5], [6],
[7] und [8]. Deswegen soll die For-Schleife mit Zahlen von 0 bis 8 wiederholt werden.
Der Teil "int i=0" erzeugt eine Variable vom Typ int, die mit 0 initialisiert wird. Das ist der
Startwert für den ersten Schleifen-Durchlauf.
Der Teil "i<liste.size()" sagt aus, wie lange die Schleife ausgeführt werden soll. In diesem Fall
soll sie so lange ausgeführt werde, wie i kleiner als die Größe der Liste ist. Wenn i die 9 erreicht,
stopp die Wiederholschleife, denn der Ausdruck ist dann nicht mehr wahr.
Der Teil "i++" legt fest, in welcher Schrittweite i erhöht wird. i++ ist eine häufig benutzte Abkürzung
für "i=i+1".
Um den Programmablauf besser nachzuvollziehen, kannst du den Debugger von deiner Entwicklungsumgebung benutzen.
Mit einem Debugger kannst du das Programm Zeile für Zeile ausführen und dabei den Inhalt aller Objekte
anschauen. Der Debugger gibt dir Einblick in die inneren Abläufe deines Programms. Die Anleitung dazu
ist dort.
Ich zeige dir jetzt, wie man den Debugger benutzt. Zuerst musst du sicher stellen, dass unten Links der
Debug-Modus eingestellt ist:
Im Debug Modus fügt der Compiler Informationen in das Programm ein, die der Debugger benötigt, um zu
funktionieren. Danach kannst du oben auf den grauen Käfer klicken, um die Debug-Ansicht zu öffnen.
Jetzt musst du dem Debugger sagen, wo er das Programm pausieren soll. Klicke dazu links neben die
Zeilennummer von der Zeile "Liste die Schüler auf...". Dann erscheint dort ein roter Haltepunkt.
Starte jetzt das Programm, indem du ganz unten links auf den grünen Pfeil mit dem Käfer klickst (über
dem Hammer). Es wird bis zum Haltepunkt laufen und dann pausieren. Die Ausgabe im Terminalfenster
bestätigt das:
Die Debugger-Ansicht sieht nun ungefähr so aus:
Vor der Zeile 37 befindet sich ein gelber Pfeil, der die aktuelle Programmzeile anzeigt. Die Zeilen
darüber sind bereits abgearbeitet worden. Im rechten Bereich wird der Inhalt der gerade bekannten
Variablen angezeigt. Hier kannst du sehen, dass die Liste der Schüler 9 Elemente hat. Das erste Element
habe ich aufgeklappt, damit du sehen kannst, welche Daten dort gespeichert sind.
Im unteren Bereich ist eine sehr schmale Leiste mit wichtigen Schaltflächen:
Hier kannst du den weiteren Ablauf des Programms manuell steuern. Wenn du mit der Maus auf die Symbole
zeigst, erscheint eine hilfreiche Beschriftung. So findest du die Knöpfe für Einzelschritte:
Klicke jetzt langsam mehrmals auf die "Einzelschritt über" Schaltfläche, bis du bei
"liste[i].ausgeben(out)" angekommen bist.
Schaue Dir nochmal die Ausgabe im Terminalfenster an, es ist eine neue Zeile hinzu gekommen: "Liste die
Schüler auf...". Das ist logisch, du hast die entsprechende Zeile vom Programm ja gerade ausgeführt.
Inzwischen befindest du dich innerhalb der for-Schleife. Der Debugger zeigt Dir ganz rechts oben an,
dass eine neue Variable erreichbar geworden ist, nämlich i. Und i hat gerade den Wert 0.
Setze das Programm jetzt fort, indem du langsam wiederholt auf die "Einzelschritt über" Schaltfläche
klickst. Beobachte, wie sich dabei der gelbe Pfeil und die Anzeige der Variablen rechts oben verändern.
Wenn i den Wert 8 erreicht, und du dann noch einmal klickst, endet das Programm. Klicke weiter auf den
Button, dann wirst du sehen, dass der gelbe Pfeil jetzt ein paar mal wild hin und her springt. Das kommt
daher, daß das Programm jetzt jedem Objekt die Gelegenheit gibt, eine abschließende Aufräum-Methode
auszuführen, den sogenannten Destruktor.
Schließlich landest du in dieser seltsamen Ansicht:
Das ist der Maschinencode, der die Meldung "Betätigen Sie die <RETURN> Taste, um das Fenster zu
schließen..." ausgibt. Hier erscheint der Maschinencode, weil du die Quelltext-Dateien von diesem Teil
des Qt Frameworks nicht installiert hast.
Du kannst das Programm trotzdem durch Anklicken der "Einzelschritt über" Schaltfläche fortsetzen, oder
du klickst auf die erste Schaltfläche mit der Beschriftung "GDB für test fortsetzen", dann läuft das
Programm von alleine bis zum finalen Ende durch.
Beim Debuggen ist Dir vielleicht aufgefallen, dass die Variable i am Anfang noch nicht (rechts oben)
angezeigt wurde. Das kommt daher, dass jede Variable nur in dem Block existiert, in dem sie erstellt
wurde. In diesem Fall wurde die Variable i in der for-Schleife erstellt und existiert deswegen nur
innerhalb dieser Schleife. Wenn das Ende der Schleife erreicht wurde, verschwindet die Variable wieder.
Diese begrenze Sichtbarkeit nennt man Scope.
Konsolen-Programme werden häufig mit Kommandozeilenargumenten gestartet, die das Verhalten des Programms
steuern. So kennst du womöglich den Befehl "rm" zum Löschen einer Datei, der als Parameter den Namen der
zu löschen Datei erwartet. Ich zeige Dir jetzt, wie dein eigenes Programm solche Kommandozeilenargumente
empfangen kann:
Starte das Programm in der Entwicklungsumgebung, dann wirst du folgende Ausgabe erhalten:
Das Betriebssystem befüllt argc immer mit der Anzahl der Kommandozeilenargumente. Die Argumente werden
als argv an das Programm übergeben und beginnend mit der 0 durch nummeriert. Das erste Argument ist
immer das Programm selbst. Danach können weitere Argumente folgen. Wir benutzen hier die bereits
bekannte for-Schleife, um die Argumente im Konsolen-Fenster auszugeben.
Um mehr Argumente auszuprobieren, kannst du das Projekt in der Entwicklungsumgebung konfigurieren:
Wechsle zurück in die "Editieren" Ansicht und starte das Programm erneut.
Ich denke, wir haben jetzt genug mit der Konsole experimentiert. Ich möchte Dir als nächstes zeigen, wie
man bunte Grafik ausgibt.
Dieses Kapitel baut auf den vorherigen auf!
Das Konsolen-Fenster kann nur Text ausgeben. Wir fügen jetzt ein weiteres Fenster basierend auf der
Klasse QWidget hinzu, das grafische Ausgaben
ermöglicht.
Dazu müssen wir zuerst in der Projekt-Datei test.pro eine Zeile ändern, um die Bibliotheken für
grafische Anwendungen und Widgets einzubinden. Ändere "QT -= gui" in:
Drücke Strg-S, um die Änderung zu speichern. Die Entwicklungsumgebung braucht nun ein paar Sekunden, um
die grafischen Bibliotheken einzubinden. Das erste grafische Programm soll so aussehen:
Wenn du das leere grafische Fenster schließt, endet auch das Programm. Das erkennst du an der
entsprechenden Meldung im Konsolen-Fenster:
Die erste wichtige Änderung hier ist der Einsatz von QApplication:
Während Konsolen-Programme einmal von oben nach unten durch laufen, warten grafische Programme auf
Ereignisse (z.B. Mausklicks und Tastendrücke), die sie abarbeiten.
Jedes grafische Programm, das auf dem Qt Framework basiert, muss genau ein QApplication Objekt enthalten. Der Konstruktor
von QApplication initialisiert die Verarbeitung der Ereignisse. Die Methode QApplication::exec() enthält
eine Warteschleife, welche die Ereignisse empfängt und verarbeitet.
Dazwischen können wir Befehle einfügen, die beim Programmstart ausgeführt werden sollen. In diesem Fall
wird ein Objekt von der Klasse QWidget erstellt, das
ist das grafische Programm-Fenster.
Probiere mal aus, zwei Fenster zu starten:
Jetzt hat das Programm tatsächlich insgesamt drei Fenster:
Für die weiteren Experimente mit Grafik brauchen wir das Konsolen-Fenster nicht mehr, deswegen entferne
in der Datei test.pro die "console" aus der "CONFIG" Zeile:
Diese Zeile sieht bei Dir womöglich ein bisschen anders aus, wenn du eine andere Version von QT
verwendest. Lasse die anderen Einträge unverändert, entferne nur die "console". Drücke dann wieder
Strg-S, um die Änderung zu speichern. Die Entwicklungsumgebung braucht ein paar Sekunden, um die
Änderung zu verarbeiten.
Starte das Programm erneut, um den Erfolg zu überprüfen. Die Konsole ist jetzt weg, es öffnen sich nur
noch zwei grafische Fenster.
Beachte den rot markierten Text. Was vorher in die Konsole geschrieben wurde, erscheint jetzt im unteren
Bereich der Entwicklungsumgebung. Das ist sehr praktisch, um Details zur Fehleranalyse auszugeben, die
normalerweise (in den grafischen Fenstern) nicht sichtbar sein sollen.
Tausche den ganzen Quelltext von main.c durch folgenden aus:
Wir haben jetzt nur noch ein Fenster übrig gelassen, und die Ausgabe von QTextStream auf spezielle Funktionen für
Debug-Meldungen umgestellt.
Unter Linux erscheinen diese Meldungen jetzt in rot:
Das liegt daran, dass die Meldung nicht mehr über den Ausgabekanal "stdout" fließen, sondern über den
Kanal "stderr". Den hättest du auch mit QTextStream verwenden können.
Der eigentliche Vorteil dieser Debug-Meldungen besteht allerdings darin, dass sie automatisch um weitere
Informationen ergänzt werden können und dass man sie nach Wichtigkeit filtern kann.
Die Formatierung der Debug Meldungen lässt sich mit mit einer Umgebungsvariable steuern. Die
Dokumentation von Qt schlägt folgende Zeile vor:
Solche Umgebungsvariablen stellt man in der Regel zentral in der Systemsteuerung von Windows ein, bzw.
unter Linux in der Datei /etc/profile. Für den Anfang ist es aber besser, das erst einmal nur in der
Entwicklungsumgebung für dieses eine Projekt zu tun. Ich habe im folgenden Bild dargestellt, wie du dort
hin kommst:
Gebe dort als Name "QT_MESSAGE_PATTERN" ein, und als Wert "[%{type}] %{appname} (%{file}:%{line}) -
%{message}".
Wechsele am linken Rand der Entwicklungsumgebung wieder zurück in die "Editieren" Ansicht und starte
dein Programm erneut. Jetzt sehen die Debug-Meldungen anders aus:
Weil jetzt jede Zeile am Anfang mit der Wichtigkeit gekennzeichnet ist, kannst du danach filtern. Wenn
du in das weiße Filter-Feld zum Beispiel "[critical]" eingibst, siehst du nur noch kritische Meldungen.
Alternativ zur Umgebungsvariable kannst du das Ausgabeformat direkt in deinem Quelltext mit der Funktion
qSetMessagePattern() festlegen.
Schau Dir diesen Artikel mal an, da sind noch mehr Formatier-Anweisungen aufgelistet, die dich
interessieren könnten.
Wir wollen jetzt eine bunte Grafik in das Widget zeichnen.
Die Ablaufsteuerung deines Betriebssystems verlangt, dass Fenster ihren Inhalt auf Kommando selbst
zeichnen, und zwar zu beliebigen Zeitpunkten und beliebig oft. Bei Bedarf sendet das Betriebssystem ein
Signal mit dem Kommando "Zeichne dich neu" an das Fenster. Dann muss das Fenster seinen eigenen Inhalt
zeichnen.
Das heißt: Du kannst in deinem Quelltext nicht einfach aktiv in das Fester zeichnen, sondern musst auf
so ein Signal warten. Das Warten erledigt die Klasse QApplication, es bleibt also nur die Notwendigkeit,
eine Methode zu schreiben, die das Zeichnen erledigt.
Zur objektorientierten Programmierung gehört die Möglichkeit, eigene Klassen von vorhandenen abzuleiten
und zusätzliche Eigenschaften (Attribute) und Methoden hinzuzufügen. Man kann auch vorhandene Methoden
durch eigene ersetzen. Genau das müssen wir tun, denn die vorhandene Methode vom QWidget, die auf das
Kommando "Zeichne dich neu" reagiert, macht einfach gar nichts. Deswegen ist das Fenster zur Zeit noch
völlig leer.
Erstelle eine neue Klasse mit dem Namen MeinWidget:
Benutze die Vorlage für C++ Klassen und stelle dort ein, dass die Basisklasse QWidget sein soll.
Öffne die generierte Datei meinwidget.h und füge darin die Methode paintEvent() ein. Diese Methode wird
aufgerufen, wenn das Fenster das Kommando "Zeichne dich neu" empfängt.
Wir haben hier eine eigene Klasse von der QWidget abgeleitet, welche die Methode paintEvent() durch eine
eigene Variante ersetzt. In dieser Methode benutzen wir ein QPainter, um in das Fenster ein Rechteck hinein zu
zeichnen.
Wie die Sache mit dem Ableiten genau funktioniert und was die Zeilen bedeuten, die deine
Entwicklungsumgebung generiert hat, werde ich weiter unten im Kapitel Klassen und
Objekte detailliert erklären.
Jetzt musst du noch die main.cpp umschreiben, damit das eigene MeinWidget anstelle von QWidget als
Hauptfenster benutzt wird.
Probiere das Programm aus:
Wenn du aufmerksam warst, hast du die Hinweismeldung "Unused parameter 'event'" bemerkt:
Das stimmt sogar, der Parameter event wird innerhalb der Methode paintEvent() tatsächlich nicht benutzt.
Das ist in diesem Fall Ok, denn wir brauchen ihn nicht. Aber wir dürfen den Parameter nicht einfach
entfernen, weil das Qt Framework diese Funktion sonst nicht mehr aufrufen würde.
Was wir aber tun können ist, den Namen des Parameter durch den Kommentar "unused" ersetzen. Dann
verschwindet die Warnung:
Lass uns noch ein paar weitere grafische Elemente zur Methode paintEvenet() hinzufügen:
Jetzt wollen mir mal kontrollieren, wann und wie oft diese paintEvent() Methode eigentlich ausgeführt
wird. Füge dazu eine Zeile in die paintEvent() Methode ein:
Beim Programmstart wird die Methode einmal aufgerufen, was logisch ist:
Wenn du die Größe des Fensters änderst, wird die paintEvent() Methode mehrfach aufgerufen. Probiere es
aus und achte dabei auf die Debug-Meldungen.
Wenn du Lust hast, kannst du noch weitere Figuren in das Fenster zeichnen oder Text hinzufügen. Mache
Dich mit den Funktionen von QPainter vertraut.
Ich zeige Dir jetzt, wie man eine Schaltfläche hinzufügt, die eine Funktion auslöst. Du musst dazu eine
Methode zur Datei meinwidget.h hinzufügen.
Diese Methode soll aufgerufen werden, wenn die Schaltfläche (die wir gleich einfügen) das Signal "ich
wurde angeklickt" aussendet. Weil die Methode das Signal empfängt wird sie "Slot" genannt.
Ändere nun auch die Datei meinwidget.cpp, um dort die Schaltfläche einzufügen und die Methode
angeklickt() zu implementieren:
Wir haben zum Konstruktor von MeinWidget zwei Zeilen hinzugefügt, um einen neuen QPushButton zu erstellen und um diesen mit der
Slot-Methode angeklickt() zu verbinden.
Das Ergebnis sieht schlecht aus:
Das Hauptfenster (Widget) ist jetzt winzig klein geworden. Offenbar hat es sich automatisch an die Größe
des Button angeglichen. Wir wollen aber, daß das Fenster groß bleibt, deswegen fügen wir eine weitere
Zeile in den Konstruktor von MeinWidget ein:
Probiere das Programm aus.
Probiere auch aus, was passiert, wenn der Button angeklickt wird:
Wir können den Button an eine bessere Position verschieben:
Beachte, dass im Konstruktor von MeinWidget das Schlüsselwort "new" benutzt wurde:
Normale Objekte belegen Speicherplatz im Stack. Alles was sich auf dem Stack
befindet, wird am Ende der Methode wieder entfernt. Die Konsequenz wäre, dass der Button nach Ausführung
des Konstruktor nicht mehr existieren würde.
Er soll aber viel länger Existieren, und zwar mindestens so lange, bis dieses Fenster geschlossen wurde.
Genau das wird mit "new" erreicht. Damit erzeugte Objekte werden im Heap gespeichert. Das ist ein anderer
Speicherbereich, der für langlebige Objekte vorgesehen ist. Der Heap umfasst den gesamten freien
Arbeitsspeicher deines Computers. Er wird also von den laufenden Programmen gemeinsam verwendet. Dabei
passt das Betriebssystem auf, dass die Programme sich nicht gegenseitig in die Daten gucken.
Die Variable "button" ist nun ein Zeiger auf den Speicherplatz im Heap. Das Sternchen vor "button"
bedeutet so viel wie "Zeiger auf button". Man kann die Attribute und Methoden des Buttons erreichen,
indem man sie indirekt über den Zeiger dereferenziert. Das bewirkt der Operator "->". Wenn du
"button->move()" schreibst, rufst du die Methode move() von dem Objekt auf, auf das button zeigt.
Weiter unten im Kapitel Zeiger und Referenzen erkläre ich dieses Thema
detaillierter.
Dadurch, daß das Fenster einen Button erhalten hat, ist es zu einem interaktiven Dialog geworden. Qt
Creator enthält ein sehr bequemes Hilfsmittel, um solche Dialog-Fenster mit einer Art Malprogramm zu
gestalten, so dass du nicht die Größen und Positionen der ganzen Eingabe-Elemente (Buttons, Textfelder,
Beschriftungen, ...) alle mühsam mit Quelltext festlegen musst. In nächsten Kapitel werden wir diesen
sogenannten "Qt Designer" ausprobieren.
In diesem Kapitel zeige ich Dir, wie du ein Dialog-basiertes Programm mit dem Qt Designer erstellen
kannst. Du kannst das vorherige "test" Projekt schließen, wir werden ein neues Projekt anlegen.
Gehe ins Menü Datei/neu... und wähle die Vorlage "Qt Widgets Application". Der Projektname soll "test2"
lauten. Klicke dann ein paar mal auf "weiter", bis das neue Projekt erstellt wurde. Das Programm ist
bereits ausführbar, probiere es aus.
Öffne jetzt die Formulardatei mainwindow.ui per Doppelklick, dann kommst du in den Qt Desginer.
Die gepunktete graue Arbeitsfläche repräsentiert den Inhalt des Fensters. Dort kannst du Dialogelemente
einfügen. Doch bevor du das machst, wechsle ganz links am Rand einmal kurz in die Editieren-Ansicht.
Dann siehst du nämlich, dass diese .ui Datei eine lesbare XML Datei ist. Diese Ansicht ist interessant,
um zu sehen, welche Eigenschaften von den Standardvorgaben abweichen, denn nur diese werden in die XML
Datei geschrieben.
Zurück in der Designer-Ansicht sollst du nun einen PushButton aus der linken Leiste in die Arbeitsfläche
ziehen und mit "Klick mich!" beschriften. Benutze dazu nach dem Ziehen die rechte Maustaste, dann den
Befehl "Text ändern".
Das sollte jetzt ungefähr so aussehen:
Markiere rechts oben (neben der Arbeitsfläche) das Objekt "MainWindow", danach suchst du darunter im
gelben Bereich die Eigenschaft "windowTitle". Ändere den Wert auf "Rette die Welt!". Starte das Programm
zur Kontrolle, es sollte jetzt so aussehen:
Die Schaltfläche hat noch keine Funktion, was wir jetzt ändern. Klicke mit der rechten Maustaste aufdie
Schaltfläche und wähle den Befehl "Slot anzeigen...". Wähle den Slot clicked() per Doppelklick aus. Nun
wechselt die Entwicklungsumgebung in die Editor-Ansicht, welche den C++ Quelltext der Klasse
mainwindow.cpp anzeigt.
Du siehst im Konstruktor einen Aufruf der Methode setupUi(this). Das ist die Stelle, wo die
Konfiguration aus dem Qt Designer geladen wird.
Weiter unten hat die IDE eine neue leere Methode mit dem Namen on_pushButton_clicked() eingefügt. Das
ist der Slot, der aufgerufen wird, wenn die Schaltfläche ihr Signal "clicked()" aussendet. Füge die
markierten Zeilen ein:
Starte das Programm erneut, um es auszuprobieren. Klicke auf den Button.
Ich zeige Dir jetzt, wie man Dialogfenster mit Hilfe von Layouts gestaltet.
Layouts richten Dialogelemente automatisch ordentlich aus und sorgen dafür, dass sie sich an
unterschiedliche Bildschirmgrößen und Schriftgrößen anpassen.
Öffne erneut die Datei mainwindow.ui. Die Arbeitsfläche hat am oberen und unteren Rand Platz für eine
Menüleiste und eine Statusleiste reserviert. Wir brauchen sie nicht. Lösche daher am rechten Rand der
IDE die beiden Objekte "menuBar" und "statusBar" mit der rechten Maustaste.
Schiebe den Button weit nach unten und Ziehe dann ein "Form" Layout in die Arbeitsfläche, etwa so:
Ziehe zwei "Labels" an den linken Rand des Form Layouts und ändere ihre Beschriftungen (mittels
Doppelklick) auf "Vorname:" und "Nachname:"
Ziehe zwei "Line Edit" Felder in die roten Markierung rechts neben die Labels:
Ganz rechts in der IDE sollst du nun die Objekt-Namen der beiden Eingabefelder ändern. Sie heißen
derzeit "lineEdit" und "lineEdit_2". Klicke mit der rechten Maustaste auf diese Namen und dann auf
"Objektnamen ändern", um sie auf "vorname" und "nachname" zu ändern:
Ziehe ein "Horizontal Layout" unter das bestehende Layout in die Arbeitsfläche und schiebe den Button
dort hinein:
Links neben den Button ziehst du nun einen "Horizontal Spacer", der Button wird dadurch an den rechten
Rand gedrückt.
Du hast ein Formular mit Labels und Eingabefelder, und du hast einen Button rechts ausgerichtet.
Klicke rechts in der Objektliste mit der rechten Maustaste auf MainWindow und wähle dann den Befehl
"Layout -> Objekte Tabellarisch anordnen". Dadurch wird das Formular automatisch an die Fenstergröße
angepasst.
Mache das Fenster ein bisschen kleiner. Etwa so:
Starte das Programm, um es zu testen.
Sieht gut aus, oder?
Wechsle am linken Rand der Entwicklungsumgebung in die Editieren-Ansicht und öffne dort die Datei
mainwindow.cpp. Dort soll nun der Slot on_pushButton_clicked() so geändert werden, dass der Name aus dem
Formular ausgegeben wird:
Wie du an diesem Beispiel sehen kannst, lässt sich die qDebug() Funktion genau so benutzen, wie das
QTextStream Objekt - mit dem << Operator.
Darunter wird der neue Meldungstext für die MessageBox zusammen gestellt, indem die beiden Namen
zusammen gefügt werden. Das fertige Programm sieht so aus:
Nun hast du die grundsätzliche Arbeitsweise mit dem Qt Designer kennen gelernt, und wie dessen
generierter Code mit selbst geschriebenem zusammen kommt.
Wenn du dein Programm mit jemandem teilen oder ohne Entwicklungsumgebung starten möchtest, musst du je
nach Betriebssystem unterschiedlich vorgehen.
Du findest die Ausgabe des Compilers im gleichen Ordner, wo auch dein Projekt gespeichert ist. Der
Ordner hat einen auffällig langen Namen, der mit "build" beginnt. Darin befindet sich eine Datei mit der
Endung .exe, in diesem Fall "test.exe". Das ist dein Maschinencode in ausführbarer Form, diese Datei
musst du teilen.
Die anderen Dateien kannst du löschen, sie werden nicht benötigt. Allerdings kannst du das Programm noch
nicht per Doppelklick starten, weil zusätzlich einige .dll Dateien (Bibliotheken) benötigt werden. Die
Fehlermeldungen zeigen es:
Der Hinweis mit der "Neuinstallation" ist allerdings nicht hilfreich, denn wir haben ja gar kein
Installationsprogramm. Also muss eine manuelle Prozedur her. Du findest die fehlenden Dateien im
Installationsordner von Qt unter Qt/x.x.x/mingwxx_64.
Kopiere die benötigten .dll Dateien (oder einfach alle) aus dem "bin" Verzeichnis in deinen
Programm-Ordner. Kopiere außerdem den ganzen "plugins" Ordner. Das Ergebnis soll so aussehen:
Jetzt ist das Programm vollständig und per Doppelklick ausführbar. Du kannst den Ordner im ZIP-Format
komprimieren und dann z.B. per E-Mail verschicken.
Du findest die Ausgabe des Compilers im gleichen Ordner, wo auch dein Projekt gespeichert ist. Der
Ordner hat einen auffällig langen Namen, der mit "build" beginnt. Darin befindet sich eine Datei ohne
Endung, in diesem Fall "test". Das ist dein Maschinencode in ausführbarer Form, diese Datei musst du
teilen.
Die anderen Dateien kannst du löschen, sie werden nicht benötigt. Manche Dateimanager können das
Programm per Doppelklick starten. Falls deiner das nicht kann, öffne ein Terminal Fenster, wechsle mit
dem "cd" Befehl in den richtigen Ordner und starte dann das Programm durch Eingabe von "./test".
Das Programm benutzt die "libqt5" Bibliotheken aus dem Installationsordner von Qt, oder zentral
installierte Bibliotheken. Wenn beides fehlt, was auf fremden Computern recht wahrscheinlich ist,
bekommst du so eine Fehlermeldung:
Die benötigten Bibliotheken findest du im Installationsorder von Qt unter Qt/x.x.x/gcc/. Vom lib Ordner
brauchst du nur die Dateien, deren Name ".so" enthält. Kopiere außerdem den ganzen "plugins" Ordner. Das
Ergebnis soll so aussehen:
Jetzt musst du noch den Suchpfad (LD_LIBRARY_PATH) angeben, damit Linux diese Dateien findet:
Damit man diesen Befehl nicht immer wieder neu eintippen muss, kannst du im Programmordner ein
Start-Script erstellen. Das ist eine Textdatei, z.B. mit dem Namen start.sh und folgendem Inhalt:
Kennzeichne dieses Start-Script im Dateimanager als ausführbar, danach kannst du es per Doppelklick
starten.
Jetzt hast du alle Dateien zusammen. Du kannst den Ordner im .tar.gz Format komprimieren und dann z.B.
per E-Mail verschicken.
Die vorherigen Kapitel zur Einführung haben dir einen ersten Überblick
verschafft wie man Programme mit Qt Creator erstellt.
In den folgenden Kapitel möchte ich Dir die Teile von der Programmiersprache C++ zeigen, die man zur
Nutzung des Qt Frameworks kennen muss. Du solltest dir die Zeit nehmen, die folgenden Themen in eigenen
Programmen auszuprobieren. Das Meiste lässt sich wohl am einfachsten in Konsole-Anwendungen austesten.
Ein Literal ist ein konkreter Wert, der direkt im Quelltext steht. Beispiel:
Die markierten Teile sind Literale. Die Programmiersprache unterscheidet zwischen unterschiedliche Typen
von Literalen:
Da wir inzwischen bei 64-Bit Rechnern angekommen sind, können Integer Zahlen inzwischen sehr große Werte
haben. Früher hat man manchmal ein "l" (kleines L) hinter Integer Literale geschrieben, um zu bestimmen,
dass es ein "long integer" sein soll. Man kann auch "ul" schreiben, um anzuzeigen, dass es ein "unsgined
long integer" sein soll. Im Zeitalter von 64-Bit Rechnern sieht man das allerdings nur noch selten.
Siehe dazu auch das nächste Kapitel Datentypen.
Innerhalb von Zeichenketten kann man sogenannte Escape Sequenzen benutzen, um spezielle Zeichen
darzustellen, die man nicht direkt hinschreiben kann:
(Es gibt noch ein paar mehr, die habe ich aber noch nie benutzt)
Beispiel:
Alle Variablen und Parameter müssen einen Typ haben. Die Programmiersprache kennt neben Klassen folgende
einfache Typen.
Integer Zahlen können positiv und negativ sein, ohne Nachkommastellen. Die C/C++ Spezifikation legt nur
eine Mindest-Größe fest. Daneben habe ich geschrieben, welche Werte der GNU C++ Compiler auf 64-Bit PC
tatsächlich erlaubt.
All diese Typen gibt es auch als "unsigned" Variante (z.B. unsigned int), dann geht der Wertebereich von
0 bis doppelt so hoch wie in der obigen Tabelle angegeben. Der Typ char ist für Textzeichen gedacht,
kann aber auch numerisch verwendet werden.
Manchmal braucht man Integer Typen die unabhängig von der Maschine eine ganz bestimmte Größe im Speicher
haben. Diese sind in der Header-Datei <cstdint> definiert:
Auch hier gibt es wieder "unsigned" Varianten, die heißen dann uint8_t, uint16_t, uint32_t und uint64_t.
Für Fließkommazahlen gibt es folgende Typen:
Zu guter Letzt gibt es noch den booleschen Datentyp:
Wobei hier die Besonderheit gilt, dass der Computer alle Zahlen ungleich 0 für wahr hält.
Aufzählungen (englisch: enumerations) werden gerne benutzt, wenn man nur ganz bestimmte wenige Werte
zulassen will. Zum Beispiel für die Farben einer Ampel:
Für rot benutzt der Compiler intern die Zahl 0, für gelb die 1 und für grün die 2. Man kann die Zahlen
alternativ dazu selber festlegen:
Variablen reservieren einen Platz im Arbeitsspeicher des Computers, um dort konkrete Werte zu speichern.
Dieses Programm definiert in der markierten Zeile eine Variable vom Typ "int" mit dem Namen "x". Der Typ
int legt fest, dass die Variable Zahlen (ohne Nachkommastellen) speichern kann. Im Kapitel Datentypen findest du weitere Typen erklärt.
Die darunter folgenden Zeilen weisen der Variable x nacheinander unterschiedliche Werte zu. Zuerst hat
sie den Wert 3, dann 4 und schließlich 5. Variablen können verändert werden, deswegen heißen sie so. Die
Zuweisung eines Wertes erfolgt mit dem "=" Zeichen. Links davon steht immer der Name der Variable,
rechts davon der Wert, der zugewiesen werden soll.
Man kann Variablen schon während der Deklaration mit einem Wert initialisieren:
Variablen, die innerhalb einer Funktion oder Methode deklariert sind, werden auf dem Stack gespeichert, das ist quasi das
Kurzzeitgedächtnis deines Computers. Der vergisst seine Inhalte am Ende der Funktion.
Dazu gibt es eine Ausnahme: Wenn man "static" vor die Variablen-Deklaration schreibt, wird sie
stattdessen im Heap gespeichert, dem
Langzeit-Gedächtnis des Computers. Der Heap vergisst seine Inhalte erst, wenn das Programm endet.
Beispiel für eine statische Variable:
Das Programm erzeugt die Ausgabe:
Beim Ersten Aufruf erstellt die Funktion ausgeben() eine Variable vom Typ "int" mit dem Namen "x" und
initialisiert sie mit dem Wert 0. Danach wird 1 addiert und ausgegeben. Bei allen folgenden Aufrufen
benutzt die Funktion die bereits bekannte Variable. Sie wird nicht erneut initialisiert. Deswegen erhöht
sich ihr Wert mit jeder Addition.
Eine andere Möglichkeit, Variablen auf dem Heap anzulegen siehst du in diesem Beispiel beim QTextStream:
Die Variable "out" befindet sich außerhalb aller Funktionen. Solche Variablen nennt man globale
Variablen. Sie sind für das gesamte Programm erreichbar, es sei denn, du schreibst zusätzlich das Wort
"static" davor, wie im obigen Beispiel. An dieser Stelle bedeutet das Wort "static", daß die Variable
für andere Quelltext-Dateien unsichtbar sein soll.
Das Schlüsselwort "static" hat also je nach Position zweierlei Bedeutung:
Global sichtbare Variablen sollte man tunlichst vermeiden, denn sie bergen das Risiko, sich zu
verzetteln. Bei einem größeren Programm mit 100 Quelltext-Dateien kann es allzu leicht passieren, dass
globale Variablen versehentlich doppelt deklariert werden oder dass man auf falsche globale Variablem
zugreift, die zu anderen Dateien gehören. Die IDE fordert dich auf, globale Variablen als "static" zu
kennzeichnen, um solche Fehler zu vermeiden.
Variablen sind nur in dem Block gültig, wo sie deklariert wurden:
Die Variable x wurde innerhalb der main() Funktion deklariert, deswegen ist sie nur dort verwendbar.
Andere Funktionen haben darauf keinen direkten Zugriff.
Normale Variablen speichern jeweils nur genau einen Wert. Arrays speichern viele Werte:
Hier wird eine Array-Variable (oder kurz: ein Array) erstellt, das Platz für drei Elemente hat. Danach
werden die drei Speicherplätze mit Werten belegt. Zum Schluss wird das erste Element auf dem Bildschirm
ausgegeben.
Beim Erzeugen des Arrays gibt man in eckigen Klammern an, wie groß das Array sein soll.
Beim Zugriff auf den Speicher gibt man in eckigen Klammern an, auf welches Element man zugreifen will.
Diese Angabe nennt man Index. Hier gilt zu beachten, dass der Index immer bei 0 beginnt.
Arrays bergen das Risiko, versehentlich falsche Indices zu verwenden und damit auf physikalische
Speicherzellen zuzugreifen, die nicht mehr zum Array gehören. Das ist ein großer Schwachpunkt dieser
Programmiersprache. Aber es gibt einen besseren Ersatz: Die Array- und Listen-Klassen wie zum Beispiel
QByteArray und QList verhindern derartige Fehler durch eingebaute
Kontrollen.
Man kann Arrays schon während der Deklaration mit Werten initialisieren. Die Größe [3] ergibt sich dann
automatisch:
Es gibt auch mehrdimensionale
Arrays, aber die erkläre ich hier nicht, weil sie in Qt Anwendungen sehr selten vorkommen.
Eine besondere Variante von Arrays sind die Zeichenketten. Zeichenketten sind Arrays von Zeichen.
Der Datentyp "char" kann ein Zeichen speichern. Ein Array aus Zeichen nennt man Zeichenkette, wobei
Zeichenketten am Ende mit einer 0 Angeschlossen werden müssen (nicht verwechseln mit dem Zeichen '0').
Die 0 kennzeichnet das Ende der Zeichenkette.
Beide Fälle erzeugen genau die gleiche Ausgabe. Der zweite Fall ist einfach eine bequemere alternative
Schreibweise für Zeichenketten. Die Abschließende Null wird im zweiten Fall automatisch angehängt.
Beachte die Unterschiedlichen Anführungsstriche. Einfache Anführungsstriche sind für Zeichen, doppelte
Anführungsstriche sind hingegen für Zeichenketten.
Zeichen und Zeichenketten leiden an einer alten Designschwäche. Sie wurden von Anfang an auf exakt 8-Bit
Größe festgelegt. Für Amerikanische Texte genügt das, aber andere Sprachen brauchen mehr Schriftzeichen,
als in char hinein passt. Heutige Computer nutzen fast ausschließlich den Unicode Zeichensatz, der alle Schriftzeichen der
Welt einschließlich Emoticons darstellen kann. Aber die passen halt nicht alle in char rein.
Am weitesten verbreitet ist die Methode, Unicode Zeichen nach der UTF-8 Methode zu speichern (oder zu
codieren). Bei UTF-8 belegen die US-Amerikanischen Zeichen nach wie vor ein char. Alle anderen Zeichen
belegen zwei bis vier chars. Die Konsequenz daraus demonstriert das folgende Programm:
Die Ausgabe ist:
Das zweite Wort "Löffel" kann nicht korrekt dargestellt werden, weil der Buchstabe "ö" zwei chars
belegt. Man sieht das in der Ausgabe, da erscheinen anstelle des "ö" plötzlich zwei komische Zeichen.
Demzufolge versagen auch zahlreiche alte Funktionen, die zur Verarbeitung von Zeichenketten vorgesehen
waren. Herkömmliche Zeichenketten kannst du nur noch gebrauchen, wenn du dich auf US-Amerikanische
Schriftzeichen beschränkst - aber wer will das schon?
Jetzt kommt die Lösung: Benutze stattdessen die Klasse QString und QStringLiteral.
Die Klassen QString und QStringLiteral speichern nicht Arrays von chars, sondern sie können mit echten
Unicode Zeichenketten umgehen.
In den oberen beiden Zeilen, wo das QStringLiteral einer QString Variable zugewiesen wird, könnte man
QStringLiteral auch weglassen, aber so wie oben dargestellt läuft der Code schneller. Die Begründung
steht dort: "In diesem Fall werden die
internen Daten vom QString beim Compilieren generiert, nicht zur Laufzeit".
Beachte, dass unter Windows (und nur dort) die Ausgabe in die Konsole nochmal auf CP850 umkodiert werden
muss, damit sie die Umlaute richtig darstellt. Sonst sieht das so aus:
Asiatische und Kyrillische Schriftzeichen kann die Windows Konsole generell gar nicht darstellen. Die
Linux Konsole kann das aber, sie unterstützt Unicode. Ich erwarte, dass die Windows Konsole ebenfalls
bald auf Unicode umgestellt wird.
Funktionen dienen dazu, das Programm zu strukturieren und Teile des Codes wiederverwendbar zu machen.
Hier wird die Funktion ausgeben() mehrmals benutzt, um die Werte der Variablen x, y, sowie des Ausdrucks
"100+34" auszugeben. Die Ausgabe sieht so aus:
Die ausgeben() Funktion hat einen Parameter vom Typ "int" mit dem Namen "was". Innerhalb der Funktion
kann man den Parameter über seinen Namen verwenden. Funktionen können mehrere Parameter haben, dann
werden sie mit Komma getrennt:
Das obige Beispiel zeigt außerdem, dass Funktionen Ergebnisse mit einem bestimmten Typ zurück liefern
können. Funktionen ohne Ergebnis haben den Typ "void", das hast du weiter oben bei der ausgeben()
Funktion gesehen.
Bei der main() Funktion gibt es zwei spezielle Ausnahmen. Sie hat immer einen Rückgabewert vom Typ int,
trotzdem darf man auf die "return" Anweisung verzichten. In diesem Fall geht der Compiler davon aus,
dass man eine 0 zurück geben wollte.
Die zweite Ausnahme bezieht sich auf die Parameter. Und zwar darf man die main() Funktion wahlweise mit
oder ohne Parameter definieren. Beides ist richtig, aber es darf nur eine von beiden im Programm geben:
Der erste Parameter der main() Funktion ist die Anzahl der Kommandozeilen-Argumente. Der zweite
Parameter enthält die Kommandozeilen-Argumente. Auf das Sternchen gehe ich später im Kapitel Zeiger ein.
Die Parameter von Funktionen können als "const" gekennzeichnet werden, um anzuzeigen, dass sie nicht
verändert werden.
Falls der Parameter ein Objekt ist, bewirkt das Schlüsselwort const, dass man nur Methoden aufrufen
kann, die ebenfalls als const gekennzeichnet sind. Solche Methoden versprechen, das Objekt nicht zu
verändern. Beispiel:
Du kannst hier nicht die Methode clear()
aufrufen, weil der Parameter als const gekennzeichnet wurde aber die Methode clear() nicht. Die Methode
clear() kann auch gar nicht const sein, weil sie den String verändert.
Die Parameter von Funktionen können Standardwerte haben:
Da der Parameter von der Funktion ausgeben() einen definierten Standardwert hat, kann man die Funktion
ohne Parameter aufrufen. Bei Funktionen mit vielen Parametern klappt das aber nur am rechten Ende:
Hier ist es nicht möglich, die beiden Parameter zu vertauschen (links den "text" und rechts das
"wohin").
Man kann Ausdrücke mit mehreren Operatoren verketten und durch Klammerung die Rangfolge der Operationen
vorgeben:
Wenn man in C++ zwei Zahlen miteinander verrechnet, wird ein Algorithmus passend zu dem komplexeren
Datentyp verwendet. Das Ergebnis entspricht vom Typ her immer dem komplexeren Operanden. Bei Integer
wird immer auf die nächste ganze Zahl abgerundet. Beispiele:
Das folgende Beispiel verdeutlicht den Unterschied zwischen Pre- und Post- Operationen:
Im oberen Fall wird zuerst der Ausdruck "2 * 5" berechnet und danach die Variable a erhöht. Im unteren
Fall wird zuerst die Variable x erhöht und dann "2 * 6" berechnet.
Die Header Datei <cmath> stellt mathematische Funktionen zur Verfügung wie z.B. sqrt() um eine Wurzel
zu ziehen, und Winkelfunktionen (Sinus, Cosinus und Tangens).
Zuweisungen benutzen die Variable auf ihrer linken Seite als ersten Operanden und speichern das Ergebnis
in diese Variable:
Beispiel:
Logische Operatoren verknüpfen Aussagen die wahr oder falsch sind:
Die Bitweisen Operatoren werden in den meisten C++ Programmen nur selten verwendet. Sie funktionieren
wie elektronische Logik-Gatter, allerdings für viele Bits parallel. Elektroniker sollten damit vertraut
sein.
Ich demonstriere die Bitweisen Operatoren anhand von Binärzahlen, weil man so ihre Wirkung
besser sehen kann:
Bei Klassen der objektorientierten Programmierung werden die Operatoren << und >> gerne
für Ein- und Ausgabe "missbraucht", wie du das bereits vom QTextStream kennt. Das ist deswegen möglich,
weil Klassen die Bedeutung von Operatoren neu definieren können.
Der Ternäre Operator "? :" testet, ob eine Bedingung wahr ist. Wenn ja, liefert er den ersten Wert (vor
dem Doppelpunkt) zurück, ansonsten liefert er den zweiten Wert (hinter dem Doppelpunkt). Beispiel:
Die Ausgabe lautet
Ich benutze den ternären Operator gerne, manche anderen Entwickler meiden ihn konsequent, was
auch OK ist. Man kann stattdessen nämlich auch if-else Konstrukte benutzen.
Computer treffen Entscheidungen: Wenn das Konto leer ist, kannst du kein Geld holen. Dafür dient in der
Programmiersprache C++ das Schlüsselwort if:
Der obere Teil wird ausgeführt, wenn der Ausdruck in den Klammern wahr oder nicht 0 ist.
Den else-Teil kann man weg lassen, wenn man ihn nicht braucht.
Die Switch-Case Anweisung kann bei numerischen Werten verwendet werden, um lange Ketten von if-Abfragen
zu ersetzen:
Die break Anweisung sorgt dafür, dass der Switch-Block an dieser Stelle abgebrochen wird. Ohne Break
Anweisung würde der Computer bei Eingabe von 1 sämtliche Wochentage ausgeben.
Beachte die Besonderheit für das Wochenende. Hier haben wir nur eine Ausgabe für zwei Fälle. Ganz unten
kann man hinter "default:" angeben, was der Computer tun soll, wenn keiner der Fälle zutrifft. Wenn er
nichts tun soll, lässt man den Teil weg.
Schleifen dienen dazu, Teile des Programms zu wiederholen.
Die while Schleife wiederholt ihren Block solange der Ausdruck in den Klammern wahr oder nicht 0
ist. Das folgende Beispiel gibt die Zahlen 0, 1, 2, 3 und 4 aus.
Dieses Beispiel gibt die Zahlenfolge 0, 1, 2, 3, 4 aus. Wenn du als Bedingung true schreibst, bekommst
du eine Endlosschleife, denn true ist immer wahr.
Die do-while Schleife wiederholt ihren Block solange der Ausdruck in den Klammern wahr oder nicht 0 ist.
Der Unterschied zum vorherigen Beispiel ist, dass die do-while Schleife mindestens einmal ausgeführt
wird, da die Bedingung erst am Ende geprüft wird.
Dieses Beispiel gibt ebenfalls die Zahlenfolge 0, 1, 2, 3, 4 aus.
Die for-Schleife ist eine kompaktere Variante der oben gezeigten while-Schleifen. Ein typischer
Anwendungsfall ist das Hochzählen einer Variable:
Auch dieses Beispiel gibt die Zahlenfolge 0, 1, 2, 3, 4 aus.
Beachte, dass die drei Parameter der for-Schleife ausnahmsweise mit Semikolon getrennt werden. Die drei
Parameter haben folgende Bedeutung:
Hier mal ein anderes Beispiel, wo die Variable i in 2er Schritten verringert wird:
Dieses Beispiel gibt die Zahlenfolge 10, 8, 6, 4, 2 aus.
Um den Inhalt eines Arrays auszugeben, würde man traditionell so vorgehen:
Dieser Lösungsansatz ist ein bisschen riskant, denn du musst darauf achten, dass die Schleife genau so
oft (vier mal) wiederholt wird, wie das Array Elemente enthält. Deutlich eleganter ist diese Variante:
Das Gleiche funktioniert auch mit praktisch allen Listen-Objekten:
Mit dem Schlüsselwort continue kannst du den aktuellen Durchlauf abbrechen und mit dem nächsten Wert
fortfahren. Das folgende Beispiel gibt alle Wochentage aus, außer den Mittwoch.
Das Schlüsselwort "break" würde hingegen die ganze Schleife abbrechen, so dass nur "Montag" und
"Dienstag" ausgegeben würden.
Die Schlüsselworte "break" und "continue" lassen sich ebenso in while und do-while Schleifen verwenden.
Bei "normalen" Zuweisungen und Funktionsaufrufen werden Objekte kopiert. Beispiel:
Hier ist a ein Objekt vom Typ QString, welcher mit dem Wort "Hallo" initialisiert wurde. b ist formell
eine Kopie von a, also ein zweites Objekt das seinen eigenen Speicherplatz belegt. Beim Funktionsaufruf
werden weitere Kopien angelegt: Das Objekt a wird in den Funktionsparameter x kopiert und das Objekt b
wird in den Funktionsparameter y kopiert.
Die vielen Kopien sind nicht unbedingt schlimm. Zum einen können unsere Computer solche Kopien sehr
schnell anlegen. Zum Anderen enthalten die meisten Qt Klassen intern raffinierte Methoden, um unnötiges
Duplizieren großer Datenmengen zu vermeiden.
Dennoch macht es Sinn, sich mit Referenzen und Zeigern zu befassen. Alleine schon deswegen, weil das Qt
Framework dich an vielen Stellen dazu zwingt.
Eine Referenz ist eine Variable, die auf ein anderes bereits bestehendes Objekt verweist. Referenzen
verbrauchen nur minimal Arbeitsspeicher, nicht mehr als eine Integer Zahl.
Referenzen werden bei der Deklaration der Variable durch das "&" Zeichen vor dem Namen der Variable
gekennzeichnet. Das gilt ebenso für die Parameter von Funktionen. Das folgende Beispiel demonstriert
dies:
In diesem Beispielprogramm gibt es nur ein einziges QString Objekt (a). Die Variable b und auch die
Parameter x und y sind Referenzen auf dieses eine Objekt. Die Konsequenz daraus ist: wenn a, b, x oder y
verändert wird, betrifft das alle vier gleichzeitig. Letztendlich verweisen alle vier auf das selbe
Objekt im Speicher.
Probiere es aus:
Die ausgeben() Funktion verändert y, indem sie " Welt!" an den String anhängt. Die danach folgenden
Ausgaben unter der gestrichelten Linie beweisen, dass sich diese Änderung sowohl auf das Objekt a als
auch auf alle drei Referenzen ausgewirkt hat.
Es ist nicht möglich, eine Referenz auf "nichts" verweisen zu lassen.
An dieser Stelle kommen Zeiger ins Spiel. Zeiger können auf Objekte zeigen oder auch auf nichts. Das
entsprechende Schlüsselwort dazu ist "nullptr". Auch Zeiger verbrauchen nur minimal Arbeitsspeicher, so
wenig wie eine Integer Zahl. Genau genommen sind Zeiger schlicht und ergreifend Zahlen, nämlich die
Position des Objektes im Arbeitsspeicher.
Zeiger-Variablen erkennt man an dem Sternchen vor ihrem Namen:
Eklig wird es, wenn man sich den Rest drumherum genauer anschaut:
Mal kommt ein Sternchen vor den Namen der Variable, mal ein Und-Zeichen und dann haben wir noch diesen
komischen Pfeil "->".
Beim Zugriff auf Zeiger (nicht bei ihrer Deklaration!) bedeuten die Zeichen folgendes:
Findest du das verwirrend? Ich schon! Diese Zeiger sind kompliziert, allerdings ermöglichen sie auch
sehr effiziente Lösungen, die andere Programmiersprachen gar nicht drauf haben. Insofern sind Zeiger wie
extrem scharfe Messer gleichzeitig gut und schlecht.
Man sollte Zeiger sparsam verwenden - beschränkt auf die Fälle, wo sie wirklich von Vorteil sind.
Zeiger kann man auf "nichts" zeigen lassen:
Die dritte Ausgabe findet nicht statt, weil der Zeiger c auf nichts zeigt. Mit Referenzen wäre das nicht
umsetzbar.
Weiter oben habe ich erklärt, das Variablen nur in dem Block gültig sind, wo sie deklariert wurden. An
Ende jeder Funktion wird der Speicher (Stack) dieser Variablen wieder frei gegeben.
Mit "new" reservierst du hingegen ein Stück vom Heap für das angegebene Objekt. Im Heap
verbleiben Objekte so lange, bis du sie ausdrücklich mit "delete" wieder entfernst.
Die Funktion erzeuge() Erzeugt ein QString Objekt auf dem Heap Speicher und liefert einen Zeiger zurück,
der auf das Objekt zeigt. Die Funktion benutze() gibt das Objekt auf dem Bildschirm aus. In der main()
Funktion werden diese beiden Schritte nacheinander aufgerufen. Damit hast du den Beweis, dass das Objekt
auch nach Beendigung der Funktion erzeuge() existiert. Ganz zum Schluss wird der Speicher wieder
freigegeben.
Der Heap umfasst den gesamten freien Arbeitsspeicher deines Computer. Die laufenden Programme teilen
sich den Heap. Allerdings verhindert das Betriebssystem, dass Programme sich gegenseitig in ihre fremden
Objekte gucken.
Die Benutzung von new und delete ist riskant. Wenn du zum Beispiel versehentlich auf Speicher zugreifst,
der vorher mit delete freigegeben wurde, kann alles Mögliche passieren - von Fehlfunktionen bis hin zum
Absturz des Programms. Früher konnten solche Fehler sogar den ganzen Rechner zum abstürzen bringen.
Ebenso gefährlich ist es, delete für die selbe Speicheradresse mehrfach aufzurufen. Dann kann es
durchaus passieren, dass falscher Speicher freigegeben wird.
Ein weiteres Risiko ist, das Freigeben zu vergessen. So ein Programm nimmt dem Computer fortlaufend
immer mehr Arbeistspeicher weg, bis der Rechner zum Stillstand kommt. Das nennt man Memory-Leak.
Einige andere Programmiersprachen haben anstelle von delete einen sogenannten Garbage Collector, der
automatisch herausfindet, wann welcher Speicher freigegeben werden kann. Garbage Collectoren sind aber
auch keine 100% saubere Lösung, da sie manchmal versagen und unter ungünstigen Umständen viel Rechenzeit
verbrauchen. Qt hat dafür eine andere Lösung, die ich weiter unten
vorstelle.
Klassen fassen Daten und Funktionen zu sinnvollen Einheiten zusammen. Damit kann man große Programme
übersichtlich gestalten. Wenn man Speicher für eine Klasse belegt, hat man ein Objekt erstellt. Das
Objekt ist eine konkrete Ausprägung der Klasse.
Um das mal mit Dingen aus dem echten Leben zu vergleichen:
Du siehst hier 10 Objekte von der Klasse Würfel.
Die Klasse Würfel hat eine einstellbare Anzahl von Seiten (Rollenspieler brauchen das) und eine
Funktion, die einen Wurf berechnet:
Dieses Programm gibt 5 Zufallszahlen zwischen 1 und 6 aus. Die Funktionen von Klassen (in diesem Fall
"wurf") bezeicnet man als Methoden. Die Variable w ist ein Objekt (oder eine Instanz) von der Klasse
Wuerfel.
Konstruktoren haben die Aufgabe, ein Objekt beim Erstellen zu initialisieren.
Wenn du das Programm mehrmals ausführst, wirst du bemerken, dass es immer wieder die gleichen
Zahlenfolgen ausgibt. Wir haben nämlich versäumt, den Zufallsgenerator zu initialisieren. Vernünftige
Ergebnisse liefert er nur, wenn er mit wechselnden Zahlen initialisiert wird.
Dazu brauchen wir den sogenannten Konstruktor, hier markiert:
Der Konstruktor initialisiert den Zufallszahlen-Generator mit der aktuellen Uhrzeit. Wenn du das
Programm jetzt mehrmals ausführst, erzeugt es daher immer wieder andere Zahlenfolgen.
Man kann dem Konstruktor Parameter übergeben, wie jeder anderen Funktion auch:
So legen wir direkt beim Anlegen des Objektes die Anzahl seiner Seiten fest.
Das Programm kann beliebig viele Objekte von der Klasse Wuerfel erzeugen:
Der Compiler erzeugt automatisch einen Kopier-Konstruktor, mit dem man das Objekt (den Würfel) kopieren
kann:
Falls dieser automatisch erzeugte Kopierkonstruktor mal nicht tut, was er soll, kann man ihn durch einen
eigenen überschreiben:
Man kann das Kopieren verbieten, indem man den Kopier-Konstruktor löscht:
Das QT Framework stellt für den gleichen Zweck das Makro Q_DISABLE_COPY zur Verfügung.
Klassen können voneinander abgeleitet werden. Man sagt dann, dass die neue Klasse die Eigenschaften der
alten erbt. Sie kann dann weitere Eigenschaften hinzufügen oder vorhandene Methoden überschreiben (durch
neue ersetzen). Als Beispiel erstellen wir einen neuen Würfel der als neue Eigenschaft eine Farbe hat:
Der FarbigeWuerfel erbt alle Eigenschaften von Wuerfel, und zwar so, dass die geerbten öffentlichen
Eigenschaften weiterhin öffentlich bleiben:
Wenn man das "public" weglassen würde, dann wären alle geerbten Eigenschaften von außen nicht mehr
zugänglich.
Der neue Würfel bekommt einen neuen Konstruktor:
Beim Initialisieren ruft dieser zuerst den geerbten Konstruktor des originalen Würfels auf und
initialisiert danach sein Attribut "farbe":
Viele Entwickler (so auch die Macher von Qt) haben sich angewöhnt, Attribute als private zu kennzeichnen
und Zugriffe darauf über sogenannte getter und setter Methoden zu kapseln:
Beachte die Zeile "this->seiten=seiten". Weil hier das Argument der Funktion genau so heißt, wie das
Attribut des Objektes müssen wir dem Compiler mit "this->" mitteilen, das sich die linke Seite der
Zuweisung auf die Farbe von diesem (this) Objekt bezieht.
Virtuelle Methoden ermöglichen abgeleiteten Klassen, die Methoden ihrer Vorlage zu überschreiben.
Probiere dazu bitte folgendes Beispiel aus:
Die main() Funktion ruft die Methode dreimal() auf, welche der Papa von Mama geerbt hat. Diese dreimal()
Methode ruft wiederum 3 mal die Methode einmal() auf, aber von welcher Klasse?
Genau das hängt davon ab, ob vor der Methode einmal() das Schlüsselwort "virtual" steht. Mit "virtual"
kommt die erwartete Ausgabe heraus: Papa Papa Papa
Wenn man aber das Schlüsselwort "virtual" entfernt, gibt der Papa fälschlicherweise "Mama Mama Mama"
aus.
Gewöhne dir deswegen folgendes an: Markiere jede Methode die überschrieben werden darf als virtual,
und unterlasse das Überschreiben von nicht virtuellen Methoden.
Als Gegenstück zu den Konstruktoren können Klassen auch Destruktoren haben. Diese werden automatisch
ausgeführt, wenn das Objekt aus dem Speicher entfernt wird. Die Aufgabe des Destruktors ist,
aufzuräumen. Falls das Objekt irgend etwas erzeugt hat, was wieder abgebaut werden muss, ist der
Destruktor der richtige Platz für den entsprechenden Code. Beispiel:
Der Papa erbt alle Eigenschaften von Mama, und zusätzlich kann er furzen (Frauen furzen nicht). Das
Programm erzeugt die Ausgabe:
Beim Erzeugen des Objektes (egal ob mit oder ohne new) wird sein Konstruktor aufgerufen und außerdem
auch der Konstruktor der Vorlagen-Klasse(n). Beim löschen des Objektes werden dementsprechend die
Destruktoren aufgerufen, und zwar in umgekehrter Reihenfolge.
Probiere jetzt das folgende fehlerhafte Programm aus:
Hier benutze ich einen Zeiger auf eine Mama, lasse ihn tatsächlich aber auf ein Objekt von der Klasse
Papa zeigen. Das ist erlaubt, weil Papa von Mama geerbt hat. Bis hierhin ist noch alles OK.
Probiere das Programm, dann siehst du den Fehler in der Ausgabe:
Da fehlt ein Destruktor-Aufruf! Der Speicher von Papa wurde nicht freigegeben. In größeren Programmen
führen solche Fehler früher oder später zum Absturz.
Was habe ich falsch gemacht? Ich habe bei den Destruktoren das Schlüsselwort "virtual" vergessen. Weil
das Wort fehlt wurde nur der Destruktor von m (also Mama) aufgerufen. Der Compiler hat sich dumm
gestellt und nicht berücksichtigt, dass der Zeiger in Wirklichkeit auf ein anderes Objekt von der Klasse
Papa zeigt.
Deswegen merke: Klassen von denen man weitere Klassen ableitet, brauchen einen virtuellen
Destruktor. Man muss ihn hinschreiben, selbst wenn er leer sein soll, denn der automatisch
erzeugte Destruktor ist nicht virtuell!
Konstruktoren sind übrigens niemals virtuell, die brauchen das nicht.
Weiter oben erwähnte ich, dass die Nutzung von Heap mittels "new" und "delete"
fehlerträchtig ist, weil man das "delete" leicht vergisst oder zum falschen Zeitpunkt ausführt. Das Qt
Framework entschärft diese Situation, indem es die Aufrufe von "delete" weitgehend automatisiert.
Jedes Qt Objekt kann beim Konstruieren mit einem sogenannten "parent" verknüpft werden. Wir hatten das
zum Beispiel an dieser Stelle gleich zweimal:
Objekte, die mit einem parent verknüpft sind, werden automatisch zusammen mit dem parent gelöscht. Dafür
sorgt dessen Destruktor. Die Konsequenz daraus ist: Wenn ein Fenster geschlossen wird, verschwinden auch
alle anderen Elemente, die darin eingefügt wurden.
Aus diesem Grund ist es in Qt Programmen nur selten nötig, Speicher aktiv mittels "delete" freizugeben.
Das musst du nur bei Objekten machen, die ohne parent konstruiert wurden
Das Konzept der Signale und Slots ist im Artikel Signal & Slots dokumentiert. Für den Einstieg
fasse ich mal die wichtigsten Punkte zusammen:
Das Qt Framework benutzt Reflexion, um zur Laufzeit
Objekte miteinander zu verknüpfen. Dadurch wird das Programmieren von grafischen Anwendungen mit Qt
genau so bequem, wie mit anderen modernen Programmiersprachen. Da der C++ Standard aber erst ab
Version 23 Reflexion unterstützt, benutzt Qt einen Code-Generator namens
Meta Object Compilers (MOC), der zusätzliche
Quelltext-Dateien (im build Ordner) erzeugt. Darin stehen alle Informationen, die für Reflexion
notwendig sind. Darauf wiederum baut das System der Signale und Slots auf.
Der Meta Object Compiler setzt voraus, dass die betroffenen Klassen in separate .h und .cpp Dateien
aufgeteilt sind, wie ich das oben in der Einführung anhand der Schüler Klasse
erklärt habe. Weiterhin müssen die gewünschten Klassen das Makro Q_OBJECT enthalten. Dieses Makro
aktiviert den Meta Object Compiler.
In Qt können Objekte miteinander kommunizieren, indem sie sich Signale senden. Um ein Signal zu senden,
ruft das Objekt eine seiner eigenen Signal-Methoden auf. Die gleiche oder eine andere Klasse kann das
Signal empfangen, wenn sie eine Slot-Methode hat, deren Parameter zum Signal passt.
Die Verbindungen zwischen Signal und Slot werden erst zur Laufzeit des Programms hergestellt, und zwar
mit der Funktion connect().
Die Namen der Signal- und Slot-Methoden kannst du dir selber nach belieben ausdenken. Ein Beispiel:
Den Quelltext für die Signal-Methode erzeugt der Meta Object Compiler. Deswegen lässt man sie in der
.cpp Datei weg.
Die connect() Funktion kann Signale mit dazu
passenden Slot-Funktionen verbinden. Auf beiden Seiten sind mehrfache Zuordnungen möglich. Beispiel für
eine einzelne Verbindung:
Hier wird das Signal "timeOver" von dem Objekt "quelle" mit dem Slot "quitGame" von dem Objekt
"ziel" verbunden.
Wenn also das Objekt "quelle" seine eigene Methode timeOver() aufruft, dann sorgt das Qt
Framework dafür, dass nun die Methode quitGame() vom Objekt "ziel" aufgerufen wird. Signale und
Slots können Parameter haben, dann müssen sie aber zusammen passen.
Du hast hier einen Einblick in die Programmiersprache C++ erhalten. Wenn du bis hierhin tapfer
durchgehalten hast, ohne durch zu drehen, stehen die Chancen gut, dass du das notwendige Talent
zum Programmieren hast.
C++ ist eine gute Basis, auf der man aufbauen kann.
Als professioneller Programmierer kann ich mir nur selten aussuchen, mit welcher
Programmiersprache ich arbeite. Das wird meistens vom Projekt oder der Firma vorgegeben. Ich
habe mit Basic und Pascal angefangen - beides Sprachen, die heute kaum noch von Bedeutung sind.
Danach lernte ich C++, wovon ich bis heute profitiere. Die Grundlagen sind nämlich bei
zahlreichen anderen Programmiersprachen sehr ähnlich, insbesondere bei Java, C#, Swift und Go. Im
übrigen wurden alle aktuellen Betriebssysteme überwiegend in C/C++ programmiert. Auch die
Script-Sprache Python wurde mit C gebaut.
Manche Entwickler wundern sich, dass viele Qt Klassen scheinbar den Funktionsumfang der
Standard-Template-Bibliothek (STL) duplizieren. In Wirklichkeit ist es aber anders herum, denn
Qt gab es bereits ein paar Jahre vor der STL. Man kann Qt mit der STL kombinieren, ich würde das
allerdings eher vermeiden.
Zum weiteren Lernen empfehle ich, ein dickes Fachbuch zu kaufen. Zum Beispiel:
Für das QT Framework brauchst du kein Buch, weil die Webseite von
Qt schon sehr umfangreich ist. Ich
hoffe, mein Artikel hat dich ausreichend vorbereitet, mit diesen Materialien weiter zu arbeiten.
Viel Erfolg!
Wiederholschleife
#include <QFile>
#include <QTextStream>
#include <QTextCodec>
int main()
{
QTextStream out(stdout);
#ifdef Q_OS_WIN
out.setCodec("CP850");
#endif
QFile datei("../test/schueler.csv");
if (datei.open(QIODevice::ReadOnly))
{
QTextStream strom(&datei);
strom.setCodec("UTF-8");
while (strom.atEnd()==false)
{
QString zeile=strom.readLine();
out << zeile << Qt::endl;
}
datei.close();
}
}
while (! strom.atEnd())
{
...
}
Text parsen
#include <QFile>
#include <QTextStream>
#include <QTextCodec>
int main()
{
QTextStream out(stdout);
#ifdef Q_OS_WIN
out.setCodec("CP850");
#endif
QFile datei("../test/schueler.csv");
if (datei.open(QIODevice::ReadOnly))
{
QTextStream strom(&datei);
strom.setCodec("UTF-8");
while (strom.atEnd()==false)
{
QString zeile=strom.readLine();
QStringList teile=zeile.split(",");
out << "Name: " << teile[0] << Qt::endl;
out << "Geburtsdatum: " << teile[1] << Qt::endl;
out << "Klasse: " << teile[2] << Qt::endl;
out << "Rufummer: " << teile[3] << Qt::endl;
out << Qt::endl;
}
datei.close();
}
}
#include "schueler.h"
#include <QFile>
#include <QTextStream>
#include <QTextCodec>
int main()
{
QTextStream out(stdout);
#ifdef Q_OS_WIN
out.setCodec("CP850");
#endif
QFile datei("../test/schueler.csv");
if (datei.open(QIODevice::ReadOnly))
{
QTextStream strom(&datei);
strom.setCodec("UTF-8");
while (strom.atEnd()==false)
{
QString zeile=strom.readLine();
QStringList teile=zeile.split(",");
Schueler schueler(
teile[0].trimmed(), // Name
teile[1].trimmed(), // Geburtsdatum
teile[2].trimmed(), // Schulklasse
teile[3].trimmed()); // Rufnummer
schueler.ausgeben(out);
}
datei.close();
}
}
Listen
#include "schueler.h"
#include <QFile>
#include <QTextStream>
#include <QTextCodec>
int main()
{
QTextStream out(stdout);
#ifdef Q_OS_WIN
out.setCodec("CP850");
#endif
QList<Schueler> liste;
out << "Lese die Datei ein..." << Qt::endl;
QFile datei("../test/schueler.csv");
if (datei.open(QIODevice::ReadOnly))
{
QTextStream strom(&datei);
strom.setCodec("UTF-8");
while (strom.atEnd()==false)
{
QString zeile=strom.readLine();
QStringList teile=zeile.split(",");
Schueler schueler(
teile[0].trimmed(), // Name
teile[1].trimmed(), // Geburtsdatum
teile[2].trimmed(), // Schulklasse
teile[3].trimmed()); // Rufnummer
liste.append(schueler);
}
datei.close();
}
out << QStringLiteral("Jetzt sind alle Schüler im Speicher") << Qt::endl;
out << QStringLiteral("Liste die Schüler auf...") << Qt::endl;
int i;
for (int i=0; i<liste.size(); i++)
{
liste[i].ausgeben(out);
}
}
QList<Schueler> liste;
...
liste.append(schueler);
for (int i=0; i<liste.size(); i++)
{
liste[i].ausgeben(out);
}
Debuggen
Kommandozeilen-Argumente
#include <QTextStream>
int main(int argc, char* argv[])
{
QTextStream out(stdout);
out << "Anzahl der Parameter: " << argc << Qt::endl;
for (int i=0; i<argc; i++)
{
out << "Parameter " << i << ": "<< argv[i] << Qt::endl;
}
}
Grafik ausgeben
Ein leeres Fenster
QT += gui widgets
#include <QApplication>
#include <QTextStream>
#include <QWidget>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QTextStream out(stdout);
out << "Starte grafische Ausgabe..." << Qt::endl;
QWidget widget;
widget.show();
return app.exec();
}
Ereignisse steuern den Ablauf
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
...
return app.exec();
}
Viele Fenster
#include <QApplication>
#include <QTextStream>
#include <QWidget>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QTextStream out(stdout);
out << "Starte grafische Ausgabe..." << Qt::endl;
QWidget widget;
widget.show();
QWidget widget2;
widget2.show();
return app.exec();
}
Meldungen ohne Konsole ausgeben
#include <QApplication>
#include <QWidget>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
qDebug("starte Widget...");
qWarning("Warnung vor dem bissigen Hund!");
qCritical("Das ist eine Fehlermeldung.");
QWidget widget;
widget.show();
return app.exec();
}
QT_MESSAGE_PATTERN="[%{type}] %{appname} (%{file}:%{line}) - %{message}"
Bunte Grafik zeichnen
#ifndef MEINWIDGET_H
#define MEINWIDGET_H
#include <QWidget>
class MeinWidget : public QWidget
{
Q_OBJECT
public:
explicit MeinWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent* event);
signals:
public slots:
};
#endif // MEINWIDGET_H
#include "meinwidget.h"
#include <QPainter>
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
}
void MeinWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.drawRect(50,50,150,100);
}
#include "meinwidget.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MeinWidget widget;
widget.show();
return app.exec();
}
void MeinWidget::paintEvent(QPaintEvent* /*unused*/)
{
QPainter painter(this);
painter.drawRect(50,50,150,100);
}
#include "meinwidget.h"
#include <QPainter>
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
}
void MeinWidget::paintEvent(QPaintEvent* /*unused*/)
{
QPainter painter(this);
// Kopf
painter.drawRect(50,50,150,100);
// Augen
painter.setBrush(QBrush(Qt::blue));
painter.drawEllipse(75,75,20,20);
painter.drawEllipse(150,75,20,20);
// Mund
painter.setPen(QColor(Qt::red));
painter.drawArc(60,80,130,50,-30*16,-120*16);
// Haare
painter.setPen(QColor(Qt::darkGreen));
painter.drawLine(123,50,115,30);
painter.drawLine(125,50,125,30);
painter.drawLine(128,50,135,30);
}
void MeinWidget::paintEvent(QPaintEvent* /*unused*/)
{
qDebug("paintEvent() wurde aufgerufen");
QPainter painter(this);
// Kopf
...
}
Ein Button zum Anklicken
#ifndef MEINWIDGET_H
#define MEINWIDGET_H
#include <QWidget>
class MeinWidget : public QWidget
{
Q_OBJECT
public:
explicit MeinWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent* event);
signals:
public slots:
void angeklickt();
};
#endif // MEINWIDGET_H
#include "meinwidget.h"
#include <QPainter>
#include <QMessageBox>
#include <QPushButton>
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
QPushButton* button=new QPushButton("Klick mich!",this);
connect(button, &QPushButton::clicked, this, &MeinWidget::angeklickt);
}
void MeinWidget::angeklickt()
{
QMessageBox::information(this,"Info","Die Schaltfläche wurde betätigt");
}
void MeinWidget::paintEvent(QPaintEvent* /*unused*/)
{
qDebug("paintEvent() wurde aufgerufen");
QPainter painter(this);
// Kopf
painter.drawRect(50,50,150,100);
// Augen
painter.setBrush(QBrush(Qt::blue));
painter.drawEllipse(75,75,20,20);
painter.drawEllipse(150,75,20,20);
// Mund
painter.setPen(QColor(Qt::red));
painter.drawArc(60,80,130,50,-30*16,-120*16);
// Haare
painter.setPen(QColor(Qt::darkGreen));
painter.drawLine(123,50,115,30);
painter.drawLine(125,50,125,30);
painter.drawLine(128,50,135,30);
}
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
setFixedSize(400,300);
QPushButton* button=new QPushButton("Klick mich!",this);
connect(button, &QPushButton::clicked, this, &MeinWidget::angeklickt);
}
Die Slot-Methode angeklickt() öffnet eine QMessageBox um folgende Info-Meldung anzuzeigen.
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
setFixedSize(400,300);
QPushButton* button=new QPushButton("Klick mich!",this);
button->move(80,190);
connect(button, &QPushButton::clicked, this, &MeinWidget::angeklickt);
}
Stack versus Heap
QPushButton* button=new QPushButton(...);
Qt Designer
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
QMessageBox::information(this,"Info","Die Schaltfläche wurde betätigt");
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Der Vorname ist " << ui->vorname->text();
qDebug() << "Der Nachname ist " << ui->nachname->text();
QString meldung = ui->vorname->text() +" "+ ui->nachname->text() +
", du bist ein Held!";
QMessageBox::information(this,"Info",meldung);
}
Programm Teilen
Programm Teilen unter Windows
Programm Teilen unter Linux
#!/bin/bash
LD_LIBRARY_PATH=./lib ./test
Lehrstoff
Literale
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int durchmesser = 145;
out << "Der Umfang ist " << (durchmesser * 3.14) << Qt::endl;
}
Beispiel
Beschreibung
Kommentar
"Hallo"
Zeichenkette
Gehört in Anführungszeichen. Es wird automatisch ein unsichtbares Zeichen mit dem Wert 0
angehängt, welches das Ende der Zeichenkette markiert.
'H'
Ein einzelnes Zeichen (char)
Gehört in Hochkommata. Kann nur 8-Bit aufnehmen, kein Unicode!
123
Integer Zahl in dezimaler Schreibweise
Ganze Zahl, ohne Nachkommastellen, darf nicht mit 0 beginnen
015
Integer Zahl in oktaler Schreibweise
Sehr ungewöhnlich, muss mit 0 beginnen, oktal 015 = dezimal 13
0xFF
Integer Zahl in hexadezimaler Schreibweise
hexadezimal 0xFF = dezimal 255
0b10010111
Integer Zahl in binärer Schreibweise
binär 0b10010111 = dezimal 151
12.3
Fließkomma Zahl
Immer in amerikanischer Schreibweise, mit Punkt als Dezimal-Trennzeichen
1.2e3
Fließkomma Zahl in exponentieller Schreibweise
Entspricht 1,2·103 oder 1200
true / false
Boolescher Wert
Alle zahlen ungleich 0 werden ebenfalls als wahr behandelt
Escape-Sequenz
Beschreibung
\"
Anführungsstriche
\n
Neue Zeile (Line Feed, LF)
\r
Wagenrücklauf (Carriage Return, CR)
\t
Tabulator
\x40
Zeichen mit ASCII Code in Hexadezmal, in diesem Fall ein "@"
\\
Das Zeichen "\" selbst (Backslash)
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
out << QStringLiteral("Der Name ist \"Rumpelstielzchen\"") << Qt::endl;
}
Der Name ist "Rumpelstielzchen"
Datentypen
Typ
Spezifikation
auf 64-Bit PC
char
-128 bis +127
-128 bis +127
short int
-32768 bis +32747
-32768 bis +32747
int
-32768 bis +32747
-2147483648 bis +2147483647
long int
-2147483648 bis +2147483647
-922337203685
long long int
-922337203685
-922337203685
Typ
Größe
Bereich
int8_t
8 Bit
-128 bis +127
int16_t
16 Bit
-32768 bis +32747
int32_t
32 Bit
-2147483648 bis +2147483647
int64_t
64 Bit
-9223372036854775808 bis +9223372036854775807
Typ
Größe
Bereich
Beschreibung
float
32 Bit
-1,17·1038 bis +3,40·1038
mit 6 Stellen Genauigkeit
double
64 Bit
-2,22·10308 bis +1,79·10308
mit 15 Stellen Genauigkeit
long double
80 Bit
-3,36·104932 bis +1,18·104932
mit 18 Stellen Genauigkeit
Typ
Größe
Bereich
Beschreibung
bool
8 Bit
true (1) oder false (0)
Boolescher Wert
Aufzählungen
#include <QTextStream>
static QTextStream out(stdout);
enum AmpelFarbe {rot, gelb, gruen};
void losfahren(AmpelFarbe farbe)
{
if (farbe==rot)
{
out << "Stopp! Die Ampel ist doch rot!" << Qt::endl;
}
}
int main()
{
losfahren(rot);
}
#include <QTextStream>
static QTextStream out(stdout);
enum AmpelFarbe {rot=10, gelb=20, gruen=30};
int main()
{
out << rot << Qt::endl; // gibt 10 aus
}
Variablen
int main()
{
int x;
x=3;
x=4;
x=5;
}
int main()
{
int x=5;
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben()
{
static int x=0;
x=x+1;
out << x << Qt::endl;
}
int main()
{
ausgeben();
ausgeben();
ausgeben();
}
1
2
3
static QTextStream out(stdout);
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben()
{
out << x << Qt::endl; // geht nicht
}
int main()
{
int x=3;
ausgeben();
}
Arrays
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int zahlen[3];
zahlen[0]=300;
zahlen[1]=110;
zahlen[2]=575;
out << "Die erste Zahl ist " << zahlen[0] << Qt::endl;
}
int zahlen[] = {300,110,575};
Zeichenketten
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
char erster[] = {'H','a','l','l','o',0};
char zweiter[] = "Hallo";
out << erster << Qt::endl;
out << zweiter << Qt::endl;
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
#ifdef Q_OS_WIN
out.setCodec("CP850"); // Für die Windows Konsole
#endif
char erster[] = "Schere";
char zweiter[] = "Löffel";
out << erster << " belegt " << sizeof(erster)
<< " Bytes im Speicher" << Qt::endl;
out << zweiter << " belegt " << sizeof(zweiter)
<< " Bytes im Speicher" << Qt::endl;
}
Schere belegt 7 Bytes im Speicher
Löffel belegt 8 Bytes im Speicher
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
#ifdef Q_OS_WIN
out.setCodec("CP850"); // Für die Windows Konsole
#endif
QString erster = QStringLiteral("Schere");
QString zweiter = QStringLiteral("Löffel");
out << erster << QStringLiteral(" hat die Länge ")
<< erster.size() << Qt::endl;
out << zweiter << QStringLiteral(" hat die Länge ")
<< zweiter.size() << Qt::endl;
}
Funktionen
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(int was)
{
out << was << Qt::endl;
}
int main()
{
int x=3;
ausgeben(x);
int y=10;
ausgeben(y);
ausgeben(100+34);
}
3
10
134
#include <QTextStream>
static QTextStream out(stdout);
int addiere(int a, int b)
{
return a+b;
}
int main()
{
int ergebnis=addiere(10, 5);
out << ergebnis << Qt::endl;
return 0; // optional
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
out << "Main ohne Parameter" << Qt::endl;
}
int main(int argc, char* argv[])
{
out << "Main mit " << argc << " parametern" << Qt::endl;
}
#include <QTextStream>
static QTextStream out(stdout);
int addiere(const int a, const int b)
{
a=3; // geht nicht
return a+b;
}
int main()
{
int ergebnis=addiere(10, 5);
out << ergebnis << Qt::endl;
}
#include <QTextStream>
static QTextStream out(stdout);
void tuwas(const QString parameter)
{
out << parameter << Qt::endl; // geht
QString grossbuchstaben=parameter.toUpper(); // geht
out << grossbuchstaben << Qt::endl;
parameter.clear(); // geht nicht
}
int main()
{
QString text("Hallo");
tuwas(text);
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString text="Hallo")
{
out << text << Qt::endl;
}
int main()
{
ausgeben(); // gibt "Hallo" aus
ausgeben("Welt!"); // gibt "Welt!" aus
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QTextStream wohin, QString text="Hallo")
{
wohin << text << Qt::endl;
}
int main()
{
ausgeben(out); // gibt "Hallo" aus
ausgeben(out,"Welt!"); // gibt "Welt!" aus
}
Operatoren
Rechnen
Beispiel
Ergebnis
Beschreibung
1 + 2
3
Addition
5 - 1
4
Subtraktion
3 * 4
12
Multiplikation
15 / 3
5
Division
13 % 6
1
Rest einer Division
i++
i=i+1
Post-increment: erhöhe i nach dem Auswerten des Ausdruckes
++i
i=i+1
Pre-increment: erhöhe i vor dem Auswerten des Ausdruckes
j--
j=j-1
Post-decrement: verringere j nach dem Auswerten des Ausdruckes
--j
j=j-1
Pre-decrement: verringere j vor dem Auswerten des Ausdruckes
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int ergebnis = 3 * (4 + 5) - 1;
out << "Ergebnis=" << ergebnis << Qt::endl; // gibt 26 aus
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int a = 5;
int b = 2 * a++;
out << "a=" << a << Qt::endl; // gibt 6 aus
out << "b=" << b << Qt::endl; // gibt 10 aus
int x = 5;
int y = 2 * ++x;
out << "x=" << x << Qt::endl; // gibt 6 aus
out << "y=" << y << Qt::endl; // gibt 12 aus
}
Zuweisungen
Beispiel
Ergebnis
Beschreibung
x = 5
x ist 5
Direkte Zuweisung (hier wird nicht gerechnet)
x += 5
x wurde um 5 erhöht
Addiere Wert
x -= 5
x wurde um 5 verringert
Subtrahiere Wert
x *= 2
x wurde verdoppelt
Multipliziere mit dem Wert
x /= 2
x wurde halbiert
Dividiere durch den Wert
x %= 3
x wurde auf den Reset der Divison von x / 3 gesetzt
Rest einer Division
x &= 0b11110000
Verknüpfe alle Bits mit UND
x |= 0b11110000
Verknüpfe alle Bits mit ODER
x ^= 0b11110000
Verknüpfe alle Bits mit Exklusiv-ODER
x <<= 3
Alle Bits in x sind 3 Schritte nach links verschoben
Verschiebe die Bits nach links
x >>= 3
Alle Bits in x sind 3 Schritte nach rechts verschoben
Verschiebe die Bits nach rechts
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int i = 5;
i += 30;
out << i << Qt::endl; // gibt 35 aus
}
Vergleiche
Beispiel
Ergebnis
Beschreibung
1 == 2
false
Liefert true, wenn beide Werte gleich sind
1 != 2
true
Liefert true, wenn beide Werte ungleich sind
3 < 4
true
Liefert true, wenn der linke Wert kleiner ist
3 > 4
false
Liefert true, wenn der linke Wert größer ist
5 <= 5
true
Liefert true, wenn der linke Wert kleiner oder gleich ist
5 >= 5
true
Liefert true, wenn der linke Wert größer oder gleich ist
Logische Operatoren
Beispiel
Ergebnis
Beschreibung
! (1>2)
true
NICHT: Kehrt die Wahrheit des Ausdrucks dahinter um
(2>1) && (3>4)
false
UND: Liefert true, wenn beide Ausdrücke wahr sind
(2>1) || (3>4)
true
ODER: Liefert true, wenn mindestens einer der beiden Ausdrücke wahr ist
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int zahl = 35;
if ( zahl>5 && zahl<100 )
{
out << "Die Zahl liegt zwischen 5 und 100" << Qt::endl;
}
}
Bitweise Operatoren
Beispiel
Ergebnis
Beschreibung
0b00000011 << 3
0b00011000
Alle Bits n Schritte nach links verschieben (und rechts mit 0 auffüllen)
0b11000000 << 2
0b00110000
Alle Bits n Schritte nach rechts verschieben (und links mit 0 auffüllen)
0b11110000 & 0b11000011
0b11000000
UND-Verknüpfung: Im Ergebnis sind die Bits 1, die in beiden Operanden 1 sind
0b11110000 | 0b11000011
0b11110011
ODER-Verknüpfung: Im Ergebnis sind die Bits 1, die in wenigstens einem Operand 1 sind
0b11110000 ^ 0b11000011
0b00110011
Exklusiv-ODER-Verknüpfung: Im Ergebnis sind die Bits 1, die in genau einem Operand 1
sind
~ 0b11000011
0b00111100
NICHT-Verknüpfung: Im Ergebnis sind alle Bits umgepolt (0->1 und 1->0)
Der Ternäre Operator
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int a=4;
int b=5;
QString ergebnis = (a==b) ? "gleich" : "nicht gleich";
out << "A und b sind " << ergebnis << Qt::endl;
}
A und b sind nicht gleich.
If-Else Bedingung
#include <QTextStream>
static QTextStream out(stdout);
static QTextStream in(stdin);
int main()
{
int eingabe;
out << "Gebe eine Zahl ein" << Qt::endl;
in >> eingabe;
if (eingabe > 100)
{
out << "das war mehr als 100" << Qt::endl;
}
else
{
out << "das war nicht mehr als 100" << Qt::endl;
}
}
Switch-Case
#include <QTextStream>
static QTextStream out(stdout);
static QTextStream in(stdin);
int main()
{
int eingabe;
out << "Gebe die Nummer eines Wochentages (1-7) ein:" << Qt::endl;
in >> eingabe;
switch (eingabe)
{
case 1:
out << "Montag" << Qt::endl;
break;
case 2:
out << "Dienstag" << Qt::endl;
break;
case 3:
out << "Mittwoch" << Qt::endl;
break;
case 4:
out << "Donerstag" << Qt::endl;
break;
case 5:
out << "Freitag" << Qt::endl;
break;
case 6:
case 7:
out << "Wochenende" << Qt::endl;
break;
default:
out << "Die Eingabe ist fehlerhaft" << Qt::endl;
}
}
Schleifen
While Schleife
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int zaehler=0;
while (zaehler < 5)
{
out << zaehler << Qt::endl;
zaehler=zaehler+1;
}
}
Do-While Schleife
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int zaehler=0;
do
{
out << zaehler << Qt::endl;
zaehler=zaehler+1;
}
while (zaehler < 5)
}
For Schleife
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
for (int zaehler=0; zaehler<5; zaehler++)
{
out << zaehler << Qt::endl;
}
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
for (int i=10; i>0; i-=2)
{
out << i << Qt::endl;
}
}
For Schleife mit Arrays und Listen
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int array[]={12,13,14,15};
for (int i=0; i<4; i++)
{
out << array[i] << Qt::endl;
}
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
int array[]={12,13,14,15};
for (int wert : array)
{
out << wert << Qt::endl;
}
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
QStringList liste={"Montag", "Dienstag", "Mittwoch", "Donnerstag",
"Freitag", "Samstag", "Sonntag"};
for (QString s : liste)
{
out << s << Qt::endl;
}
}
Schleifen abbrechen
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
QStringList liste={"Montag", "Dienstag", "Mittwoch", "Donnerstag",
"Freitag", "Samstag", "Sonntag"};
for (QString s : liste)
{
if (s=="Mittwoch")
{
continue;
}
out << s << Qt::endl;
}
}
Zeiger und Referenzen
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString x, QString y)
{
out << "x=" << x << Qt::endl;
out << "y=" << y << Qt::endl;
}
int main()
{
QString a("Hallo");
QString b=a;
ausgeben(a,b);
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString& x, QString& y)
{
out << "x=" << x << Qt::endl; // gibt Hallo aus
out << "y=" << y << Qt::endl; // gibt Hallo aus
}
int main()
{
QString a("Hallo");
QString& b = a;
ausgeben(a,b);
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString& x, QString& y)
{
out << "x=" << x << Qt::endl;
out << "y=" << y << Qt::endl;
y.append(" Welt!");
}
int main()
{
QString a("Hallo");
QString& b = a;
out << "a=" << a << Qt::endl;
out << "b=" << b << Qt::endl;
ausgeben(a,b);
out << "---------------" << Qt::endl;
out << "a=" << a << Qt::endl;
out << "b=" << b << Qt::endl;
ausgeben(a,b);
}
a=Hallo
b=Hallo
x=Hallo
y=Hallo
---------------
a=Hallo Welt!
b=Hallo Welt!
x=Hallo Welt!
y=Hallo Welt!
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString* x, QString* y)
{
out << "x=" << *x << Qt::endl;
out << "y=" << *y << Qt::endl;
y->append(" Welt!");
}
int main()
{
QString a("Hallo");
QString* b = &a;
ausgeben(&a,b);
ausgeben(&a,b);
}
#include <QTextStream>
static QTextStream out(stdout);
void ausgeben(QString* x, QString* y)
{
out << "x=" << *x << Qt::endl;
out << "y=" << *y << Qt::endl;
y->append(" Welt!");
}
int main()
{
QString a("Hallo");
QString* b = &a;
ausgeben(&a, b);
ausgeben(&a, b);
}
#include <QTextStream>
static QTextStream out(stdout);
int main()
{
QString a("Hallo");
out << "a=" << a <<Qt::endl;
QString* b=&a;
if (b!=nullptr)
{
out << "b=" << *b <<Qt::endl;
}
QString* c=nullptr;
if (c!=nullptr)
{
out << "c=" << *c <<Qt::endl;
}
}
New und Delete
#include <QTextStream>
static QTextStream out(stdout);
QString* erzeuge()
{
QString* s=new QString("Hallo");
return s;
}
void benutze(QString* was)
{
out << *was << Qt::endl;
}
int main()
{
QString* zeiger=erzeuge();
benutze(zeiger);
delete zeiger;
}
Klassen und Objekte
Attribute und Methoden
#include <QTextStream>
#include <QRandomGenerator>
static QTextStream out(stdout);
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
int wurf()
{
return 1 + generator.generate() % seiten; // Zufallszahl berechnen
}
};
int main()
{
Wuerfel w;
w.seiten=6;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
}
Konstruktoren
#include <QTextStream>
#include <QRandomGenerator>
#include <QDateTime>
static QTextStream out(stdout);
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
Wuerfel()
{
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
int wurf()
{
return 1 + generator.generate() % seiten; // Zufallszahl berechnen
}
};
int main()
{
Wuerfel w;
w.seiten=6;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
}
#include <QTextStream>
#include <QRandomGenerator>
#include <QDateTime>
static QTextStream out(stdout);
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
Wuerfel(int seitenZahl) : seiten(seitenZahl)
{
// alternativ: seiten=seitenZahl;
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
int wurf()
{
return 1 + generator.generate() % seiten; // Zufallszahl berechnen
}
};
int main()
{
Wuerfel w(6);
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
out << w.wurf() << Qt::endl;
}
Objekt-Instanzen
int main()
{
Wuerfel a(6); // ein sechs-seitiger Würfel
Wuerfel b(6); // ein sechs-seitiger Würfel
Wuerfel c(10); // ein zehn-seitiger Würfel
out << a.wurf() << Qt::endl;
out << b.wurf() << Qt::endl;
out << c.wurf() << Qt::endl;
}
Kopier-Konstruktor
int main()
{
Wuerfel a(6); // ein sechs-seitiger Würfel
Wuerfel b=a; // Kopie vom Würfel a
Wuerfel b(a) // Alternative Schreibweise, bewirkt das Gleiche
out << a.wurf() << Qt::endl;
out << b.wurf() << Qt::endl;
}
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
Wuerfel(int seitenZahl) : seiten(seitenZahl)
{
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
Wuerfel(Wuerfel& original)
{
seiten=original.seiten;
}
int wurf()
{
return 1 + generator.generate() % seiten;
}
};
#include <QTextStream>
#include <QRandomGenerator>
#include <QDateTime>
static QTextStream out(stdout);
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
Wuerfel(int seitenZahl) : seiten(seitenZahl)
{
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
Wuerfel(Wuerfel&) = delete;
int wurf()
{
return 1 + generator.generate() % seiten;
}
};
int main()
{
Wuerfel a(6);
Wuerfel b=a; // geht nicht mehr
}
Vererbung
#include <QTextStream>
#include <QRandomGenerator>
#include <QDateTime>
static QTextStream out(stdout);
class Wuerfel
{
private:
QRandomGenerator generator;
public:
int seiten;
Wuerfel(int seitenZahl) : seiten(seitenZahl)
{
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
Wuerfel(Wuerfel &) = delete;
int wurf()
{
return 1 + generator.generate() % seiten;
}
};
class FarbigerWuerfel : public Wuerfel
{
public:
QString farbe;
FarbigerWuerfel(int seitenZahl, QString dieFarbe) :
Wuerfel(seitenZahl), farbe(dieFarbe)
{}
};
int main()
{
FarbigerWuerfel w(6,"gelb");
out << w.wurf() << Qt::endl;
}
class FarbigerWuerfel : public Wuerfel
FarbigerWuerfel(int seitenZahl, QString dieFarbe)...
FarbigerWuerfel(int seitenZahl, QString dieFarbe) :
Wuerfel(seitenZahl), farbe(dieFarbe)
Getter und Setter
#include <QTextStream>
#include <QRandomGenerator>
#include <QDateTime>
static QTextStream out(stdout);
class Wuerfel
{
private:
int seiten;
QRandomGenerator generator;
public:
Wuerfel(int seitenZahl) : seiten(seitenZahl)
{
generator.seed(QDateTime::currentMSecsSinceEpoch());
}
void setSeiten(int seiten)
{
this->seiten=seiten;
}
int getSeiten()
{
return seiten;
}
int wurf()
{
return 1 + generator.generate() % seiten;
}
};
class FarbigerWuerfel : public Wuerfel
{
private:
QString farbe;
public:
FarbigerWuerfel(int seitenZahl, QString dieFarbe) :
Wuerfel(seitenZahl), farbe(dieFarbe)
{}
void setFarbe(QString neueFarbe)
{
farbe=neueFarbe;
}
QString getFarbe()
{
return farbe;
}
};
int main()
{
FarbigerWuerfel w(6,"gelb");
out << w.wurf() << Qt::endl;
out << QStringLiteral("Die Farbe des Würfels ist ")
<< w.getFarbe() << Qt::endl;
// Farbe und Seitenzahl ändern:
w.setFarbe("blau");
w.setSeiten(20);
out << w.wurf() << Qt::endl;
out << QStringLiteral("Die Farbe des Würfels ist ")
<< w.getFarbe() << Qt::endl;
}
Virtuelle Methoden
#include <QTextStream>
static QTextStream out(stdout);
class Mama
{
public:
virtual void einmal()
{
out << "Mama" << Qt::endl;
}
void dreimal()
{
einmal();
einmal();
einmal();
}
};
class Papa : public Mama
{
public:
virtual void einmal()
{
out << "Papa" << Qt::endl;
}
};
int main()
{
Papa p;
p.dreimal();
}
Destruktoren
#include <QTextStream>
static QTextStream out(stdout);
class Mama
{
public:
Mama()
{
out << "Mama wurde erzeugt" << Qt::endl;
}
virtual ~Mama()
{
out << "Mama wurde entfernt" << Qt::endl;
}
void lachen()
{
out << "hahaha" << Qt::endl;
}
};
class Papa : public Mama
{
public:
Papa()
{
out << "Papa wurde erzeugt" << Qt::endl;
}
virtual ~Papa()
{
out << "Papa wurde entfernt" << Qt::endl;
}
void furzen()
{
out << "brrrt..." << Qt::endl;
}
};
int main()
{
Papa* p=new Papa();
p->lachen();
p->furzen();
delete p;
}
Mama wurde erzeugt
Papa wurde erzeugt
hahaha
brrrt...
Papa wurde entfernt
Mama wurde entfernt
#include <QTextStream>
static QTextStream out(stdout);
class Mama
{
public:
Mama()
{
out << "Mama wurde erzeugt" << Qt::endl;
}
~Mama()
{
out << "Mama wurde entfernt" << Qt::endl;
}
void lachen()
{
out << "hahaha" << Qt::endl;
}
};
class Papa : public Mama
{
public:
Papa()
{
out << "Papa wurde erzeugt" << Qt::endl;
}
~Papa()
{
out << "Papa wurde entfernt" << Qt::endl;
}
void furzen()
{
out << "brrrt..." << Qt::endl;
}
};
int main()
{
Mama* m=new Papa();
m->lachen();
delete m;
}
Mama wurde erzeugt
Papa wurde erzeugt
hahaha
Mama wurde entfernt
Speicherverwaltung von Qt
MeinWidget::MeinWidget(QWidget* parent) : QWidget(parent)
{
QPushButton* button=
new QPushButton("Klick mich!",this); // QPushButton(text, parent)
connect(button, &QPushButton::clicked, this, &MeinWidget::angeklickt);
}
Signale in Qt
#ifndef MEINWIDGET_H
#define MEINWIDGET_H
#include <QWidget>
class MeinWidget : public QWidget
{
Q_OBJECT
public:
explicit MeinWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent* event);
signals:
void timeOver(); // Signal
public slots:
void quitGame(); // Slot
};
#endif // MEINWIDGET_H
MeinWidget quelle(...);
MeinWidget ziel(...);
connect(quelle, &MeinWidget::timeOver, ziel, &MeinWidget::quitGame);
Wie geht es weiter?
Dieses Buch wurde mir von mehreren Entwicklern empfohlen. Ich habe es selbst nicht
gelesen.
In Rezensionen wird immer wieder gelobt, dass der Autor komplexe Themen leicht verständlich
erklären kann. Diskussionsforen zufolge sind einige seiner Code-Beispiele jedoch schwer
nachvollziehbar, teilweise sogar fehlerhaft.
Von einem Professor in akademischem Stil geschrieben. Es eignet sich für Erwachsene mit
Vorkenntnissen. Ich mag beim diesem Buch, dass es auch die ganz neuen Sachen von C++ 17
lehrt.
Auch dieser Autor ist Professor mit entsprechendem Schreibstil. Er beschreibt die
Programmiersprache aus Sicht eines erfahrenen Softwareentwicklers, ganz ohne Eitelkeiten.