Tutorium #4
Softwareentwicklung Praktikum
Gruppe 3 – Jörg Müller
prog-tutor-joerg@iicm.tugraz.at
Gruppe 4 – Michael Schwarz
prog-tutor-michael@iicm.tugraz.at
Gruppe 5 – Anja Karl
prog-tutor-anja@iicm.tugraz.at
Gruppe 8 – Thomas Neff
prog-tutor-thomas@iicm.tugraz.at
Ablauf des Tutoriums
- Häufige Fehler
- File IO
- Operator Overloading
- Casting
- Exceptions
- Ressourcenverwaltung
- Auto-Pointer
- Templates
- Ass3
Häufigste Fehler in Ass2
- Nicht genügend getestet (z.B. "next 1a")
- Messages nicht 1 zu 1 aus der Angabe kopiert
- If - ElseIf - Else zum matchen von Commands
- Doppelter Code (z.B. bei Next)
- Zu tiefe Verschachtelungen
- Speicherlöcher, Valgrindfehler
- Codingstandard
Häufige Fehler, die viele Punkte kosten
- Valgrind Warnings
- unnötiges this
- Doppelter Code
- C statt C++ Casts
- Codingstandard
File-IO - File öffnen
Binärdatei zum Schreiben öffnen
#include <fstream>
std::ofstream file("tut4.bin",
std::ios::out | std::ios::trunc | std::ios::binary);
if (!file.is_open())
{
std::cerr << "cannot open file" << std::endl;
} else file.close();
Binärdatei zum Lesen öffnen
std::ifstream input("tut4.bin", std::ios::in | std::ios::binary);
if (!input.is_open())
{
std::cerr << "cannot open file" << std::endl;
} else input.close();
File-IO - Schreiben
// schreibe "SEPRISK\0" in Datei
file << "SEPRISK";
file << '\0';
// schreibe Zahl 7 als Binärwert -> in char speichern
// und dann in Datei schreiben
char num = 7;
file << num;
// schreibe Zahl 7 als Ziffer ('7') -> wenn Zahl
// in int gespeichert ist
int num2 = 7;
file << num2;
// schreibe C-String mit Nullbyte
char* str = "Hallo Welt";
file << str << '\0';
Dateien können behandelt werden wie std::cout und std::cin
File-IO - Lesen
// lese C-String Zeichenweise bis Nullbyte zurück
char c;
while((input >> c) && c != 0) {
std::cout << c;
}
std::cout << std::endl;
// lese Binärwert 7 zurück (und zeige als char und integer an)
char y;
input >> y;
std::cout << y << " = " << static_cast<int>(y) << std::endl;
// lese Zahl 7 als Ziffer ('7') zurück
int x;
input >> x;
std::cout << x << std::endl;
Operator Overloading
Operator Overloading - Beispiel (als Member)
class Complex
{
private:
float real_part_;
float imaginary_part_;
public:
bool operator==(const Complex &c) const
{
return ( c.real_part_ == real_part_ &&
(c.imaginary_part_ == imaginary_part_) );
}
Complex operator+(const Complex& right)
{
Complex result;
result.setImaginary(imaginary_part_ + right.getImaginary());
result.setReal(real_part_ + right.getReal());
return result;
}
[...]
};
Casts in C++
- static_cast
- Funktioniert wie das von C bekannte Casting (typ)variable. Funktioniert nur, wenn der Compiler eine Regel zum Casten kennt.
int a = 16, b = 9;
float ratio = static_cast<float>(a) / b;
- const_cast
- Nur zum Hinzufügen oder Entfernen (gefährlich)
des const
Keywords.
- dynamic_cast
- Casts innerhalb einer Klassenhierarchie von polymorphen Klassen. Aufwändige Type-Checks zur Laufzeit.
// Cast von *CD in *MusicCD
MusicCD *charts = dynamic_cast<*MusicCD>(cd1);
- reinterpret_cast
- Keine Konvertierung, Daten werden ohne Änderung als neuer Datentyp interpretiert,
plattformabhängig, sollte nur bei hardwarenaher Programmierung verwendet werden.
static_cast verlustfreie Regeln
Castings sind nur dann ohne Datenverlust, wenn auf einen größeren Datentyp gecastet wird.
Exceptions
Grundlegendes Konstrukt für Exceptions
try
{
someFunction() // könnte eine Exception werfen...
[...]
throw(e); // ...oder manuelles Werfen einer Exception
}
catch(MyException& e1)
{
// fange Exception vom Typ MyException
}
catch(std::exception& e)
{
// fange alles, was vom Typ std::exception abgeleitet ist,
// und noch nicht gefangen wurde
}
Exceptions
Nützlich, aber nicht übertreiben → Formale Fehlerquellen
- Kontrollfluss durch Exceptions
- Silent Catch von Exceptions
- Wurf einer Nicht-std::exception-Unterklasse
- Im Destruktor dürfen keine Exceptions geworfen werden
Also immer
class MyException : public std::exception {
[...]
};
Exceptions - Beispiel
#include <exception>
class brick : public std::exception {
virtual const char* what() const throw()
{
return "Achtung Ziegel!\n";
}
};
int main ()
{
try
{
throw brick();
} catch (std::exception& ex)
{
std::cout << ex.what();
}
return 0;
}
Neu zu beachten: Out of Memory!
int main ()
{
try
{
int* myarray = new int[10000];
}
catch (std::bad_alloc& ba)
{
std::cout << "Out of Mana!" << std::endl;
}
return 0;
}
Neu zu beachten: Out of Memory!
In jedem Fall muss aller Speicher wieder freigegeben werden!
...
try
{
NextCommand* next = new NextCommand("next");
}
catch (std::bad_alloc& ba)
{
std::cout << "Out of Mana!" << std::endl;
return 0;
}
try
{
ShowCommand* show = new ŚhowCommand("show");
}
catch (std::bad_alloc& ba)
{
delete next;
std::cout << "Out of Mana!" << std::endl;
return 0;
}
...
Aber geht das nicht besser???
Besser:
- Smart Pointer verwenden!
- Variablen sofort nach dem "new" Aufruf in eine Klassen-Member-Variable Speichern, die vom Destruktor wieder freigegeben wird.
- RAII
-> Es reicht bad alloc in der Main zu fangen.
Außer: Exceptions im Konstruktor!
Achtung: Exception im Konstruktor
- Wird im Konstruktor eine Exception geworfen, ist das Objekt nicht erstellt, und auch der Destruktor wird nicht aufgerufen.
#include <memory>
#include <iostream>
#include <vector>
class Foo {
private:
std::vector<int*> list;
public:
Foo() {
list.push_back(new int);
list.push_back(new int);
throw 1;
}
~Foo() {
for(auto it : list) {
delete it;
}
}
};
int main() {
Foo f;
return 0;
}
Resource Acquisition Is Initialization (RAII)
- Konzept zur Verwaltung von Ressourcen
- Automatische Freigabe beim Verlassen des Scopes
- Kann für Handles (z.B. Dateien), Locks und Speichern verwendet werden
- Erleichtert das Schreiben von Exception-sicheren Programmen
Kein RAII - Beispiel
int median(int* numbers, int count) {
int* data = new int[count];
std::memcpy(data, numbers, count * sizeof(int));
// error handling
if(numbers == NULL) {
delete[] data; // <---
return 0;
}
if(count == 0) {
delete[] data; // <---
return 0;
}
if(count == 1) {
delete[] data; // <---
return *numbers;
}
// sort
if(mySortFunction(data, count) == false) {
delete[] data; // <---
return 0;
}
// return median
int result = data[count / 2];
delete[] data; // <---
return result;
}
RAII - Beispiel
int median(int* numbers, int count)
{
Data data(count);
data.setData(numbers, count);
// error handling
if(numbers == NULL)
return 0;
if(count == 0)
return 0;
if(count == 1)
return *numbers;
// sort
if(mySortFunction(data.getData(),
count) == false)
return 0;
return data.getData()[count / 2];
}
class Data
{
private:
int* data_;
public:
Data(int count) {
data_ = new int[count];
}
int* getData() const {
return data_;
}
void setData(int* data,
int count)
{
std::memcpy(data_, data,
count * sizeof(int));
}
~Data() {
delete[] data_;
}
};
Smart Pointer 
"Stirbt" der Smart-Pointer, wird der reservierte Speicher (durch delete) freigegeben.
Smart Pointer - Beispiel 
#include <iostream>
#include <memory>
class Foo {
public:
void bar() {
std::cout << "foobar" << std::endl;
}
};
std::unique_ptr<Foo> createFoo() {
Foo* bar = new Foo();
return std::unique_ptr<Foo>(bar);
}
int main() {
std::unique_ptr<Foo> ptr = createFoo();
std::vector<std::unique_ptr<Foo>> foos;
foos.push_back(std::unique_ptr<Foo>(new Foo()));
foos.push_back(std::unique_ptr<Foo>(new Foo()));
foos.push_back(std::unique_ptr<Foo>(new Foo()));
ptr->bar();
}
Templates
- Erlauben es, generische Funktionen zu schreiben
- Werden in der STL ausgiebig verwendet
- Es gibt Funktions- und Klassentemplates
- Deklarations-Syntax:
template <typename identifier> function_declaration;
- Aufrufs-Syntax:
function<argument_type>(arguments);
bzw. classname<class_type>(constructor_values);
Templates - Beispiel (Funktionstemplate)
template <typename Type>
Type max(Type a, Type b)
{
if(a > b) return a;
else return b;
}
int main()
{
// ruft max<int> auf (wird an Argumenten erkannt)
std::cout << max(3, 7) << std::endl;
// ruft max<double> (wird an Argumenten erkannt)
std::cout << max(3.0, 7.0) << std::endl;
// Argumente nicht eindeutig, Typ muss angegeben werden (<double>)
std::cout << max<double>(3, 7.0) << std::endl;
return 0;
}
Templates - Beispiel (Klassentemplate)
template <typename T>
class Math {
public:
T getMin(T x1, T x2) {
if(x1 < x2) return x1;
else return x2;
}
T getMax(T x1, T x2) {
if(x1 > x2) return x1;
else return x2;
}
};
int main() {
Math<int> int_math;
Math<std::string> str_math;
std::cout << "max(2, 4): " << int_math.getMax(2, 4) << std::endl;
std::cout << "min(\"abc\", \"bcd\"): "
<< str_math.getMin("abc", "bcd") << std::endl;
}
Assignment 3
http://towersep.appspot.com/
Übernommene Befehle
show
Ausgabe von Spielfeld, Kapital und HP
next
Runde beenden bzw. n Runden vorspulen
quit
Beendet das Programm
Neue Befehle
stats
Zeigt Spielstatistiken an
buy
Turm kaufen und errichten
show target
Zeigt an, was sich auf einem Feld befindet
upgrade (Bonus)
Bonus: Turm upgraden auf besseren Turm
Ratio
Rationale Zahl, bestehend aus zwei teilerfremden Ganzzahlen.
int main() {
Ratio ratio_1(7,3);
Ratio ratio_2(3,2);
std::cout << "Ratios: " << std::endl;
std::cout << ratio_1 << std::endl;
std::cout << ratio_2 << std::endl;
Ratio sum = ratio_1 + ratio_2;
std::cout << "Sum:" << std::endl;
std::cout << sum << std::endl;
sum = sum - 2;
std::cout << "Sum - 2:" << std::endl;
std::cout << sum << std::endl;
if(sum > 1)
std::cout << "Ratio: " << sum << " is > 1" << std::endl;
else
std::cout << "Ratio: " << sum << " is < 1" << std::endl;
}
Ratio
Ausgabe
Ratios:
7 / 3
3 / 2
Sum:
23 / 6
Sum - 2:
11 / 6
Ratio: 11 / 6 is > 1
SVG
- Scalable Vector Graphics
- Bild wird durch Vektoren beschrieben
- Beschreibung als XML
- Beliebig skalierbar
SVG - Aufbau
Header
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
Objekte
<image x="0" y="0" width="1016px" height="638px"
xlink:href="base.svg"> </image>
[...]
Footer
</svg>
SVG - Einfache Objekte
- Text:
<text x="10" y="490">Player1</text>
- Kreis:
<circle cx="64" cy="90" r="14" stroke="black"
stroke-width="2" fill="red"/>
- Rechteck:
<rect x="0" y="448" width="152" height="52" fill="green" />
- Eingebettete Bilder:
<image x="0" y="0" width="1016px" height="638px"
xlink:href="base.png"></image>
Viel Glück