Array und viele Probleme

quink

Grünschnabel
Hi,
ich versuche mich seit einiger Zeit an C++ (mit Code Blocks) und derzeit habe ich ein problem wo ich nicht weiterkomme. Ich hoffe einer von euch kann mir helfen.

Ich hatte vor eine Zahlenkette (vom Typ int) von der Tastatur einzulesen und die einzelnen Zahlen in ein array zu speichern. Klappte, das habe ich gemacht:

C++:
#include <iostream>
#include <sstream>

using namespace std;

int main()
{
  int zahl;
  cout << "Zahl eingeben: ";
  cin >> zahl;

  std::stringstream sstr;
  sstr << zahl;

Eingabe beispielsweise Zahl: 98765
nun wird ja jede einzelne Zahl in ein eigenes array feld gespeichert.
Also hier die 9 in [0], die 8 in [1], usw.
Bis hier ist noch alles ok.

gebe ich beispielsweise zur Überprüfung ein:
C++:
cout << "\nArrary Feld1: " << sstr.str()[0];
cout << "\nArrary Feld2: " << sstr.str()[1];
...
zeigt er mir auch schön in der Ausgabe (in diesem Beispiel) an:
Arrary Feld1: 9
Arrary Feld2: 8
...

erweiter ich nun den Code
C++:
int main()
{
  int zahl;
  cout << "Zahl eingeben: ";
  cin >> zahl;

  std::stringstream sstr;
  sstr << zahl;

  int rechnen = 1;
  rechnen = rechnen + sstr.str()[0];
  cout << "\nErgebnis: " << rechnen;

kommt da nicht 10 (also 1+9) raus sondern: 58 :(

steht im Feld sstr.str()[0] eine 1 kommt 50 raus
steht im Feld sstr.str()[0] eine 2 kommt 51 raus
usw..

er rechnet also falsch.
Nach einigem grübeln und online suchen kam ich drauf das das keine int Zahlen mehr sind im array sstr.str sondern char (denke ich da richtig?)

Nach noch mehr grübeln dachte ich mir, ok, erstellste einfach ein zweites Array, aber als int und übergibts einfach Feld für Feld, also so:
C++:
  int zahl;
  cout << "Zahl eingeben: ";
  cin >> zahl;

  std::stringstream sstr;
  sstr << zahl;
  cout << "\nArrary Feld1: " << sstr.str()[0];
  cout << "\nArrary Feld2: " << sstr.str()[1];

  int array2[10];
  array2[0] = sstr.str()[0];

  int rechnen = 1;
  rechnen = rechnen + array2[0];
  cout << "\nErgebnis: " << rechnen;

Ergebnis ist aber leider immer noch 50 :confused:

Dachte ich mir, ok, einfach rüber schieben ist (leider) nicht, mußte umwandeln. Gesucht, gesucht und das hier probiert:
C++:
  int zahl;
  cout << "Zahl eingeben: ";
  cin >> zahl;

  std::stringstream sstr;
  sstr << zahl;
  cout << "\nArrary Feld1: " << sstr.str()[0];
  cout << "\nArrary Feld2: " << sstr.str()[1];

  int array2[10];
  stringstream Str;
  Str << sstr.str()[0];
  Str >> array2[0];

  int rechnen = 1;
  rechnen = rechnen + array2[0];
  cout << "\nErgebnis: " << rechnen;

das klappt auch, also steht im Feld sstr.str()[0] eine 9 kommt 10 raus, ABER:
C++:
  int zahl;
  cout << "Zahl eingeben: ";
  cin >> zahl;

  std::stringstream sstr;
  sstr << zahl;
  cout << "\nArrary Feld1: " << sstr.str()[0];
  cout << "\nArrary Feld2: " << sstr.str()[1];

  int array2[10];
  stringstream Str;
  Str << sstr.str()[0];
  Str >> array2[0];

  Str << sstr.str()[1];  
  Str >> array2[1];

  int rechnen = 1;
  rechnen = rechnen + array2[0];
  rechnen = rechnen + array2[1];
  cout << "\nErgebnis: " << rechnen;
das klappt nicht, ein weiteres zusätzliches Feld kann ich nicht übergeben, dann rechnet er wieder falsch :(

Was ich nun möchte (ich hoffe ich habs nicht zu umständlich beschrieben):
Im Array sstr.str() habe ich (pro Feld) eine Zahl drin stehen (leider nicht vom Typ int sondern vermutlich char.
Diese Zahlen aus dem Array sstr.str() möchte ich nun alle genauso in ein neues Array vom Typ int haben, das ich damit weiter rechnen kann.

Wenn ihr mir da helfen könnt, ich würde mich freuen.
 

sheel

I love Asm
Hi und Willkommen bei Tutorials.de :)

Du machst es dir etwas zu umständlich mit den Stringstreams...aber alles der Reihe nach.

Vor dem eigentlichen Problem ein kleiner Hinweise zu den aktuellen Codes:
str() von einem Stringstream in jeder Zeile neu aufrufen ist a) ineffizient und b) mehr Schreibarbeit. Den Wert, den man davon bekommt , könnte man in einem std::string speichern und dann damit weiterarbeiten.
Zb: Alt:
C++:
std::stringstream sstr;
sstr << zahl;
cout << "\nArrary Feld1: " << sstr.str()[0];
cout << "\nArrary Feld2: " << sstr.str()[1];
Neu
C++:
std::stringstream sstr;
sstr << zahl;
std::string derstring = sstr.str();

cout << "\nArrary Feld1: " << derstring[0];
cout << "\nArrary Feld2: " << derstring[1];
(und das ist ja nur ein Beispiel, aber vor Ausgeben von [0] und [1] sollte man eigentlich prüfen, ob der String überhaupt lang genug ist. Oder gleich den ganzen String ohne Indizes ausgeben).

---

Zum "Warum das so ist" (wird etwas länger):

Missverständnis 1:
Ob was in char gespeichert wird oder int ist hier nicht das Problem, weil char ist nur ein kleines int.
Ein int hat zB. 32bit (variiert etwas nach Gerät, aber egal) und kann daher zB. Werte von 0 bis (2^32)-1, also 0 bis 4294967295, oder in der signed-Variante von -2^31 bis (2^31)-1 also -2147483648 bis +2147483647, speichern.
Ein char ist üblicherweise komplett gleich, nur mit weniger bit, zB. 8bit: damit kann es Zahlen von 0 bis 255 oder -128 bis +127 speichern.
...
char steht ja eigentlich "character" (Buchstabe), aber das ist so gesehen sehr irreführend. Es speichert Zahlen, sonst nichts. Noch mehr, es gibt keinen Datentyp im Computer, der "Buchstaben" speichert. Der Computer kann nur Zahlen.

Missverständnis 2: Charsets
Mit einem Code wie
C++:
std::string s;
cin >> s;
cout << s;
(also einen String einlesen und gleich wieder ausgeben) und einer Eingabe "hallo" muss man dann wohl irgendwie ein Array von Zahlen statt dem Text haben ... und das wäre (für hallo) 104 97 108 108 11. Warum? Weil es Zeichensätze wie den Ascii-Code gibt, die praktisch ein Regelwerk sind, wie Buchstaben zu Nummern zugeordnet werden. Der Ascii-Code ist einfach als Tabelle darstellbar, zB. http://www.torsten-horn.de/techdocs/ascii.htm (nur der oberste Teil). Das kleine a rechts oben hat (dezimal) die Nummer 97. Daher.

Daher kommt letztendlich der name "char", weil man Arrays von chars gerne zum Speichern von Textnummern verwendet. Der Ascii-Code hat nur 127 Einträge, größere ints wären einfach Speicherverschwendung. (Auch std::string speichert seinen Text in sich in ein char-Array. Es ist nur bequemer zum Verwenden, ein Array selber hat die ganzen Funktionen nicht eingebaut.)

Warum man beim cout dann doch Buchstaben sieht liegt einfach daran, dass das Betriebssystem ein Bilder für jede Asciinummer hat und diese am Bildschirm anzeigt (einfach gesagt).

Warum cout für größere ints doch Nummern anzeigt, statt ein Bild für die Zahl zu suchen? cout in C++ behandelt den C++-Typ char und andere int-Typen einfach anders. Beides sind nur Nummern im Arbeitsspeicher, ohne Info was es ist. Die Art der Behandlung macht der Compiler je nach deinem Code, ob da eben "char" oder "int" steht. char wird direkt ans OS gegeben, zum Buchstabenbildersuchen, bei int wird in cout zuerst eine Umwandlung gemacht, ähnlich zu deinen.

...

Du kannst dir jetzt sicher schon denken, wo das hingeht ... Oben hatte ich als Beispiel "hallo", aber der Asciicode hat nicht nur Buchstaben, sondern auch Satzzeichen, Ziffern, usw. . stringstream wandelt dein int in die passenden Asciinummern um, bereit zum Ausgeben oder sonstwas (so wie cout es für int-Variablen macht). Die Ziffer 0 ist im Asciicode 48, 1 ist 49 usw. ... und mit diesen Nummern versuchst du eben zu rechnen. Geht natürlich, sind auch nur Zahlen. Aber eben nicht so wie du wolltest.

(Kleine Zwischenbemerkung: Zeichensätze sind ein eigenes riesiges Thema für sich, das hat noch nicht mal an der Oberfläche gekratzt. Aber das ist für diese Aufgabe zum Glück unwichtig).

Was man dagegen jetzt tun kann? Da die Ziffern hintereinander kommen, also 0 1 2 3 ist 48 49 50 51 usw., könnte man einfach von jeder Stelle 48 abziehen. (Bzw. '0': 0 in C++ ist die Zahl 0, '0' ist der Buchstabe 0 also 48)
C++:
//einlesen
int zahl;
cout << "Zahl eingeben: ";
cin >> zahl;

//Stringstream-Teil
std::stringstream sstr;
sstr << zahl;
std::string derstring = sstr.str();

//Umwandlung zu rohen Ziffern
for(int i = 0; i < derstring.length(); i++) derstring[i] -= '0';

//jetzt weiterrechnen

//wenn man es später wieder ausgeben will, rückumwandeln
for(int i = 0; i < deranderestring.length(); i++) deranderestring[i] += '0';
Zwei Anmerkungen
a) Statt Stringstream zu verwenden und dann nochmal alles umrechnen zu müssen könnte man auch eine eigene Funktion schreiben, die ein int nimmt und einen std::string gleich direkt mit den richtigen Werten füllt, aber egal.
b) Diese Schleifen funktionieren natürlich nur dann richtig, wenn im String wirklich nur Ziffern sind. Wenn da hallo drinsteht und von jedem Buchstaben 48 abgezogen wird kommt irgendein Müll raus. Aber da du mit cin ja auch ein int einliest, würde eine Nichtzahl schon da Müll ergeben... also hier auch egal.
 
