C++/Tutorial: 2. Variablen und cin

Aus Scientia
Version vom 6. April 2011, 07:29 Uhr von Alexis Hiemis (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Beispielprogramm

Ich möchte mich in diesem Kapitel an einem Beispielprogramm entlanghangeln, keine Sorge es ist nicht dafür gedacht, von vornerein verstanden zu werden, deswegen werde ich es ja in diesem Kapitel erklären. Das Programm verlangt vom Benutzer die Eingabe einer Zahl und gibt das doppelte dieser Zahl aus, es tut also schon "wirklich" etwas.

#include <iostream>
#include <limits>
 
using namespace std;
 
int main()
  {
  int number; //Variable number deklarieren
  cin >> number; //Zahl einlesen und in number speichern
  cin.clear(); //Errorflags von cin löschen
  cin.ignore(numeric_limits<streamsize>::max(), '\n'); //cin Buffer leeren
 
  cout << (number*2) << endl; //Das Doppelte ausgeben
 
  cin.get(); //unformatierte Eingabe(Konsole offen halten)
  }

Kommentare

Zunächst einmal möchte ich die Erklärungen erklären, denn im Quellcode steht ja scheinbar Text, der vom Compiler ignoriert wird. Dabei handelt es sich um sogenannte Kommentare. Von denen gibt es in C++ zwei Arten.

  • Das Zeilenkommentar // ist eine Anweisung für den Compiler alles bis zum Ende der Zeile zu ignorieren
  • Das Blockkommentar /* */ ist eine Anweisung für den Compiler alles zwischen /* und */ zu ignorieren

Variablen

Der erste Teil des Programmes(innerhalb von main) ist

int number;

Es handelt sich dabei um eine sogenannte Variable.
Eine Variable ist eine Art zwischenspeicher, in ihnen `merkt` sich das Programm etwas.
Bei der Zeile handelt es sich um eine Variablendeklaration, der Zwischenspeicher wird dem Compiler hier erst bekannt gemacht.
Das ganze besteht aus 2 Teilen, dem Variablentyp und dem Bezeichner(identifier).

Typen

In C++ hat jede Variable und jedes Literal einen Typ. Man kann sich das so vorstellen: der Computer ist nur in der Lage irgendwelche Dinge in Bytes zu speichern, aber er ist nicht in der Lage zu sagen, wie diese Bytes zu interpretieren sind. Der Typ einer Variable sagt dem Programm, wie die Bytes zu interpretieren sind, ob als Zahl, ob als String, als Bild oder oder oder...
Es gibt in C++ 2 Arten von Typen: Eingebaute Datentypen und benutzerdefinierte Typen(so bezeichne ich nun einfach mal alle Typen, die in C++ selbst definierbar sind.), anders als z.B. in Java unterscheiden sich diese in der Benutzung nicht(Objekte sind nicht automatisch Referenztypen für alle, die andere Sprachen beherschen). Die eingebauten Literale haben auch nur eingebaute Typen, wodurch ihnen schon eine Sonderrolle zukommt(durch implizite Casts, aber vernachlässigbar), dazu kommt, dass eingebaute Typen immer verfügbar sind, benutzerdefinierte benötigen ein #include.
Benutzerdefiniert heißt in dem Fall auch nicht, dass ihr sie definieren müsst, auch in der Standardbibliothek sind welche enthalten.

Ausgewählte Typen

  1. Ganze Zahlen sind Zahlen ohne Nachkommastellen, im Gegensatz zu Gleitkommazahlen, sind diese immer genau haben aber einen beschränkten Bereich. Es gibt verscheidene Typen für ganze Zahlen
    1. int ist in dem Beispiel verwendet, es handelt sich hierbei um den Standardtyp für Ganze Zahlen, mit dieser Größe kann der Computer am besten umgehen. Die Größe ist im Standard nicht festgelegt, beläuft sich aber meistens auf einen Bereich von −2.147.483.648 bis 2.147.483.647 und belegt 4 Bytes Speicher auf 32-Bit und 64-Bit Systemen. Aber verlasst euch nicht drauf!
    2. unsigned int oder nur unsigned ist ein Vorzeichenloser Datentyp, man kann alle ganzen Zahlentypen mit unsigned modifizieren, wodurch sich der Bereich ins Positive verschiebt(von 0 bis bisheriges Maximum*2). Normale Zahlenliterale haben den Typ int(5 z.B. - mit den Prefixen 0x für Hexadezimale Zahlen(z.B. 0xff) und 0 für Oktale Zahlen ebenso(010 ist nicht das selbe wie 10!)).
    3. short int oder nur short ist ein kleinerer oder gleich großer Datentyp für ganze Zahlen
    4. long int oder nur long ist ein größerer oder gleich großer datentyp für ganze Zahlen
    5. char ist ein genau ein Byte großer Datentyp, der dadurch von -128 bis 127 geht. Er wird auch verwendet um Buchstaben darzustellen, Characterliterale sind in einzelnen Hochkommata('a', "a" ist was anderes!)
  2. Gleitkommazahlen sind einfache Kommazahlen, ihr Wertebereich ist unbegrenzt, dafür sind sie niemals exakt und sollten auch nicht als exakt angenommen werden.
    1. double ist der Standarddatentyp für Gleitkommazahlen. Gleitkommazahlen werden mit Punkten als Kommata in Literalen geschrieben, das heißt 5.5 z.B., aber nicht 5,5
    2. float und long double sind kleinere bzw. größere Datentypen für Gleitkommazahlen.
  3. bool
    1. bool ist ein Wahrheitswert, er kann entweder true oder false sein(2 Literale). Wenn man ihn zu einer Zahl castet entspricht false 0 und true 1, wenn man eine Zahl zu bool castet ist alles außer 0 true.
  4. Strings
    1. string ist ein benutzerdefinierter Typ. Man kann zwar Stringliterale in einer Stringvariable speichern, aber Stringliterale haben eigentlich den Typ char const[], den wir später besprechen werden. Dennoch ist string der Typ, den man für Strings verwenden sollte.

Bezeichner

Der zweite Teil einer Variablendeklaration ist der Bezeichner. Bei ihm handelt es sich um den Namen der Variable, der immer dann verwendet wird, wenn man den Wert der Variable auslesen oder verändern möchte. Diese Namen sind Case-Sensitive, das bedeuted, dass Groß- und Kleinschreibung relevant sind und dürfen aus Buchstaben, Zahlen und dem _ bestehen. Ein paar Regeln gibt es noch

  • Ein Bezeichner darf nicht mit einer Zahl beginnen
  • Bezeichner, die mit _ beginnen sind für den Compiler reserviert(kein Fehler, aber man sollte sie nicht verwenden)
  • Bezeichner dürfen keine Schlüsselwörter sein.

Bezeichner stehen für eine Variable. Man kann sie stellvertretend für den Wert verwenden, die in der Variable gespeichert sind, es macht keinen Unterschied ob es sich um eine Variable, ein Literal oder sonst was handelt, der Compiler unterscheidet nur folgendes

  • Ist der Wert des Ausdrucks zur Compilezeit bekannt, sprich eine Konstante?
  • Ist der Ausdruck ein LValue, oder ein RValue -> kann der Wert verändert werden, oder nicht?

Initialisierung

In einer Variablendeklaration kann noch eine Initialisierung vorgenommen werden, auch wenn dies im Beispiel nicht der Fall ist. Das bedeuted leglich, dass wir der Variable von vornerein einen Ausgangswert zuweisen und kann folgendermaßen aussehen, entweder so

int var(2); //Variable hat den Wert 2

oder so

int var = 2; //Das Selbe

Ich persönlich bevorzuge die erste Variante, aber das ist Geschmackssache.

Durch implizite Casts ist es auch möglich eine Ganze Zahl einem Gleitkommazahlentyp zuzuweisen und sogar eine Gleitkommazahl einer Ganzen Zahl zuzuweisen, dabei wird abgerundet

double d(3); //d = 3.0 oder 3.
int i(3.3); //i = 3

Zuweisung

Natürlich kann man den Wert einer Variable auch im Nachhinein ändern(das ist ja der Sinn der Sache) und zwar durch eine sogenannte "Zuweisung".
Das ganze ist ziemlich einfach und sieht folgendermaßen aus.

int i(0); //i = 0
i = 4; //i = 4;)

Eingabe mit cin

cin ist für Eingaben vom Benutzer verantwortlich, eine Variante kennen wir bereits cin.get(), hierbei handelt es sich um eine unformatierte Eingabe, es wird einfach eine Zeile eingelesen, ohne sie speziell zu formatieren. Im Beispiel handelt es sich um eine formatierte Eingabe, das heißt es wird genau eine ganze Zahl gelesen.

cin >> number;

speichert also eine ganze Zahl(da number vom Typ int ist) in number. number kann nun stellvertretend für die eingegebene Zahl verwendet werden, so einfach ist das schon. Aber! wir haben die Eingabe noch mit Enter bestätigt, das heißt wir haben auch einen Zeilenumbruch eingegeben und der bleibt nun im Puffer und wird bei cin.get(); eingelesen. Das heißt für uns, dass durch den Puffer cin.get() am Ende nutzlos wird, denn der Zeilenumbruch im Puffer reicht ihm und er verlangt keinen weiteren -> die Konsole bleibt nicht offen.
Deswegen müssen wir cin erst zurücksetzen, man kann ganze Artikel über Vorgehensweisen hier schreiben, aber in den meisten Fällen tut es folgendes:

  cin.clear(); //Errorflags von cin löschen
  cin.ignore(numeric_limits<streamsize>::max(), '\n'); //cin Buffer leeren

Ich verlange nicht, dass ihr die Anweisungen komplett versteht, es reicht wenn ihr wisst, dass die erste Zeile eventuell aufgetretene Fehler ignoriert und die zweite den Puffer bis zum nächsten Zeilenumbruch leert.
Nach jeder formatierten Eingabe sollten diese Zeilen kommen, sonst könnte man eine Überaschung erleben.

Rechnen mit den Rechenoperatoren

Nun fehlt nur noch eine Zeile, und zwar die Ausgabe. Wie die Ausgabe an sich funktioniert sollte klar sein, ich möchte auf das *2 eingehen. C++ definiert die ganz normalen Rechenoperatoren +, -, *(mal) und /(geteilt). Dabei wird auch Punkt-Vor-Strichrechnung beachtet und Klammern können verwendet werden. Als Operanden können sowohl Zahlenliterale, als auch Variablen verwendet werden.
Dazu gesellt sich der sogenannte Modulooperator %. Aus der Grundschule kennt ihr die Rechenweise geteilt mit Rest, Modulo ist dieser Rest. 5 % 2 liefert also 1, weil 5 durch 2 = 2 Rest 1.
Klammern in der Ausgabe um Rechnungen sind nicht immer notwendig, aber bei manchen Operatoren sind sie es.(Bei Rechnungen nicht, aber bei Vergleichen z.B. ich setze derartige Ausdrücke daher immer in Klammern)

Zuweisungsoperatoren

Bisher kennen wir nur einen Zuweisungsoperator und zwar den `normalen Zuweisungsoperator` =. Oftmals will man aber eine bestehende Variable verändern, zum Beispiel indem man sagt "addiere zu a 3 dazu". Natürlich könnte man das folgendermaßen bewerkstelligen

a = a + 3;

C++ verfügt hierfür aber über eine Art Kurzschreibweise, welche aus Operatoren besteht, die den Zuweisungsoperator mit allen anderen Operatoren verbinden. So z.B. wäre für unser Beispiel ein Zuweisungsoperator sinnvoll, der den normalen Zuweisungsoperator mit dem Additionsoperator verknüft, also

a += 3; //äquivalent zu a = a + 3;

Typecasts

Das Beispielprogramm sollte nun verständlich sein, aber ein paar Dinge möchte ich dennoch noch ansprechen, folgender Code:

int a(5);
int b(2);
cout << (a/b) << endl;

Was man erwarten würde, wäre die Ausgabe 2.5, aber die Ausgabe lautet 2 - wieso?
Weil a und b ganze Zahlen sind ist auch das Ergebnis der Division eine ganze Zahl, also wird abgerundet und das Ergebnis ist 2. Das ist häufig sogar praktisch und keineswegs lästig, aber manchmal möchte man dann doch eine Gleitkommazahl als Ergebnis.
Um das zu erreichen muss einer der beiden Operanden eine Gleitkommazahl sein, da man das jedoch nicht immer erreichen kann, muss man den Typ der Variable ändern, dazu gibt es die sogenannten Type-Casts:
static_cast, const_cast, dynamic_cast, reinterpret_cast, C-Style-Cast, Function-Style-Cast
C-Style Cast und Function-Style-Cast sind Relikte aus C und werden so verwendet: (double)a bzw. double(a) ändert den Typ von a in double. Die beiden Casts sind jedoch nicht zu empfehlen, da sie zu mächtig sind und großes Potenzial bieten falsch angewendet zu werden. Die 4 C++ Casts können das Selbe(bzw. mehr), verlangen aber vom Programmierer, dass er weiß was er tut.
static_cast ist hier der richtige Cast, es handelt sich hierbei um den Standardcast und alle anderen sind irgendwo gefährlich. Er tut genau das was passieren würde, wenn man eine ganze Zahl einer Variable vom Typ double zuweisen würde, er ändert den Typ im Prinzip semantisch logisch.

int a(5);
int b(2);
cout << (static_cast<double>(a)/b) << endl; //Der Typ von a ist nun double

Arbeiten mit Strings

Nun möchte ich noch den Datentyp string vorstellen und wie man damit arbeitet. Wie bereits angesprochen handelt es sich bei string um einen benutzerdefinierten Typ, das heißt für diesen Zeitpunkt folgendes:

  • Er muss mit einer #include Anweisung zur Verfügung gestellt werden
  • Stringliterale können nicht vom Typ string sein.

Erst einmal zum ersten, das bedeuted, dass oben in der Datei, bei den ganzen anderen #include Anweisungen auch

#include <string>

stehen muss, damit der Datentyp zur Verfügung steht.

Zum zweiten ist zu sagen, dass Stringliterale vom Typ char const[] sind. Aber durch implizite Casts, Konstruktoraufrufe u.ä. ist es problemlos möglich einem string ein Stringliteral zuzuweisen

string s("hi!");

man kann strings genau so ausgeben und einlesen, wie andere Datentypen, sie können aber noch mehr:
der + Operator hat eine spezielle Bedeutung für string(keine Magie, nennt sich Operatorenüberladung - ein Operator kann für andere Datentypen mit anderem Verhalten versehen werden) und zwar eine verbindende, doch vorsicht, das geht nur wenn einer der Operanden eine stringvariable ist, nicht mit beiden Literalen:

string a("hi");
string b(", du");
string c(a + b); //c = "hi, du"
string d(a + ", du"); //d = "hi, du"
string e("hi" + b); //e = "hi, du"
string f("hi" + ", du"); //nicht was man denken könnte!

Ich möchte an dieser Stelle auch noch drauf hinweisen, dass man seine Variablen in einem richtigen Programm niemals so benennen sollte, Variablenbezeichner sollten immer selbsterklärend sein. Die Kunst gut zu programmieren ist es sein Programm nachher noch lesbar, erweiterbar und wartbar zu machen, wenn man allerdings nach 2 Wochen selbst nicht mehr durchblickt, weil man nicht weiß welche Variable was bedeuted, funktioniert das nicht.

lexical_cast

Für eine Möglichkeit Zahlen in Strings umzuwandeln und umgekehrt empfehle ich einen Blick auf Boost