Martin Sulzmann
Sichtbarkeitsbereiche und Referenzübergabe
IO streams und Strings
Überladene Operatoren/Funktionen
Klassen
Virtuelle (dynamisch) versus nicht-virtuelle (statisch) Methoden(auswahl) und static
Templates
#include <stdio.h>
// Sichtbarkeitsbereiche ("scope")
// In C gibt es nur 2 Sichtbarkeitsbereiche.
// (Im neusten C Standard gilt die gleiche Regel wie in C++)
// In C++ sind beliebige Schachtelungen moeglich.
// Jeder Codeblock { ... } entspricht einem Sichtbarkeitsbereich.
void scopeInC() {
float i = 1.0;
int x[5] = {1,2,3,4,5};
int i_2;
int j;
int x_3;
for(i_2 = 0; i_2 < 5; i_2++) {
j = x[i_2];
x_3 = j;
printf("%d",x_3);
}
}
// B = Begin. E = End.
// Markiert Codeblock.
void scopeInCPP() { // B1
float i = 1.0;
int x[5] = {1,2,3,4,5};
for(int i=0; i < 5; i++) { // B2
int j = x[i];
{ // B3
int x = j;
printf("%d", x);
} // E3
} // E2
} // E1
// Referenzübergabe.
// Syntaktischer Zucker in C++.
struct Pair {
int left;
int right;
};
// Variante 1.
void swapPair_Ref(struct Pair& p) {
int tmp = p.left;
p.left = p.right;
p.right = tmp;
}
// Variante 2.
void swapPair_Pointer(struct Pair* p) {
int tmp = p->left;
p->left = p->right;
p->right = tmp;
}
void testSwap() {
struct Pair p = {1,2};
struct Pair q = {1,2};
printf("\nPair: p = (%d,%d)", p.left, p.right);
swapPair_Ref(p);
printf("\nPair swapped: p = (%d,%d)", p.left, p.right);
printf("\nPair: q = (%d,%d)", q.left, q.right);
swapPair_Pointer(&q);
printf("\nPair swapped: q = (%d,%d)", q.left, q.right);
}
int main() {
scopeInC();
scopeInCPP();
testSwap();
}
IO streams
string
Datentyp
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
void IO_in_CPP() {
float f = 1.0;
cout << f;
int i = 5;
cout << i;
// "<<" ist ein binaerer Operator. Links ist das Ausgabeziel und rechts der Ausgabewert.
// "<<" ist ueberladen, auf der rechten Seiten koennen int, float, .... Werte stehen.
// Sequenz von Ausgaben.
cout << f << " " << i;
// "<<" ist links assoziativ.
((cout << f) << " ") << i;
// Beobachtung:
// cout << f, rechter Operand ist vom Typ float.
// (...) << " ", rechter Operand ist vom Typ string.
// (...) << i, rechter Operand ist vom Typ int.
cout << i << "Hallo" << f;
((cout << i) << f) << i << f << "Hallo";
}
void vergleich_zu_C() {
float f = 1.0;
printf("%f",f); // cout << f;
int i = 5;
printf("%d",i); // cout << i;
// C: Dynamische Pruefung der (Typ)kompatibilitaet von Argument und Platzhalter
// C++: Statische (zur Compilezeit) Pruefung!
}
void strings_cpp() {
string s = "\nHallo";
cout << s;
string s2;
s2 = s + s; // String Konkatenation
cout << s2;
string s3 = "Hallo";
cout << "\n" << s.length();
cout << "\n" << s2.length();
cout << "\n" << s3.length();
}
int main() {
IO_in_CPP();
strings_cpp();
}
C++ unterstützt überladene Operatoren/Funktionen.
Regel: Instanz muss eindeutig bestimmbar sein via Argumenttypen.
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
// Eigene Instanz fuer "<<" Operator.
// Man spricht hier von "Überladung".
struct Pair {
int left;
int right;
};
ostream& operator<< (ostream &out, struct Pair &p) {
out << "(" << p.left << "," << p.right << ")";
return out;
}
// Transformation des Compilers.
// Generiere eindeutigen Namen, normalerweise Hash-Index
// basierend auf Argumenttypen.
// Wir verwenden einfach "_Pair".
ostream& outStream_Pair (ostream &out, struct Pair &p) {
out << "(" << p.left << "," << p.right << ")";
return out;
}
void IO_in_CPP() {
struct Pair p = {1,2};
cout << p; // Verwende obige "out stream" Instanz.
// Compiler Transformation.
// Verwende eindeutigen Funktionsaufruf.
outStream_Pair(cout,p);
}
// Beachte:
// - swapPair ist eine überladene Funktion.
// - Mehrfache Definition, aber für verschiedene Argumenttypen.
// Variante 1.
void swapPair(struct Pair& p) {
int tmp = p.left;
p.left = p.right;
p.right = tmp;
}
// Intern passiert folgendes.
// Annahme, der Funktionsname swapPair_Ref ist eindeutig.
void swapPair_Ref(struct Pair& p) {
int tmp = p.left;
p.left = p.right;
p.right = tmp;
}
// Weitere Transformation.
// Uebersetze call-by-reference in call-by-value + pointer
void swapPair_Ref_Transformed(struct Pair* p) {
int tmp = p->left;
p->left = p->right;
p->right = tmp;
}
// Variante 2.
void swapPair(struct Pair* p) {
int tmp = p->left;
p->left = p->right;
p->right = tmp;
}
void swapPair_Pointer(struct Pair* p) {
int tmp = p->left;
p->left = p->right;
p->right = tmp;
}
// Variante 3 (call-by-value)
/*
"'swapPair' is ambiguous"
void swapPair(struct Pair p) {
int tmp = p.left;
p.left = p.right;
p.right = tmp;
}
*/
void testSwap() {
struct Pair p = {1,2};
struct Pair q = {1,2};
cout << "\nPair: " << p;
swapPair(p);
// Compiler verwendet hier Variante 1 mit explizitem eindeutigem Namen.
// 1ter Transformationsschritt.
swapPair_Ref(p);
// 2ter Transformationsschritt.
swapPair_Ref_Transformed(&p);
cout << "\nPair swapped: " << p;
cout << "\nPair: " << q;
swapPair(&q);
swapPair_Pointer(&q);
cout << "\nPair swapped: " << q;
}
int main() {
IO_in_CPP();
testSwap();
}
Kapselung von Zustand (Attribute) und Verhalten (Methoden)
Erweiterung der Klassenhierarchie (“extends”).
Objekt ist Instanz einer Klasse
C++ Objekte können auf dem Stack oder auf dem Heap allokiert werden
#include <iostream>
#include <string>
using namespace std;
///////////////////////
// Klassen
class Point2D {
float x, y; // private
public:
Point2D(float x2=0, float y2=0) {
x = x2; y = y2;
}
void scale(float f) {
x = f * x; y = f * y;
}
float getX() { return x; }
float getY() { return y; }
void setXY(float x2, float y2) {
x = x2; y = y2;
}
string show() {
// + Konkatenation zweier Strings.
// Konvertierung von float nach String via to_string.
// C++ unterstuetzt Ueberladung (overloading).
// D.h wir koennen z.B. to_string auf Werte vom Typ float,
// als auch auf Werte vom Typ int anwenden.
// Konkret.
// string str = to_string(1) + to_string(1.0);
return ("\nx = " + to_string(x) + "\ny = " + to_string(y));
}
}; // Point2D
void testPoint2D() {
Point2D p1; // Wir haben den Default Auruf
// p1 = Point2D();
// Aber wir haben Default Werte definiert.
// Deshalb wir obige Anweisung interpretiert als
// p1 = Point2D(0,0);
Point2D p2 = Point2D(1,2);
Point2D p3(3,4); // Verschieden Schreibweise fuer einen Konstruktoraufruf.
Point2D p4 = Point2D(1); // Point2D(1,0)
// Beachte. In Java werden Objekte immer mit Hilfe von "new" angelegt!
// In Java liegen alle Objekte auf dem Heap.
// In C++ koennen Objekte auf dem Stack ("kein new") oder dem Heap ("new") liegen.
// Im obigen Beispiel, liegen die Objekte p1, p2, p3 und p4 auf dem Stack.
cout << p1.show();
cout << p2.show();
p3.scale(2);
cout << p3.show();
}
// Erweiterung der Klassenhierarchie ("extends").
class Point3D : public Point2D {
float z;
public:
Point3D(float x2=0, float y2=0, float z2=0) : Point2D(x2,y2) {
z = z2;
}
void scale(float f) {
this->setXY(f * this->getX(), f * this->getY());
z = f * z;
}
string show() {
return ("\nx = " + to_string(getX())
+ "\ny = " + to_string(getY())
+ "\nz = " + to_string(z));
}
};
void testPoint3D() {
Point3D p = Point3D(1,2,3);
cout << p.show();
p.scale(2);
cout << p.show();
// Point3D <= Point2D,
// bedeutet jedes Objekt der Klasse Point3D kann auch an einer Stelle verwendet werden,
// an welcher ein Objekt der Klasse Point2D erwartet wird.
Point2D q = p;
cout << q.show();
}
int main() {
testPoint2D();
testPoint3D();
}
Virtuelle Methoden:
dynamische Methodenauswahl basierend auf dem dynamischen Typ des Objekts zur Laufzeit
In C++ muss zur virtuellen Methodenauswahl, der Methodenaufruf via “pointer objects” stattfinden
Nicht-virtuelle Methoden:
static
:
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
public:
static int number;
static int getNo() { return number; }
virtual int maxSpeed() { return 0; }
string info() { return "I'm a vehicle"; }
};
int Vehicle::number = 0;
// car <= Vehicle
class car : public Vehicle {
public:
car() { Vehicle::number++; }
int maxSpeed() { return 100; }
string info() { return "I'm a car"; }
};
// Bike <= Vehicle
class Bike : public Vehicle {
public:
Bike() { number++; }
int maxSpeed() { return 30; }
string info() { return "I'm a bicycle"; }
};
void test1(Vehicle v) {
cout << "\nInfo: " << v.info();
cout << "\nSpeed: " << v.maxSpeed();
}
void test2(Vehicle* v) {
cout << "\nInfo: " << v->info();
cout << "\nSpeed: " << v->maxSpeed();
}
void test3(car v) {
cout << "\nInfo: " << v.info();
cout << "\nSpeed: " << v.maxSpeed();
}
int main() {
Vehicle::number = 0;
Bike b = Bike();
car c = car();
test1(c); // car <= Vehicle
// das car Objekt c wird konvertiert
// in ein Vehicle Objekt
test2(&c); // *car <= *Vehicle
// Die Referenz auf ein car Objekt
// wird uebergeben.
test3(c);
test1(b);
test2(&b);
cout << "\n There are "
// << Vehicle::number
<< Vehicle::getNo()
<< " vehicles";
}
“Look-and-feel” ähnlich wie Generics in Java.
Elementare Unterschiede (z.B. Template Expansion zur Compile-Zeit, mehr dazu später).
#include <iostream>
using namespace std;
// class template
template<typename T>
class Elem {
T x;
public:
Elem() {}
Elem(T y) { x = y; }
void replace(T y) { x = y; }
T get() { return x; }
};
// function template
template<typename T>
void mySwap(T& x, T& y) {
T tmp;
tmp=x; x=y; y=tmp;
}
// template instantiation
int main() {
Elem<int> i = Elem<int>(1);
Elem<int> i2 = Elem<int>(2);
Elem<char> c = Elem<char>('a');
i.replace(3);
mySwap<Elem<int>>(i,i2);
// Take note.
// 1. Because of T tmp in mySwap,
// we need to provide the parameterless constructor in the Elem class
// 2. Pior to "c++11" we need to include a space in between >>.
// in some cases, template instances can be inferred by the compiler
mySwap(i2, i);
int j, j2;
j = 1; j2 = 3;
mySwap<int>(j, j2);
mySwap(j2, j);
return 1;
}
// Compiler generates
class Elem_int {
int x;
public:
Elem_int() {}
Elem_int(int y) { x = y; }
void replace(int y) { x = y; }
int get() { return x; }
};
class Elem_char {
char x;
public:
Elem_char() {}
Elem_char(int y) { x = y; }
void replace(char y) { x = y; }
char get() { return x; }
};
void mySwap_Elem_int(Elem_int& x, Elem_int& y) {
Elem_int tmp;
tmp=x; x=y; y=tmp;
}
void mySwap_int(int& x, int& y) {
int tmp;
tmp=x; x=y; y=tmp;
}
int main2() {
Elem_int i = Elem_int(1);
Elem_int i2 = Elem_int(2);
Elem_char c = Elem_char('a');
i.replace(3);
mySwap_Elem_int(i,i2);
// Because of T tmp in mySwap,
// we need to provide the parameterless constructor in the Elem class
// in some cases, template instances can be inferred by the compiler
mySwap_Elem_int(i2, i);
int j, j2;
j = 1; j2 = 3;
mySwap_int(j, j2);
mySwap_int(j2, j);
return 1;
}