Zuletzt bearbeitet:

quink

Grünschnabel
Hi,
vielen vielen Dank für deine ausführliche und gut erklärte Anleitung. Das bringt eine ganze Menge Licht hinein. Aber es ist auch viel, ich muß das erstmal richtig verstehen und mal durch programmieren. Aber ich werde dann noch ausführlicher hier dazu antworten,
viele Grüße
 

quink

Grünschnabel
Hi,
ich habs mal durchprobiert und nun klappt es so wie ich möchte, vielen Dank dafür :)

Aber mein Compiler (Code Blocks, Windows) gibt eine Warnung aus:

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

Als Warnung zeigt er mir diese Zeile an:
for(int i = 0; i < derstring.length(); i++) derstring -= '0';

wobei ich die Anweisung:
derstring -= '0';
nicht wirklich verstehe. Kannst du mir die erklären was die genau macht?

oben hast du geschrieben:
Ein int hat zB. 32bit (variiert etwas nach Gerät, aber egal) und kann daher zB. Werte von 0 bis (2^32)-1, also 0 bis 4294967295, oder in der signed-Variante von -2^31 bis (2^31)-1 also -2147483648 bis +2147483647, speichern.

bezogen auf die Warnung versteh ich das nun nicht mehr. Die größe reicht doch bei beiden locker aus, warum meckert er? Und signed und unsigned, hat das praktischen Nutzen, ich meine wann brauche ich was?


