In einer Euphorie-Welle kann ich nun endlich sagen:
Unser Programm macht nun endlich das, was es soll: Funktionieren. Wie wir zu diesem erstaunlichen Ergebnis gekommen sind, werde ich nun etwas näher schildern:
Uns hat vor allem eines geholfen: eine LOG - Funktion.
Diese hat die Aufgabe, jeden Funktionsaufruf mitzukommentieren und uns somit die Fehlersuche und das anschließende Debuggen erheblich zu erleichtern. Natürlich hat das Mitloggen auch viel Zeit gekostet, jedoch sind wir auf unsere Rechnung gekommen, da das Programm zeitgerechter einwandfrei funktionierte. Somit kann ich behaupten, ein kompilierendes Programm auf meinem Computer zu haben! (welches keine valgrind Fehler verursacht)
Mit dem wunderschönsten Designdokument-Deckblatt der Welt und einem funktionierendem Programm (sogar die Zusatzpunkte sind implementiert!) unter dem Arm kann ich nun etwas ausgelassener der Abgabe entgegen eifern!
In der Hoffnung, nichts bei der Abgabe vergessen zu haben,
wünsche ich euch allen viel Erfolg beim Abgabegespräch und bei der (hoffentlich) anschließenden Prüfung!
Irgendwie etwas umständlich, wenn man sich darum kümmert, dass alles auf NULL, 0 bzw. das dementsprechende gesetzt wird. Es geht auch einfacher:
Konstruktor(): a_pointer(), an_int(), ...
Entspricht genau dem obigen, nur dass hier der Compiler entscheidet, was das jeweilige "0" ist.
auto_ptr
In diesem Blogeintrag möchte ich über auto_ptr schreiben. auto_ptr haben sehr viele Vorteile aber auch einige Nachteile.
Ein auto_ptr lässt sich fast gleich wie ein normaler Pointer verwenden, nur dass man sich um die Speicherfreigabe nicht mehr kümmern muss und auto_ptr nur einen Besitzer haben können, doch dazu später.
Zur Verwendung:
als Membervariable:
.h:
std::auto_ptr auto_pointer; //Anstelle von int können beliebige andere Typen genommen werden
.cpp:
in der Initialisierungsliste Konstruktor(): auto_pointer(new int (3)) ...//anstelle von new int kann man auch eine bestehenden Pointer auf ein int zuweisen
Zuweisung auto_pointer = std::auto_ptr(new int(3)); oder auto_pointer.reset(new int(3)); //zu reset mehr unter "Ein Besitzer?"
Das folgende ist hingegen nicht möglich: int *normal_pointer; normal_pointer = new int(3); auto_pointer = normal_pointer; //wird mit einem Kompilierfehler quitiert delete normal_pointer;
Umgang
Wie bei normalen Pointern lassen sich * und -> verwenden, hierbei gibt es keine Unterschiede. Will man den Pointer auf das Objekt selbst haben, so erhält man das mit .get() in unserem Fall also würde normal_pointer = auto_pointer.get(); funktionieren. Hier ein paar kleine Beispiele zum Umgang mit auto_ptr:
std::auto_ptr<Dice> a_dice(new Dice(NULL)); int result = a_dice->roll();
Kein delete ist mehr nötig, selbst bei exceptions nicht, auto_ptr verhält sich hier wie eine "normale" lokale Variable bzw. Membervariable. Erstellt auto_ptr ein Objekt einer Klasse in der selbst mit new Speicher angefordert (nicht in Form von auto_ptr) wird kümmert sich auto_ptr nicht um dessen Freigabe, d.h. im Destruktor dieser Klasse muss weitehin delete vorkommen etc.
Ein Besitzer?
Vorhin habe ich gemeint, dass ein auto_ptr nur einen Besitzer haben kann. Um das zu verstehen zeige ich, wie sicher viele von euch Pointer verwendet haben:
Field *field = board_->getField(31); //wenn getField einen Pointer zurückliefert
im Prinzip macht man da folgendes, man weißt einen Pointer einen anderen Pointer zu:
int *temp = new int(3); int *pointer = temp; delete temp;
Hier zeigen sowohl temp als auch pointer auf die gleiche Adresse. auto_ptr hingegen können nicht auf die gleiche Adresse zeigen (zumindest in der oben gezeigten Form), denn wer sollte zum Schluss den Speicher freigeben?
pointer hat nun die Adresse von dem erstellten Integer gespeichert, aber was ist mit temp? temp zeigt auf nichts mehr (NULL), es hat das "Eigentum" des Pointers an pointer weitergegeben ("transfer of ownership" auf Englisch). Soetwas kann schnell einmal zu Problemen führen, deswegen als Faustregel sollte man keine auto_ptr anderen auto_ptr zuweisen. Erstellt nur einmal einen auto_ptr und sonst greift direkt mit .get() auf den Pointer zu:
std::auto_ptr<int> temp(new int(3)); int *pointer; pointer = temp;.get();
Hier habe ich einen Integer auto_ptr mit dem Wert 3 erstellt. In der darauffolgenden Zeile weise ich temp einen anderen auto_ptr mit dem Wert 70 zu, ohne vorher den zuvor allokierten Speicher freigegeben zu haben, denn das macht auto_ptr selbst. Der angeforderte Speicher für "new int(3)" wird automatisch freigegeben. Das ist praktisch, wenn eine Methode/Funktion die Speicher anfordert mehrmals aufgerufen wird. Mit Pointern müsste man selbst bei jeden Aufruf dafür sorgen, dass der Speicher freigegeben wird bevor neuer angefordert wird, auto_ptr übernehmen das automatisch.
Funktionalität in diesem Zusammenhang
das oben schon erwähnte .get()
.relase() gibt das Eigentum auf und gibt einen Pointer auf das Objekt zurück, also ähnlich wie .get(), nur dass auto_ptr danach auf NULL zeigt.
.reset(a_ptr) gibt den bisher verwendeten Speicher frei und weist a_ptr dem auto_ptr zu
Feinheiten
Bis jetzt klingt ja alles recht toll, doch einen sehr großen Nachteil haben auto_ptr:
auto_ptr kann man nicht in containern verwenden!!!
Probiert es gleich gar nicht, dass führt nur zu Problemen und ist nicht vorgesehen.
euer LV-Leiter Christian Safran befindet sich gerade im Ausland und hat uns gebeten euch die Testfiles für das Ex3 umgehend zur Verfügung zu stellen. Da wir keinen Zugriff auf den Institutswebserver haben, erfolgt das nun interimsmäßig über meinen Blog - etwaige Updates der Testfiles werden dann aber nur mehr auf der offiziellen Webseite [1] veröffentlich.
Im Ex3 müssen wir den Spielstand in eine Datei speichern und ihn aus einer solchen auch wieder laden. Die Wahl für das Dateiformat fiel dabei sehr schnell auf XML, da es universell einsetzbar und plattformübergreifend mit einem einfachen Texteditor menschenlesbar bearbeitet werden kann.
Als noch nicht so erfahrener Programmierer wird man vielleicht dazu tendieren, dass man die zu speichernden Daten alle nacheinander in einer bestimmten Reihenfolge in eine Datei schreibt und sie nachher in derumgekehrten Reihenfolge wieder einliest. Diese Methode ist zwar scheinbar die einfachste, hat jedoch ein paar gravierende Nachteile:
Sollte man an dem Dateiformat irgendwas ändern, und sei es nur das hinzufügen oder entfernen einer Zahl, so werden alle früher geschriebenen Dateien nutzlos, da sie nicht mehr lesbar sind.
Die Daten sind nicht, oder nur schwer von Menschen les- und veränderbar, da die Datei nur aus einem langen Strom von Daten besteht.
Dadurch dass man praktisch hardgecodete Schreib- und Leseroutinen schreiben muss, die meistens nur durch fehleranfälliges copy & paste wiederverwendbar sind, kann man diese Routinen meistens nicht in andere Programme einbauen, sondern muss dafür wieder neue Funktionen schreiben,
Wir haben uns für Xml entschieden, weil es die soeben aufgelisteten Nachteile nicht hat, und nachdem man einmal einen funktionierenden Parser geschrieben hat leicht auch für andere Sachen verwenden kann. Ich habe jetzt einen Parser geschrieben, der ein komplettes Xml Dokument in einen Baum (DOM) einliest, auf den man nachher einfach mit verschiedenen Klassen zugreifen kann.
Dadurch ist zusätzlich zum einfachen Zugriff auch ein einfaches Speichern möglich, in dem man den Baum einfach "händisch" aufbaut, und speichert.
Ein weiterer Vorteil von Xml ist, dass es standardisiert ist, und somit systemunabhängig festgelegt ist, wie ein Xml Dokument aufgebaut sein soll. Weiters kann man Xml Dateien mit jedem Browser anschauen und mit jedem Texteditor bearbeiten, und so einfach auch händisch Dateien erzeugen, bearbeiten und betrachten.
Zum Schluss noch ein kleines Beispiel für eine Xml Datei:
Wieman erkennen kann gibt es verschiedene Arten Daten in Xml zu speichern, wobei die hauptsächliche Verwendung von Attributen für Daten die Variante mit dem wenigsten overhead ist. Das ist eigentlich der einzige Nachteil von Xml, dass durch die "Datenbeschriftung" ein gewisser overhead entsteht, der aber meistens nicht wirklich ins Gewicht fällt. Wem das zu viel ist der sollte einmal nach Binary Xml suchen, was aber für unser Monopoly sicher nicht erforderlich ist.
2. Abgabegespräche vorbei und alle sind glücklich und zufrieden.
Alle....? Zumindest die meisten.
Aller begangenen Fehler zum Trotz muss ganz am Anfang einmal festgehalten werden, dass mir jede einzelne(!) Gruppe erfolgreich bewiesen hat, dass sie in der Lage ist, das Beispiel in Teamarbeit erfolgreich zu lösen. Erfolgreich deswegen, weil ihr alle habt es geschafft ein Monopoly zu implementieren das grundsätzlich den Anforderungen des Ex2 entspricht - das alleine verdient schon einmal ein Lob, weil glaubt mir, da hab ich auch schon ganz anderes gesehen.
Da es bei einigen dann in der Bewertung dann doch nicht ganz soo rosig ausfiel kann auf 2 prinzipielle Gründe reduziert werden:
* fehlende Genauigkeit und * Zeitdruck
Tutini...so viel Lob ich für euch hatte, ich war auch ein bisschen enttäuscht von euch...ich weiß ihr habt viel zu tun ganz abgesehen von SEP, aber mehr als die Hälfte der Gruppen hat zwischen 5 und 12 Punkten einfach weggeworfen weil kurz vor der Abgabe noch irgendwelche Änderungen gemacht wurden, die dann einen anderen Teil des Programms negativ beeinflusst haben.
Jeden Fehler muss man einmal gemacht haben um daraus zu lernen...?! Das ist schlimm und meistens wahr - aber muss es denn wirklich immer so sein....!?! Sagt nicht, ich hätte euch nicht beides gesagt...in jedem Tutorium....! ("Viel Testen und lest euch das URD ganz genau durch" waren da meine mahnenden Worte)
So, schluss jetzt mit der Predigt, konstruktiver sind da schon folgende Hinweise:
Ich weiß, nein, ich bin mir sicher, dass ihr das ALLE draufhabt, beim letzten Beispiel nicht mehr als sagen wir 5 Punkte abgezogen zu bekommen. Das ist für JEDE GRUPPE schaffbar. Dazu müsst ihr euch aber folgende Dinge ganz ganz fest merken und zu Herzen nehmen:
* Wenn ihr nicht schon begonnen habt, beginnt HEUTE mit dem Beispiel! Weil nur so könnt ihr vermeiden, dass ihr in der letzten Minute noch irgendwas ins Programm hacken müsst.
* Programmiert Testfall-orientiert Auch in euren weiteren Softwareentwicklungsprojekten wird es wichtig sein die Funktionsweise eurer Software zu verifizieren. Je unaufwändiger und schneller diese Verifikation durchgeführt werden kann, desto öfter werdet ihr sie auch verwenden und desto schneller findet ihr auch Fehler in eurem Programm. Ich kann und will euch nicht dazu verpflichten, aber das beste was ihr dazu machen könnt ist, dass ihr max. eine 1/2 stunde aufwendet und in eurem makefile ein neues Target hinzufügt, dass die Testcases automatisch ausführt und mit den Musterlösungsoutputs difft. Idealerweise ruft ihr also nur soetwas wie "make tests" auf und seht dann am diff-output ob euer Programm wie gewünscht funktioniert. Es gibt unterschiedliche Methoden das zu realisieren, ein Kollege von euch hat euch in diesem Block schon einen Hinweis gegeben, aber auch die rudimentärste Implementierung würde den meisten schon viel helfen...:
test: $(TITLE) make all echo "---- Test 1 ----" ./$(TITLE) < test_files/tests/selftest1.in > out diff test_files/tests/outfiles/selftest1.out out
....
Glaubt mir, die Arbeit die ihr da hineinsteckt wird sich 10-fach bezahlt machen! und wichtig: vergesst nicht in die Newsgroup zu schauen - da werden einige eurer kollegen sicher wieder testcases posten.
* Vertraut nicht eurem Auge, vertraut dem Computer Das ist eine Erweiterung des vorherigen Punkts. Ganz akutes Beispiel: Verwendet Valgrind! Einige Gruppen hatten u.a. durch ihre late-hacks speicherverwaltungsfehler drin, die das ganze Programm unbrauchbar machten. Auch hier wieder der Appell - erstellt euch ein Target im Makefile! Wie das geht könnt ihr euch entweder selber überlegen oder einfach aus den Folien des 2. Tutoriums entnehmen.
* Behaltet das Bewertungsschema IMMER im Hinterkopf Lernt es auswendig, kontrolliert unbedingt vor jeder Abgabe ob ihr euch auch wirklich sicher seid, dass ihr jeden einzelnen Punkt erfüllt und überlegt euch bei jeder Zeile, die ihr schreibt, ob ihr nicht vielleicht gerade einen Blödsinn macht. Es gibt keinen unnötigeren Abzug als Copy-Konstruktor und Assignment-Operator nicht implementiert...! Verzettelt euch dabei aber nicht!! Ihr wisst ich mag gern schöne Programme, in erster Linie müssen sie aber dem Bewertungsschema (Funktionalität und Stil-Basics) entsprechen - nicht meinem oder eurem persönlichen Sinn von Ästhetik.
Ich garantiere euch, dass wenn ihr das alle beherzigt, dass wir die Übung mit einem SEHR erfreulichem Ergebnis abschließen können - auch diejenigen die jetzt nach dem 2. Beispiel mit ein bisschen hängenden Köpfen hinausgegangen sind.
Für die Gruppen, die ich mir für das 3. Beispiel vorgemerkt habe (1743, 1756): Erinnert euch an den Deal. (Also, macht keine Fehler, weil die werden dann ausnahmslos geahndet...)
So, für heute ist Schluss, und alle die nicht ganz zufrieden waren, lasst die köpfe nicht hängen und bemüht euch für das ex3. ich weiß, dass ihr das könnt...und ihr wisst das auch...!!
wir sehen uns wieder beim nächsten Tutorium am 26. bzw 28. bei dem wir Fragen zum Ex3 beantworten, uns nocheinmal kurz die "Best-of"-Fehler des Ex2 ansehen und auch das supa lässige Thema Exceptions behandeln werden.
lg und alla cama (heute wegen übermüdung ein bisschen unkreativ)
4. Tutorium vorbei - der freie Himmel war zwar schön, noch freier tätt ich mich aber fühlen würden meine Schlüssel auch dort sperren wo sie sperren sollten (weil ob drinnen oder draussen - ist ja rein eine Frage der Definition) .
Einige Designfragen die wir dort erläutert haben waren ja wirklich spannend - ich bin schon gespannt wir ihr die Aufgabenstellung des 2. Beispiels löst.
Was ich aber vergessen habe zu erwähnen (jaja, mea culpa), was mir aber sehr wichtig ist (und in weiterer Folge ist es (wiedereinmal per Definition) auch für euch sehr wichtig ), ist das DDD. Benotet wird das DDD laut Bewertungsschema, ganz klar. Wenn ihr aber ein gutes DDD schreibt, dann hab ichs beim Korrigieren eurer Arbeit um vieles leichter, weil ich nicht erst raten muss, was ihr euch bei dieser und jener Designentscheidung gedacht habt, sondern ich es genau dort rauslesen kann. Was ich also idealerweise drin stehen haben will:
* schematischer Kontrollfluss der verschiedenen Aktionen * Designentscheidungen (grob: warum habt ihr euch entschlossen, das Problem genau so zu lösen) * kurzen Funktionsbeschreibungen sofern nicht ausgesprochen trivial (getter und setter müssen nur angeführt, nicht beschrieben werden) * sonstige Anmerkungen die dem Verständnis dienlich sind
Das heißt jetzt nicht, dass ihr auf 20 Seiten alles bis ins letzte Detail genau beschreiben müsst und insofern eine Mehrarbeit gegenüber anderen Gruppen habt. Idealerweise enthält aber ein Punkt des DDDs eine max. 1-2 Seiten lange Zusammenfassung, die euer Programm so präzise beschreibt, dass jedem der es danach ansieht sofort klar ist, worum es an den verschiedenen Stellen geht - weil GENAU DAS ist ein gutes DDD. Sollte selbige Zusammenfassung (analog zum Punkt "Beschreibung der gesamten Funktionalität" im DDD des Ex1) nicht oder nicht ausreichend vorhanden sein müsst ihr mit dem entsprechenden Punkteabzug rechnen (0 Punkte auf das DDD wegen dünner Ausführung).
So. Das erste Abgabegespräch haben wir mehr oder weniger gut überstanden.
Das Programm an sich hat einwandfrei funktioniert, und auch der Aufbau ist uns recht gut gelungen. Und doch haben wir leider einiges an Punkten verloren, was wenn wir das Programm mit den testfiles getestet hätten wahrscheinlich nicht passiert wäre .
Aber naja. Nachher ist man meistens schlauer als nachher, und so hab ich mir gleich nachher das mit den Tests angeschaut. Schaut eigentlich nicht schlecht aus hab ich mir gedacht, ist aber trozdem irgendwie umständlich jedes Testfile in die Konsole eingeben und nachher noch mit diff überprüfen.
Da bin ich dann irgendwann drauf gekommen, dass es eigentlich ganz einfach ist die Tests in das makefile einzubauen. Man braucht dann z.B. nur mehr 'make difftests' aufrufen und, wenn keine Fehler auftauchen, dann weiß man, das Programm könnte eventuell sogar fehlerfrei sein
Bei mir schaut das dann ca. so aus. Man muss dann nur noch die Variable TEST_FILES mit den richtigen Namen füllen, und schon hat man ein fast vollautomatisches Programmtesting eingebaut.
difftests:
for file in $(TEST_FILES); do ./monopoly < ./tests/$$file.in > ./tests/outfiles/$$file.realout; diff ./tests/outfiles/$$file.realout ./tests/outfiles/$$file.out; done
Das ist zwar nicht meine vollständige Version, die schon noch etwas optiomiert ist, und auch unter Linux funktioniert, aber wir sollen ja auch keine vollständigen Lösungen aufzeigen, sondern Denkanstöße in die richtigen Richtungen geben.
die ersten AGs mit Bravour überstanden (die meisten zumindest ) - bin wirklich sehr zufrieden!
Natürlich sind aber auch Fehler passiert, einige davon fallen wirklich in die Kategorie "SEHR unnötig"...:
* Testcases nicht bestanden Wir haben uns im Tutorium kurz angesehen wie man am besten testet und dass wir die Abgaben genauso testen. Trotzdem haben es gerade 4/12 Gruppen geschafft alle Punkte auf die Testfälle zu bekommen und die Funktionalitäts-Punkte bei diesem Beispiel waren wirklich noch geschenkt. Also: 1) Ausgabe-Strings (Schriftart Courier) im URD GENAU lesen (am besten per Copy&Paste einfügen) 2) Unsere Testcases verwenden, eigene Testcases schreiben und GUT testen!
Testen nochmal zur Erinnerung mit (schematisch): ./monopoly < test1.in > test1.out und dann diff test1.out musterlösung1.out
Ihr könnt zB auch die NG verwenden um Testfiles (.in und .out) den anderen zugänglich zu machen bzw. Ergebnisse zu vergleichen.
* Codingstandard Bei den meisten Abgaben musst ich mich nichteinmal bemühen einige CS-Fehler zu finden - hätt ich wirklich akribisch zu suchen angefangen dann hätte sicher die eine oder andere Gruppe noch einen Punkt weniger oder gar -100% auf das Beispiel wegen "konsequentem Ignorieren des Codingstandards". Der Codingsstandard ist nicht nur lästig, er macht auch Sinn! Jeder von euch sollte ihn zumindest einmal durchgelesen und jeden einzelnen Punkt verstanden haben - dann haltet ihr euch schon beim coden dran und müsst dann nicht stundenlang vor der Abgabe CS-Verstöße suchen.
Nächstes Tutorium werden wir uns ein bisschen mit Polymorphie und Vererbung beschäftigen, uns fragen was Kapselung und Erweiterbarkeit heißt und natürlich könnt ihr auch Fragen zum 2. Beispiel stellen.
Viele von euch verwenden eh schon "pluto" -- also Linux -- jetzt dachte ich mir, dass ich einige nützliche Befehle hier übersichtlich zusammenfasse und so einige der Möglichkeiten aufzeige. Zum Beispiel wenn ihr es leid seid ständig die neueste Version eures Programms auf Pluto upzuloaden findet ihr hier die Lösung (siehe svn). Wollt ihr zu einem Befehl mehr wissen sollte euch die Hilfe weiterhelfen. Gibt es fragen einfach posten.
Allgemeines
STRG + C
bricht den Prozess im Vordergrund ab
STRG + R
sucht und vervollständigt schon einmal eingegebene Befehle
TAB
verollständigt Befehle, Pfade und Dateinamen, gibt es mehrere Möglichkeiten TAB zwei Mal drücken
PFEILTASTE NACH OBEN
Pfeiltaste nach oben drücken wechselt zu den schon eingegebenen Befehlen
*.cpp
betrifft alle cpp-Dateien in einem Ordner, praktisch für cp, rm (dort z.B. *.o), etc. Auch z.B. game TAB zum vervollständigen drücken -> gamehandler.* ist möglich
Hilfe
BEFEHL --help
zeigt meist eine kurze Hilfe zu den Möglichkeiten eines Befehls an
man BEFEHL
zeigt die Anleitung eines Befehls an. Verlassen durch drücken von q. man man zeigt die hilfsdatei zu man an
info BEFEHL
zeigt die Anleitung eines Befehls an. Verlassen durch drücken von q. info erlaubt auch hyperlinks in den Hilfs-Dateien. info info zeigt die hilfsdatei zu info an
Dateien und Ordner
mkdirXY
erstellt einen Ordner names XY
touchXY
setzt den Zeitsstempel von XY auf jetzt bzw. erstellt XY ohne Inhalt falls es noch nicht vorhanden ist
diffXY VW
zeigt Unterschiede zwischen XY und VW
cpQuelle Ziel
kopiert Quelle nach Ziel
mvQuelle Ziel
verschiebt Quelle nach Ziel
rmXY VW
löscht die Dateien XY und VW. rm -f XY entfernt ohne Nachfrage XY. rm -rf XY entfernt ohne Nachfrage den Ordner XY.
Erstellt ein zip-archiv (Option "z" für zip) wubar.tar.gz (Option "f" gefolgt vom Dateinamen) aus allen cpp- und h-Dateien im aktuellen Ordner, aus Makefile und distribution.txt und gibt aus welche Dateien dem Archiv hinzugefügt wurden (Option "v" für verbose)
tar-tvf wuba.tar.gz
zeigt den Inhalt von wuba.tar.gz an
tar-xf wuba.tar.gz
extrahiert alle Dateien aus wuba.tar.gz
Navigieren
cdXY
wechselt in das Verzeichnis XY, falls es im derzeitigen Verzeichnis vorhanden ist, auch XY/test/wuba wäre möglich, falls die Verzeichnis-Struktur so aufgebaut ist
cd..
wechselt eine Verzeichnis-Ebene höher
cd~
wechselt zu deinem Homeverzeichnis, ist /home/ANFANGSBUCHSTABE_VON_USER_NAME/USER_NAME
checkout (anstelle von co kann man auch checkout schreiben) deines Projekts
svnup
bist du im YOUR_PROJECT-Verzeichnis führt dieser Befehl zu einem Update.
svnadd *.cpp asdf.h
fügt alle cpp-Dateien und asdf.h dem Projekt hinzu
svnci *.cpp *.h
commit (statt ci geht auch commit) veränderte cpp und h-Dateien, alle anderen werden automatisch nicht commited. wenn ihr das macht kommt ihr zu joe, ein Texteditor. Die ganze eingabe löschen und hinschreiben was ihr geändert habt (das ist das Log-File) nun STRG + K drücken und danach X drücken (speichert und verlässt joe)
Editoren
Es gibt sehr viele Editoren von denen ich nur einen ganz kurz erwähnen möchte. vim. Nicht das vim leicht zu bedienen wäre, nur hat dieser Editor standardmäßig Hervorhebung von Befehlen -- ob HTML, c++ ... -- auf Pluto, ihr müsst euch also nicht mit Konfigurationen rumschlagen. Einfachere Editoren sind joe und nano (^ steht für STRG). Ein weiterer sehr umfangreicher "Editor" ist emacs (startet mit -nw). Editoren sind praktisch sollte euer Programm nicht kompilieren wollen und ihr möchtet einen Fehler beheben ohne ständig pluto verlassen zu müssen.
VIM
vimXY ZA öffnet XY und ZA in vim bzw. erstellt diese Dateien. Die Besonderheit von VIM ist, dass vim zwei Betriebsmodi hat. Einen Eingabemodus -- hier kann man Text eingeben -- und einen Befehlsmodus. Drückt man i kommt man in den Eingabemodus und kann ganz "normal" Text eingeben, löschen etc. Drückt man ESC kommt man in den Befehlsmodus, wo man Dateien speichern, Zeilennummern anzeigen ... kann. Die folgende Liste stellt Befehle dar, die also im Befehlsmodus eingegeben werden können. Seid ihr es leid jedes mal wieder die Zeilennumern u.ä. einzustellen? "cp /etc/vimrc ~/.vimrc" "vim ~/.vimrc" nun kommentiert "set ai" aus, fügt darunter "se nu" und "se sw=2" ein.
ESC
ESC drücken führt einem zum Befehlsmodus
i
i führt einem zum Eingabemodus.
:se ai!
Einrückung der vorherigen Zeile übernehmen bzw. das wieder ausstellen
:se nu!
stellt Zeilennummern ein bzw. aus
:make
führt make aus, praktisch, da man vim nicht verlassen muss, wenn man nur Fehler finden will
:q!
verlässt vim ohne zu speichern
:w
speichert Datei
:wq
speichert und schließt vim
:X
Springt zu Zeilennummer X
X<< bzw. X>>
rückt X Zeilen aus bzw. ein. vorher einmal ":se sw=2" setzen, damit zwei Leerzeichen ein bzw. ausgerückt werden
:!XY
führt Befehl XY aus. zb. ":!ls" um zu sehen welche Dateien im Ordner sind und welche man öffnen will
:n bzw. :N
nächste, vorhergehende Datei editieren
STRG w v
Teilt Fenster vertikal
STRG w w
Springt zwischen Fenstern hin und her
STRG w o
aktuelles Fenster wird einziges Fenster
STRG w =
Beide Fenster werden gleich groß, praktisch, wenn man die Konsole (putty) größer gemacht hat
ja, so ist das halt nunmal im Leben. Hat mein eine nette Idee, kommt morgen schon der nächste und übertrumpft sie. Schlecht für mich, gut für euch - ihr könnt nämlich wiedereinmal eurer Wissen unter Beweis stellen. Mein engagierter Kollege Matthias hat sich einen Schokoriegelcontest überlegt, mitmachen - da bin ich mir sicher ;-) - kann natürlich jeder.
Wer jetzt denkt, dass er nicht mehr im Kindergarten, sondern auf einer Uni is, der *) hat das gut beobachtet *) mag anscheinend keine Schokoriegel.
Den Quiz gibts unter http://tugll.tugraz.at/sep08mp/weblog/4353.html - ist vielleicht schon eine kleine Prüfungsvorbereitung und solange wir keins auf die Mütze kriegen gibts also Shokoriegel und Zuckerl.
Kleine Anmerkung noch um Fehlinterpretationen zu vermeiden: WIR WOLLEN, dass ihr euch mit dem Stoff der Vorlesung kontinuierlich auseinandersetzt, weil IHR WERDET es DRINGEND brauchen. Dass es im LearnLand lustig zu geht, ändert nichts an der strengen Bewertung der Übungen (gemäß BS) - strengt euch also dementsprechen an, weil sonst habt ihr am Ende zwar vielleicht einen Schokoriegel aber kein Zeugnis in der Hand!