C++ Einführung
C++ Variablen im Speicher, Zeiger
C++ Texte etc.
C++ Dateien, Formatierte Ausgabe, ...
C++ OOP
Hilfe / Anhang
Downloads / Links
Partner
Fehler melden / Kontakt
Impressum
|
| Seite: [1] - Drucken |
Inhalt:
Durchatmen - Zusammenfassung des vierten Teils
| [drucken] |
Datum und Uhrzeit
Um die Zeit herauszufinden, kannst du wie folgt vorgehen:
//datumzeit.cpp //Gibt Datum&Zeit aus #include <iostream> #include <ctime> int main() { std::time_t zeit; std::tm *jetzt; zeit = std::time(0); jetzt = std::localtime(&zeit); std::cout << jetzt->tm_mday << '.' << jetzt->tm_mon+1 << '.' << jetzt->tm_year+1900 << " - " << jetzt->tm_hour << ':' << jetzt->tm_min << std::endl; return 0; }
"std::time_t" und "std::tm" dienen zum Speichern der Informationen! Sie kommen aus "ctime":
- "std::time_t" ist eigentlich ein "long"-Wert. In ihm werden die Sekunden seit dem 1.1.1970 gespeichert, wenn man die Funktion "time()" aufruft und den Rückgabewert übergibt. Alternativ kann auch die Addresse eines Zielobjekts (Typ: "std::time_t") übergeben werden (Call-by-reference!). Verwendet man "0" als Parameter, wird der Wert einfach zurückgegeben.
1179417811
Ist nicht der Code meines Panzerschranks, sondern der Wert von "zeit" zu diesem Augenblick. Dies hat sich allerdings auch soeben wieder geändert...
- "tm" ist eine Ansammlung von anderen Werten. Es handelt sich hierbei um integer, welche das Datum in für uns lesbarer Form speichern:
- Tag
- Monat
- Jahr
- Stunde
- Minute
- Sekunde
- Wochentag
- Sommerzeit?
Um aus unserer tollen Zahl ein solches Datum zu erzeugen, müssen wir nur die Funktion "localtime()" aufrufen. Als Parameter wird die Zeit als "time_t" übergeben, zurück wird ein "tm" gegeben. Nun können die Werte ausgelesen werden:
1179417811
17.5.2007 - 18:3 //3 Minuten nach sechs...
Wichtig ist das folgende:
| Was? |
Bemerkungen |
Wert |
Name der Variable |
| Tag |
des Monats |
1-31 |
tm_mday |
| Monat |
im Jahr |
0-11 |
tm_mon |
| Jahr |
seit 1900 |
--- |
tm_year |
| Tage seit Sonntag |
Wochentag |
0-6 |
tm_wday |
| Tage seit Neujahr |
--- |
0-365 |
tm_yday |
| Stunden |
--- |
0-23 |
tm_hour |
| Minuten |
--- |
0-59 |
tm_min |
| Sekunden |
--- |
0-59 (61) |
tm_sec |
| Sommerzeit |
--- |
s.u. |
tm_isdst |
Die Pfeile ("->") geben an, dass die Eigenschaft ausgelesen werden soll. Normalerweise verwendet man Punkte ("."), da wir aber mit einem Zeiger arbeiten sind die Pfeile nötig. (mehr dazu im Kapitel über OOP!)
namespaces - Namensräume
Du erinnerst dich an das erste Programm, in dem wir einen Text ausgegeben haben? Wir haben "std::cout" benutzt. Wir hätten ebenso "cout" nutzen und "using namespace std;" unter die "#include"-Zeile(n) schreiben können:
using namespace std; |
= |
//using namespace std; |
cout << "bla"; |
std::cout << "bla"; |
Namespaces wurden eingeführt, um Namenskonflikte zu verhindern. So kann man seine Funktionen nennen wie man will, ohne Angst haben zu müssen, dass es eine Funktion mit diesem Namen bereits gibt.
Daher sollte man "using namespace std;" eigentlich nicht nutzen. Aber man kann (und sollte bei größeren Projekten) auch eigene Namespaces definieren: Dies läuft genauso ab, wie beim schreiben von Funktionen - aber ohne runde Klammern:
Beispiel
Unser Beispiel-Namespace enthält eine Variable, welche wir ebenfalls außerhalb des Namespaces angelegt haben:
//namespaces.cpp // Erklärt, was Namespaces sind, und wie man sie nutzt #include <iostream> //KEIN USING NAMESPACE STD int i; //globale Variable namespace nasenbaer { int i; //Variable gehört zum Namespace "Nasenbaer" void sages() { ::std::cout << "Test!\n"; //::std::cout bedeutet: // "gehe in den obersten Namespace, // suche NS "std", // suche "cout"... } } int main() { ::i=3; //"::" ist der oberste NS! // Kann hier auch weggelassen werden! ::Nasenbaer::i=5; ::Nasenbaer::sages(); ::std::cout << " " << ::i << "<->" << ::Nasenbaer::i; ::std::cout << "\nEnde..." << std::endl; return 0; }
Abschluss
Während es bei kleinen Programmen oder Beispielen wirklich egal ist, ob du namespaces nutzt, solltest du es bei größeren Projekten tun! Ansonsten bieten Anweisungen wie "using std::cout" eine Alternative:
Hier wird nicht der gesamte namespace, sondern nur die Funktion "cout" einbezogen!
Zufallswerte und Zufallszahlen
C++ bietet einen Zufallszahlengenerator. Für die Meisten Fälle reicht dieser aus, ansonsten kann man sich auch mit denen der "Boost"-Bibliothek behelfen. Dazu später mehr!
rand()
Diese Funktion findet sich in "<cstdlib>" und liefert einen Zufallswert von 0 bis "RAND_MAX". Dieser "Zufallswert" wird über eine komplexe Formel berechnet. Daher handelt es sich auch immer um die gleichen Zahlen in der gleichen Reihenfolge:
//zufall1.cpp //Gibt "Zufallszahlen" aus... #include <iostream> #include <cstdlib> int main() { std::cout << "Zufallszahlen?\n"; std::cout << "\"RAND_MAX\": " << RAND_MAX << "\n\n"; std::cout << "Zahl 1: " << std::rand() << "\n"; std::cout << "Zahl 2: " << std::rand() << "\n"; std::cout << "Zahl 3: " << std::rand() << "\n"; return 0; }
srand()
Mit dieser Funktion kannst du den Zufallszahlengenerator initialisieren! Dies geschieht genau einmal am Anfang des Programms. Die Funktion erwartet einen "unsigned int" als Parameter. Hier kann man z.B. die Zeit übergeben:
//zufall2.cpp //Gibt Zufallszahlen aus... #include <iostream> #include <cstdlib> #include <ctime> int main() { std::srand(std::time(0)); std::cout << "Zufallszahlen!\n"; std::cout << "\"RAND_MAX\": " << RAND_MAX << "\n\n"; std::cout << "Zahl 1: " << std::rand() << "\n"; std::cout << "Zahl 2: " << std::rand() << "\n"; std::cout << "Zahl 3: " << std::rand() << "\n"; return 0; }
Solange man das Programm nicht innerhalb einer Sekunde mehrfach aufruft,
bekommt man nun die gewünschten Zahlen! (daher auch nur 1 Aufruf
von "srand()")!
Einen Würfel simulieren
Um einen Würfel zu simulieren, wollen wir einfach mal Zufallszahlen zwischen 1 und 6 ausgeben... Klingt einfach, ist einfach:
- Zufallszahlen initialisieren (mit "srand" und "time")
- Zufallszahl erzeugen
- Wert zwischen 0 und "RAND_MAX" so umformen, dass man eine Zahl von 1 bis 6 bekommt...
In C++ sieht das folgendermaßen aus:
Dabei wird ausgenutzt, das der Rest beim Teilen durch 6 (Stichwort: "Modulo"/"%") zwischen 0 und (6-1)=5 liegt. Einfach noch eins addieren und fertig:
int augen = rand()%6+1;
Dateien
Wenn man Variablen oder Einstellungen auch nach dem Ende des Programmablaufs behalten möchte, muss man diese auf der Festplatte speichern. Dies geschieht in Form von Dateien. Dabei muss man zwischen Text- und Binärdateien unterscheiden, wobei erstere portabler sind als binäre und sich auch von Hand anpassen lassen.
Um mit Dateien arbeiten zu können, muss man "<fstream>" inkludieren. Danach stehen folgende Datentypen zur Verfügung:
- ifstream (in Datei schreiben)
- ofstream (aus Datei lesen)
- fstream (lesen und schreiben)
Ein Handle
Um mit einer Datei arbeiten zu können, benötigen wir ein Dateihandle. Ein Handle ist eine Variable, die die Datei darstellt. Dieses Handle kann nun entweder vom Typ ifstream, ofstream oder fstream sein!
#include <fstream> /*...*/ std::fstream fdh; //Dateihandle erstellen std::ifstream idh; //Dateihandle erstellen std::ofstream odh; //Dateihandle erstellen
Datei öffnen
Nun können wir diesem Handle eine Datei zuweisen:
dh.open("test.txt", /*wie?*/);
Für "wie" können wir nun folgende Werte einsetzen:
| lesen |
ios::in |
| schreiben |
ios::out |
| Inhalt anhängen |
ios::app |
| Datei vorher löschen |
ios::trunc |
| Cursor ans Ende setzen |
ios::ate |
| Binäre (Daten-) Datei |
ios::binary |
Mehrere dieser Eigenschaften kann man mit "|" verknüpfen. Um also eine Datei zum Schreiben zu öffnen, etwa ein Logfile, das zu Beginn geleert werden soll, müsste man schreiben:
dh.open("logfile", std::ios::out|std::ios::trunc); //Alternativ: ofstream log("test.log", std::ios::out|std::ios::trunc);
Der zweite Parameter darf entfallen. Wird er nicht angegeben, werden die Standardwerte genutzt. Diese sind, je nach Typ des Handles:
| ifstream | ios::in |
| ofstream | ios::out |
| fstream | ios::in|ios::out |
Nun sollte man noch überprüfen, ob die Datei erfolgreich geöffnet wurde. Dies geschieht am Besten durch:
if ( dh.is_open() ) { //Alles OK! }
Datei lesen
Um eine Datei lesen zu können, solltest du natürlich "ios::in" verwenden. Danach kannst du den Inhalt mit den folgenden Funktionen problemlos auslesen. Dabei musst du dir vorstellen, dass du einen Cursor in der Datei bewegst, fast wie in einem Editor, der die Stelle angibt, an der du gerade liest! (Zum Ändern der Cursor-Position gleich noch mehr!)
| Funktion |
Rückgabe |
Parameter |
Bemerkung |
| dh.get() |
char |
- |
Liest ein Zeichen |
| dh.getline(char*,int) |
- |
wohin und wie viele Zeichen maximal? |
Eine Zeile, oder max. Anzahl; liest auch Leerzeichen ein |
| dh >> string |
- |
- |
String wird gelesen, aber nur bis zum nächsten Leerzeichen/Zeilenumbruch (wie "cin") |
Datei schreiben
Nachdem du die Datei mit "ios::out" geöffnet hast, kannst du mit den folgenden Funktionen in sie schreiben. Auch dabei musst du dir einen Cursor denken, der eine Position in der Datei angibt, an der du schreibst!
| Funktion |
Parameter |
Bemerkung |
| dh.put(char) |
Zeichen |
Ein Zeichen |
| dh << string |
- |
String wird geschrieben, auch Leerzeichen (Wie "cout")! |
Datei schließen
Um eine Datei zu schließen, und das Handle wieder gebrauchen zu können, muss man:
aufrufen. Nun wird die Datei auf die Festplatte geschrieben (Vorher wurde sie zum Schnellzugriff im RAM (Arbeitsspeicher) behalten). Um dieses Verhalten hervorzurufen, kann man auch:
schreiben. Hierbei wird nur die Datei auf die Festplatte gebannt, dass Handle bleibt offen!
Den "Cursor" setzen - sich in der Datei orientieren
Um sich in einer Datei zu orientieren gibt es die folgenden Möglichkeiten. Dabei muss man zwischen Aus- und Eingabecursor unterscheiden!
seekg(int); //[ifstream] setzt den Cursor seekp(int); //[ofstream] int i=tellg(); //[ifstream] liefert die Position zurück int i=tellp(); //[ofstream]
Die "seek"-Funktionen können außerdem einen weiteren Parameter haben (standardmäßig "ios::beg"), der angibt, von welchem Punkt ausgegangen werden soll. Von diesem Punkt wird dann der erste Parameter weitergezählt und der Cursor gesetzt!
| Parameter |
Position, von der gelesen wird |
| ios::beg |
Dateianfang |
| ios::cur |
aktuelle Position |
| ios::end |
Dateiende |
EOF - End of file und andere Flags
Flags sind "bool"-Werte, die den Status ("Flags") beschreiben. Mit den folgenden "Funktionen" kannst du die Dateiarbeiten überwachen:
| dh.bad() |
Gibt "true" zurück, wenn das Schreiben fehlschlägt, etwa weil keine Datei geöffnet ist oder kein Platz mehr vorhanden ist! Ist alles OK, wird "false" zurückgegeben. |
| dh.fail() |
Wie "bad()", aber gibt ebenfalls "true" zurück, wenn wir z.B. eine Zahl einlesen wollen, dort aber ein Buchstabe steht... Bei "false" ist alles OK. |
| dh.eof() |
Gibt "true" zurück, wenn das Dateiende erreicht wurde. |
| dh.good() |
Das wichtigste so genannte "Flag" (Alles in dieser Tabelle sind "Flags"): Gibt "true" zurück, wenn alle anderen oben genannten Werte "false" sind, also alles OK ist! Oft wird "while(dh.good())" geschrieben, um eine Datei auszulesen... |
Binäre Dateien
Da binäre Dateien nicht wie Textdateien schön formatiert sind, kommt man mit den oben genannten Methoden nicht weit. Zum Lesen oder Schreiben brauchen wir etwas neues: (Datei muss mit "ios::binary" geöffnet sein!)
Wie du weißt, hat ein "char" eine Größe von genau einem Byte. Eine Binärdatei kann sehr einfach und "byteweise" gelesen werden. Dazu benötigen wir die beiden folgenden Funktionen:
read(wohin, wieviel); write(woher, wieviel);
"wohin" und "woher" sind Zeiger auf "char", oder der Name eines char-Arrays, wenn wir mehr als ein Byte einlesen wollen. "wieviel" ist dann die Größe in Byte!
Textdateien
Textdateien sind alle Dateien, die wir ohne "ios::binary" öffnen. Hier werden also Zeichen bereits interpretiert, etwa "\n" und Co. Die Dateien enthalten ASCII-Zeichen o.ä.
//textfile.cpp // Beispiel zu Textdateien #include <iostream> #include <fstream> int main() { std::ifstream idatei; std::ofstream odatei; std::string zeile; //---------------------------------------------------------------------------- // SCHREIBEN: "test.txt" //---------------------------------------------------------------------------- odatei.open("test.txt"); //Abbrechen, wenn Fehler: if (!odatei.good()) { std::cout << "Fehler: Datei kann nicht geoeffnet werden!" << std::endl; return -1; } odatei << "Zeile 1\n"; odatei << "Zeile 2\n"; odatei << "Zeile x\n"; odatei.close(); //---------------------------------------------------------------------------- // LESEN: "test.txt" //---------------------------------------------------------------------------- idatei.open("test.txt"); //Abbrechen, wenn Fehler: if (!idatei.good()) { std::cout << "Fehler: Datei nicht gefunden!" << std::endl; return -2; } while (idatei.good()) { std::getline(idatei, zeile); //Eine Zeile in "zeile" speichern... std::cout << zeile << std::endl; // ...und ausgeben } idatei.close(); return 0; }
>Binärdateien
Binäre Dateien lesen wir mit "read()" und schreiben sie mit "write()". Ich hoffe, das folgende Beispiel verdeutlicht das. Du wirst direkt erkennen, das es etwas komplizierter ist, und du Pointer kennen solltest:
//binfile.cpp // Beispiel zu Binärdateien #include <iostream> #include <fstream> int main() { std::ifstream idatei; std::ofstream odatei; std::ifstream::pos_type size; char* im_ram; //Ein Pointer auf ein char(-Array) //---------------------------------------------------------------------------- //Eine Binärdatei einlesen, // im RAM zwischenspeichern (komplett) // und Kopie anlegen //---------------------------------------------------------------------------- idatei.open("test.exe", std::ios::in|std::ios::binary|std::ios::ate); //Halt irgendeine Datei... (Wir beginnen am Ende ("ate") if (idatei.good()) { size=idatei.tellg(); //Größe ermitteln... std::clog << "Datei \"test.exe\" ist " << size << "Byte gross!" <<std::endl; im_ram=new char[size]; //Genug Arbeitsspeicher reservieren idatei.seekg(0, std::ios::beg); //An den Anfang... idatei.read(im_ram, size); if (idatei.good()) std::clog << size << " Byte gelesen!" << std::endl; else std::clog << "Fehler: 0 Byte gelesen!" << std::endl; idatei.close(); odatei.open("test2.exe", std::ios::out|std::ios::binary|std::ios::trunc); if (odatei.good()) { odatei.write(im_ram,size); if (odatei.good()) std::clog << size << " Byte geschrieben!" << std::endl; else std::clog << "Fehler: 0 Byte geschrieben!" << std::endl; odatei.close(); } else std::clog << "Fehler: Ausgabedateifehler!" << std::endl; } else std::clog << "Fehler: Eingabedateifehler!" << std::endl; return 0; }

Ein weiteres Beispiel folgt im Kapitel über die OOP, wenn wir große Mengen an Daten auf einmal speichern!
 | Leider kann man nicht immer eine Binärdatei nehmen, da diese nicht von einem Rechner auf einen anderen Rechner übertragen werden können, falls es sich um verschiedene Systeme handelt. Obwohl es meist keine Probleme gibt, kann es bei Systemen mit "Little-Endian" und Systemen mit "Big-Endian" Probleme geben, wenn man Dateien übernehmen möchte... (siehe auch: http://de.wikipedia.org/wiki/Byte-Reihenfolge) |
Dateigröße ermitteln
Um die Größe zu ermitteln, gehen wir zuerst an den Dateianfang, dann ans Dateiende, und gucken dann, wie viele Bytes dazwischenlagen...:
//filesize.cpp // Gibt die Groesse einer Datei aus... #include <iostream> #include <fstream> int main() { long anfang, ende; std::ifstream datei("test.txt"); //Abbrechen, wenn Fehler: if (!datei.good()) { std::cout << "Fehler: Datei nicht gefunden!" << std::endl; return -1; } //Jetzt Groesse bestimmen: anfang=datei.tellg(); //Der Dateianfang datei.seekg(0,std::ios::end); //Ans Ende gehen ende=datei.tellg(); //Das (Datei-)Ende! datei.close(); std::cout << "Datei \"test.txt\" ist " << (ende-anfang) << "Byte gross!" << std::endl; return 0; }
Formatierte Ausgaben
Um Ausgaben mit "cout" ein wenig zu formatieren, gibt es eine weitere Erweiterung, die Datei "<iomanip>". Also einfach direkt unter "iostream" einbinden! Nun können wir mit den folgenden Kommandos die Ausgabe beeinflussen:
| Wie? |
Was? |
setw(int breite) |
setzt die minimale Größe der direkt folgenden Ausgabe |
setprecision(int praez) |
Gibt die Zahl der Stellen für Festkomma- oder Gleitkommadarstellung hinter dem Komma an, ansonsten die Maximalzahl der Stellen. |
setiosflags |
s.u. |
resetiosflags |
s.u. |
Nun zu den "flags":
| Flag |
Gruppe |
Was? |
left |
adjustfield |
links ausrichten |
right |
adjustfield |
rechts ausrichten |
internal |
adjustfield |
+/- links und Zahl rechts |
dec |
basefield |
Zahlen dezimal ("normal") ausgeben |
hex |
basefield |
Zahlen hexadezimal ("Sechzehnersystem") ausgeben |
oct |
basefield |
Zahlen oktal ("Achtersystem") ausgeben |
showbase |
|
zeigt Dezimalbasis von hex- und oct-Zahlen |
showpos |
|
"+" vor positive Zahlen |
uppercase |
|
Großschreibung |
fixed |
floatfield |
Festkommazahl |
scientific |
floatfield |
wissenschaftliche Darstellung |
Anwendung
Die folgenden Zeilen:
std::cout << std::setfill (' ') << std::setw (10) << std::setprecision (5); std::cout << 1234.5678;
führen zu einer Ausgabe von "____1234.5" ("_"=Leerzeichen!)
Um Flags zu nutzen, musst du sie wie folgt aktivieren:
std::cout.setf (std::ios::/*Flag*/, std::ios::/*Gruppe*/) std::cout << std::setiosflags (std::ios::/*Flag*/, std::ios::/*Gruppe*/)
Das Deaktivieren geht wie folgt:
std::cout.unsetf (std::ios::/*Flag*/) std::cout << std::resetiosflags (std::ios::/*Flag*/) //(ohne Gruppe) std::cout.unsetf (std::ios::/*Gruppe*/) std::cout << std::resetiosflags (std::ios::/*Gruppe*/) //Alle Flags einer Gruppe auf Standartwerte
Callbacks (Funktionszeiger)
Einen Funktionszeiger legst du an, indem du den Funktionsnamen ohne Klammern ausliest: (aus "beenden()" wid "beenden")
Ein Beispiel
//callback.cpp // Demonstriert den Einsatz von Funktionszeigern #include <iostream> #include <cmath> void ausgeben(); void beenden (); double wurzel(int,void bei_fehlern()); //Für reelle Zahlen int main() { //Wurzel aus "-1" ist nicht reell: wurzel(-1, ausgeben); //Bei Fehler: Einfach ausgeben wurzel(-1, beenden ); //Bei Fehler: Beenden return 0; } void ausgeben() { std::cout << "Es ist ein nicht fataler Fehler aufgetreten!" << std::endl; } void beenden() { std::cout << "Es ist ein fataler Fehler augetreten!" << std::endl; std::exit(-1); } double wurzel(int wovon, void bei_fehlern()) { if (wovon<0) bei_fehlern(); //Aua! else return std::sqrt(wovon); }
 | author@laptop:~/cpp$ ./callback
Es ist ein nicht fataler Fehler aufgetreten!
Es ist ein fataler Fehler augetreten!
author@laptop:~/cpp$ |
Mehr Informationen: "http://www.newty.de/fpt/index.html" (Englisch)
|
| [drucken] |
Keine Beispielprogramme
Hier stehen diesmal keine Programme, die zeigen, was wir schon gelernt haben:
Ich denke wir hatten bereits genug Beispiele in den einzelnen Tutuorials...
Ein paar Aufgaben
Wenn du die Aufgaben lösen kannst, solltest du dieses Kapitel verstanden haben...
- Schreibe ein Programm, dass eine Textdatei anlegt, den Namen des Nutzers darin speichert, und ihn nur danach fragt, wenn die Datei nicht existiert... Danach soll er begrüßt werden!
- Schreibe nun mehrere Phrasen, mit denen der Nutzer begrüßt werden kann, etwa "Hallo " oder "Guten Tag ". Der Nutzer soll nun eine zufällige Begrüßung sehen! Profis unterscheiden auch nach Tageszeit! (Tipp: Arrays aus "std::strings")
- Erweitere das Programm um die Möglichkeit, den Namen und das Alter rechtsbündig auszugeben. Profis speichern den Geburtstag ab und rechnen schnell das Alter aus...!
Wie geht's weiter?
Glückwunsch! Du hast Kapitel 4 absolviert! Eigentlich kannst du jetzt schon alles, was du für Programme brauchst, aber mit der OOP kannst du im nächsten Kapitel ein weitere riesiges Konzept kennen lernen, was du SEHR oft brauchen und vorfinden wirst! Sobald du dich ein wenig damit auskennst, kannst du anfangen, graphisch zu Programmieren: Fenster, Simulationen oder Spiele! Wir sehen uns im nächsten Kapitel!
|
| Seite: [1] - Drucken |
|