Und noch zu diesen Stringstreams, gibt es noch eine andere Methode so eine Zahlenkette zb. 98765 einzulesen und direkt in ein Array zu verschieben?
Das schöne was ich am Stringstream Array oben ja mag, ich muß vorher nicht ein Array mit der Größe X definieren, es hängt ja hier von der Eingabe ab (wieviele Zeichen eingegeben werden) und soit wie groß das Array wird.

Ich habe übrigens dazu in verschiedenen online frei erhältlichen C++ Büchern nichts zu Stringstream und diesem einlesen mit std:: gefunden.
Verschiedene Typumwandlungen von char zu int habe ich zwar gefunden, auch ausprobiert, aber entweder kamen nur Fehler vom Compiler oder ich konnte nur ein Feld aus dem stringstream auslesen, umwandeln und in ein int schreiben. Beim nächsten Feld wieder Fehler vom Compiler.

Und was du hier schriebst:
(Kleine Zwischenbemerkung: Zeichensätze sind ein eigenes riesiges Thema für sich, das hat noch nicht mal an der Oberfläche gekratzt. Aber das ist für diese Aufgabe zum Glück unwichtig).

Ja, je mehr ich da nun kratze umso weniger versteh ich ehrlich gesagt. Kennst du da Online Seiten/Bücher wo das ausführlich (am besten in deutsch) mal für blöde Anfänger erklärt wird?
 

quink

Grünschnabel
Ich habe noch eine Frage zu deinem Stück Code:

C++:
std::string s;
cin >> s;
cout << s;

Du hast oben geschrieben das bei einer Eingabe von hallo im Grunde 104 97 108 108 11 gespeichert wird, aber hallo wieder ausgibt. Er speichert das also als dezimal (als hexadezimal wäre das hallo ja 68 61 6c 6c 6e).
Warum speichert er als dezimal, als reine Verständnisfrage. Ich war der Überzeugung das er intern alles als hexadezimal speichert.
Und wenn stringstream es im grunde als dezimal 104 97 108 108 11 einliest und als Bild in s speichert und cout es als Bild hallo wieder ausgibt. Gibt es einen anderen Befehl anstatt cin und cout der es als dezimal einliest und wieder ausgibt?
 

sheel

I love Asm
Hi

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

Als Warnung zeigt er mir diese Zeile an:
for(int i = 0; i < derstring.length(); i++) derstring -= '0';
Das hat in dem Fall nichts mit "derstring[ I ] -= '0';" zu tun, sondern mit der for-Schleife. Schnelle Lösung: Statt "int i = 0" "size_t i = 0" schreiben.

Warum? (lang)
...
Wie bisher schon bekannt ist, gibt es in C++ verschiedene int-artige Datentypen, zB. int und char, die sich normalerweise (normalerweise!) nur in der Größe, also der Bitanzahl, unterscheiden. Üblich sind 1 Byte bzw. 8bit für char, und zB. 4 Byte bzw. 32bit für int. Dann noch short mit 2 Byte und long mit 8 Byte ... zumindest in einer Idealen Welt.
Es gibt es nämlich keine Vorschrift, dass das in Compiler und Hardware immer so sein muss. Übliche Privatcompuer heute haben Prozessoren, die mit 8/16/32/64-bit-Werten umgehen können. Wer bestimmt, dass int 32 bit hat statt zB. 64? Und was ist mit anderen (selteneren) Geräten, bei denen die möglichen Größen 17/40/56 sind? (erfundene Zahlen, aber ähnlich verrückte Sachen gibt es wirklich).

Was Sprachen wie Java da machen: Festlegen "int hat 32bit" usw., und wenn das auf einer bestimmten Geräteart nicht möglich ist gibt es eben kein Java dafür (kein Compiler usw.). C++ ist da flexibler: Es gibt schon bestimmte Mindestanforderungen (zB. dass ein unsigned char "mindestens" die Werte von 0 bis 255 speichern kann, unsigned short mindestens bis 65536 usw.), aber sonst ist ziemlich viel offen gelassen. Auf normalen Computern kann ein int zB. je nach Compiler usw. manchmal 32 und manchmal 64 bit haben. Für die anderen Geräte, ein 17bit char ist genauso wie ein 8bit char erlaubt (nur zB. 5bit geht nicht weil man da keine 255 verschiedenen Werte reinbringt). Kann alles passieren. (Es gibt auch noch andere mögliche Unterschiede außer der Größe, ist hier aber egal.).

Was also char, short, int, long und long long genau sind, ist aus C++-Programmierersicht offen gelassen (außer man will das Programm für nur ein bestimmtes Gerät schreiben und kümmert sich nicht drum, ob es auch anderen Computern/Betriebssystemen/... auch funktioniert). Letztendlich muss sich also der Programmierer also damit herumärgern, welcher Datentyp für die erwarteten Zahlen sicher geeignet ist, und welcher nur vielleicht (diese besser vermeiden, klar). Wenn man sowas wie ASCII-Werte, Geburtsjahre, Lebensmittelpreise usw. speichern will, ist das auch kein Problem: Es gibt 127 ASCII-Einträge, ein Geburtsjahr von Menschen heute wird wohl irgendwo zwischen 0 und 9999 liegen, Lebensmittelpreise werden nie 4 Milliarden (3294967296) übersteigen usw. Mit solchen Kriterien findet man immer einen geeigneten Typ.

Andere Sachen sind aber nicht so leicht bestimmbar: Was ist, wenn man (wie in der Schleife hier) Elemente von einem String bzw. Array durchzählen will? Wie groß kann ein Array auf Gerät X sein, bzw. wie viel Arbeitsspeicher hat es? Müsste man ja wissen, um einen passenden Typ zu finden, und man weiß es eben auch nicht. (Und ja, dass ist ein wirkliches Problem. Es war noch nicht so lang her, dass viele C++-Programmer 4Byte-int für Arraygrößen, Stringlängen usw. verwendet hat (und keiner hat sich Gedanken gemacht, dass das irgendwann nicht mehr reicht). Dann sind Computer mit mehr als 4GB Arbeitsspeicher gekommen (4296946296 Byte = 4GB).... ähnliche Probleme vorher mit dem 2-4 Sprung usw.). Die bisher genannten Typen haben auch überhaupt keinen Bezug zu Arraygrößen, es gibt also keine C++-Vorschrift wie "das, was ich [Compiler] als int verstehe reicht sicher für Arraygrößen".

Die Rettung sind Zusatztypen für bestimmte Anwendungsfälle, wie zB. "size_t". size_t ist so wie int ein Variablentyp für den Compiler, von dem man auch eben Variablen anlegen kann usw.. Das Besondere ist die Größe: Es wird immer so festgelegt, dass es für Arraygrößen reicht.. Mit anderen Worten, size_t für Durchzählvariablen verwenden ist sicher immer richtig.
(Oft ist "size_t" das Selbe wie "unsigned int", aber eben nicht immer).

---

Zu "derstring[i] -= '0';".

Im C++-Code gibt es einige verschiedene Werteschreibweisen.
a) 1 und 1234 sind Zahlen.
b) "1" und "1234" und "hallo" sind Strings bzw. char-Arrays gefüllt mit Ascii-Nummern.
c) '1' und 'a' sind Einzelbuchstaben. Das einer Variable zugewiesen ergibt den Ascii-Wert in der Variable.
(Eigentlich ist alles noch viel genauer differenzierbar, zB. kann man bei Strings auch ein paar Charsets außer dem ASCII angeben, usw.usw.)

"derstring[i] -= '0';". bzw. "derstring[i] = derstring[i] - '0';".subtrahiert also den ASCII-Code der Ziffer 0 von derstring[i] und ist damit gleichbedeutend zu "derstring[i] -= 48;". bzw. "derstring[i] = derstring[i] - 48;".

Da die Ziffern im ASCII-Code hintereinander sind (0 1 2 3 wäre 48 49 50 51) bekommt man durch abziehen von 48 die eigentliche Zahl heraus.

Bisher klar...?

Warum '0' und nicht 48 im Code:
Erstens ist es klarer, was gemeint ist. Ist da 48 wegen dem Ascii-Code von 0 oder weil es die Türchenanzahl von zwei Adventskalendern ist? Blödes Beispiel, ja, aber wenn man fremden Code mit irgendwelchen unerklärten Nummern sieht kann man manchmal wirklich Probleme haben, den Grund zu verstehen.
Zweitens, selten aber doch, kommt man auch zu Compilern und Geräten, wo '0' nicht 48 ist, also keine ASCII-Nummern verwendet werden. Für hier eigentlich komplett unwichtig, aber nur als Hinweis. EBCDIC usw. ist zwar beinahe ausgestorben, exisitert aber noch.

("Eigentlich" ist das Problem ja viel größer: Wie oben gesagt ist der ASCII-Code eigentlich nicht alles an den Charsets, im Gegenteil. Weil so ziemlich alle anderen Sprachen außer Englisch Probleme damit hatten (zB. gibt es kein äöüß, kein Griechisch, Russisch, Japanisch, usw.usw.) ist der pure ASCII-Code eigentlich beinahe nirgends mehr in Verwendung, andere Nummerierungssysteme haben ihn abgelöst. Zum Glück haben alle modernen Systeme die Ziffern auch auf 48, 49 usw., Daher kein Problem hier. Bzw. es wurde gerade deswegen so gemacht, dass es damit kein Problem gibt.)

---

Und noch zu diesen Stringstreams, gibt es noch eine andere Methode so eine Zahlenkette zb. 98765 einzulesen und direkt in ein Array zu verschieben?
Man könnte auch generell Text einlesen und dann prüfen, ob es eine Zahl ist. (das hat aber nichts mit Stringstreams zu tun. cin, cout, getline usw.).

Das schöne was ich am Stringstream Array oben ja mag, ich muß vorher nicht ein Array mit der Größe X definieren, es hängt ja hier von der Eingabe ab (wieviele Zeichen eingegeben werden) und soit wie groß das Array wird.
Auch das hat nichts mit den Stringstreams zu tun. std::string ist das (und ja, die Klasse ist sehr angenehm :)).
Die Stringstreamklasse kann zwar für so Umwandlugnen gebraucht (missbraucht?) werden, ist aber allgemeiner dazu gedacht, Strings (bzw. char/Byte-Arrays) wie einen (Datei)stream etc. behandeln zu können (daraus lesen/schreiben).

Ja, je mehr ich da nun kratze umso weniger versteh ich ehrlich gesagt. Kennst du da Online Seiten/Bücher wo das ausführlich (am besten in deutsch) mal für blöde Anfänger erklärt wird?
a) Dürfte hier überhaupt nicht helfen. Wenn es Fragen gibt, bitte immer her damit :), aber das hier hat mit Zeichensätzen selber so viel zum tun wie ein Schraubenzieher mit einem Dampfschiff.
b) Leider kenn ich nichts. Eine einzelne Erklärung, die anfängerfreundlich ist und trotzdem fortgeschrittene Sachen zumindest erwähnt, kenn ich nichtmal auf Englisch.

Warum speichert er als dezimal, als reine Verständnisfrage. Ich war der Überzeugung das er intern alles als hexadezimal speichert.
Weder noch :)
Dezimal, hexadezimal usw. ist alles nur eine Frage der Betrachtungsweise.
Gespeichert wird eine Zahl (punkt aus). Wenn ich das einfach so ausgeben lasse ist es dezimal. Mit einem etwas anderen Ausgabebefehl ist es hexadezimal. Wenn man die Stromzustände auf den Leitungen im Prozessor anschaut ist es binär. Im Arbeitsspeicher, Festplatte usw. ist je nach Technologie binär oder irgendwas anderes.
...
Umwandlungen zwischen Prozessor, RAM usw. haben die Teile eingebaut.
Um ein int in irgendeinem System auszugeben (bzw. die Ziffern darin auszurechnen) reichen Befehle mit Addition, Division usw., die der Prozessor auch eingebaut hat.

Und wenn stringstream es im grunde als dezimal 104 97 108 108 11 einliest und als Bild in s speichert und cout es als Bild hallo wieder ausgibt. Gibt es einen anderen Befehl anstatt cin und cout der es als dezimal einliest und wieder ausgibt?
Es wird nichts als Bild gespeichert usw.
Zwischen Tastatur, CPU und RAM laufen nur Zahlen herum.
Was der Bildschirm für Pixel wie einfärben soll können diese Zahlen von sich aus aber nicht sagen; dafür gibt es seitens Betriebssystem eine Bildersammlung für die Nummern.
Zu den anderen Befehlen, vielleicht solltest du alles nochmal langsamer lesen...