Allgemeine Fragen zu Programmierübungen

coder111

Mitglied
Ich habe versucht die oben genannte Aufgabe zu lösen. Allerdings werden keine 16 Buchstaben in einer Zeile ausgegeben (Siehe Bild).
Außerdem wird nicht der Rest ausgegeben aber ich weiß auch warum das so ist. Wenn ich noch den Rest mplementiere, dann habe ich insgesamt 4 Schleifen.
Es gibt bestimmt eine bessere Lösung?



abcdefghij klmnop
qrstuvw xyz
ABC DEF GHIJ
KLM NOP QRS TUVW
XY
Z


C++:
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
using namespace std;

int main() {
    string dateiname = "";
    char c = ' ';
    const string codierung{ "0123456789ABCDEF" };
    int ctr = 0;
    vector<int> v(16);
    vector<string> zeichen(16);

    cout << "Dateiname: ";
    cin >> dateiname;

    ifstream quelle( dateiname, ios::binary );

    while (quelle.get(c)) {
        zeichen.at(ctr) = c;
        v.at(ctr)= static_cast<int> (c);
        ctr++;
        if (ctr == 15) {
            for (int i = 0; i < 16; i++) {
                cout << zeichen.at(i);
            }
            cout << "\n";
            for (int i = 0; i < 16;i++) {
                cout << codierung.at(v.at(i)/16) << codierung.at(v.at(i)%16);
            }
            cout << "\n";
            ctr = 0;
        }
    }
    cout << "\n";
    getchar();
    quelle.close();
    getchar();
    return 0;
}
 

Anhänge

  • Unbenannt.PNG
    Unbenannt.PNG
    6,4 KB · Aufrufe: 4

cwriter

Erfahrenes Mitglied
Hi

Ein Vektor ist ein bisschen Overkill, findest du nicht?
C++:
char buf[16];

size_t filecntr = 0;
while(filecntr < 16) {
    if(!quelle.get(buf[cntr])) break;
    cntr++;
}
size_t cntr = 0;
while(cntr < filecntr) {
    std::cout << "  " << buf[cntr]; //"  ", damit das alignment stimmt (ein char ist 1 Zeichen breit, als hex aber 2)
    cntr++;
}
std::cout << std::endl;
cntr = 0;
while(cntr < filecntr) {
    char c = buf[cntr];
    std::cout << " " << codierung.at(c/16) << codierung.at(c%16); //" ", damit ein space bleibt (macht das Lesen einfacher)
    cntr++;
}
std::cout << std::endl << std::endl;
Und das Ganze noch loopen, bis die Datei vollständig gelesen wurde.

Weniger als 4 Schleifen: Ja, auf Kosten des Speicherverbrauchs kannst du beide Repräsentationen in einem Loop berechnen, abspeichern und dann ausgeben. Allerdings ist das Ausgeben auch ein Loop, du gewinnst also nichts.

Kompiliert dein Code überhaupt?

Gruss
cwriter
 

cwriter

Erfahrenes Mitglied
Allerdings, weiß ich nicht, was an meinem Code falsch ist?

C++:
vector<string> zeichen(16);
Du erstellst einen Vektor von 16 strings.

C++:
zeichen.at(ctr) = c;
Du setzt einen String (nämlich den String "zeichen[ctr]") auf einen char. Der überladene Operator '=' erlaubt das in diesem Falle sogar (halte ich ja für bad behavior, aber naja.)

Dann machst du wieder ein paar überflüssige Dinge mit int-Casts und so, aber das habe ich ja schon anfangs erwähnt.

C++:
           for (int i = 0; i < 16; i++) {
                cout << zeichen.at(i);
            }
Hier ist es ganz schlimm: Du gibts die Strings aus. Die Strings sind aber nur 1 Zeichen lang. Also hast du soeben erfolgreich mindestens 4 Bytes verschwendet (mal von der teuren Konvertierung von char zu std::string abgesehen). Und das 16 Mal.
Nicht semantisch falsch, nur sträfliche Sloppyness.

Dein Problem ist aber ein anderes: Du erhöhst den Counter zu früh.

C++:
        //ctr = 0
        zeichen.at(ctr) = c;
        v.at(ctr)= static_cast<int> (c);
        ctr++;
        if (ctr == 15) {/* ...*/}
Machen wir es einfacher:
C++:
        //ctr = 0;
        zeichen.at(ctr) = c;
        v.at(ctr)= static_cast<int> (c);
        ctr++;
        if (ctr == 1) {/* ...*/}
Nun sollte es offensichtlich sein: Du liest zuerst einen Wert ein, dann erhöhst du den Counter (sagst also "Ich bin jetzt bei x").
Und dann prüfst du, ob du die Nummer x schon gelesen hast. Hast du aber nicht, denn du hast erst gesagt, dass du als nächstes x lesen wirst.
Ein klassisches Inclusion-Exclusion-Problem also.
Zwei Möglichkeiten: Du erhöhst setzt ctr == 16 (statt ctr == 15) als if-Bedingung, oder du setzt ctr++ in den else-Block derselben.

Das löst das Problem aber nur teilweise, denn das Ausgeben eines '\n' erzeugt nunmal eine neue Zeile.
Hier kannst du nur versuchen, mittels isalnum() o.ä. zwischen speziellen und normalen Zeichen zu unterscheiden (und im Falle von speziellen Zeichen nur ein Leerzeichen auszugeben).

Ansonsten ist dein Code ziemliches Flickwerk. Lernen durch Ausprobieren ist gut und wichtig, aber ein bisschen Logik sollte schon reinfliessen (Ein Vektor von Strings ist eigentlich ein Vektor von Vektoren*, und das brauchst du für diese Aufgabe sicher nicht).

Und das mit dem Casting: Lass es. Es bringt wirklich genau gar nichts (ausser für die Ausgabe natürlich, aber da kannst du lokal casten, sodass du nicht so viel Speicher brauchst).

Ich kann es dir auch vorrechnen:
Du brauchst zwei Vektoren. Jeder Vektor selbst ist (bis auf wenige Ausnahmen) mindestens 8 Bytes gross.
Dann hast du im einen wiederum mindestens 8 Bytes pro Element -> 128 Bytes für deinen Stringvektor, plus je mind. 1 Byte für die chars darin = 144 Bytes.
und im anderen hast du ints, also mindestens 4 Bytes -> 64 Bytes für deinen Intvektor. (Von diesen 64 Bytes nutzt du aber nur einen Viertel, also 16 Bytes für Daten (der Rest ist 0)).

Mein Code braucht nur 16 chars, also 16 Bytes an Speicher.
144 + 64 = 208, 208 / 16 = 13.
Du brauchst also mehr als 10x soviel Speicher wie eigentlich nötig.
Bei kleinen Programmen wie diesen ist das egal, aber würdest du ein ähnliches Programm für die Analyse einer grossen (4GB) Datei schreiben (und alles würde gleich skalieren (was es in der Realität selten tut)), bräuchtest du 52(!)GiB Arbeitsspeicher, während es in 4 GB Arbeitsspeicher möglich wäre. Wie viel Arbeitsspeicher hast du zur Verfügung? Wie viel Arbeitsspeicher hat ein Durchschnittscomputer?
Daher ist es wirklich nicht ok, solchen Code zu schreiben. In C und C++ gilt: Jeder Wert kann (fast) alles sein. Durch casts. Du musst nicht alles unterschiedlich abspeichern, sondern einfach nur anders anschauen.

Gruss
cwriter

*) Ja, stimmt nicht ganz, aber das wäre dann echte Haarspalterei.
 
Zuletzt bearbeitet:

coder111

Mitglied
Wann soll ich denn Vektoren und wann Arrays benutzen?

Diese Überprüfung verstehe ich nicht. Was ist mit einem nicht druckbaren Zeichen gemeint?
Und warum wird das auf < ' ' überprüft?
C++:
 if (c < ' ') { // nicht druckbares Zeichen
      c = '.';
    }
 
Zuletzt bearbeitet:

cwriter

Erfahrenes Mitglied
Wann soll ich denn Vektoren und wann Arrays benutzen?
Ein Vektor ist ein skalierbarer Array. Generell muss man zwischen Stack- und Heaparrays unterscheiden: Erstere sind extrem schnell, müssen aber vergleichsweise klein sein, da sie sich den Platz mit den Funktionsframes teilen (geht eher Richtung Assembly / Computer Architecture, aber vereinfacht gesagt muss ja eine Variable, die du in einer Funktion definierst, irgendwo gespeichert werden, und dafür gibt es den Stack).
Heaparrays hingegen können sehr gross sein, erfordern aber ein Betriebssystem, das den Speicher zuweist. Allerdings muss man sich da selbst um das Aufräumen kümmern.
Da Stackarrays nun auf dem Callstack sein müssen (bzw. meistens sind, rein theoretisch ist das nicht vorgeschrieben), kannst du sie nicht vergrössern, da ja direkt danach andere Daten kommen können. Heaparrays hingegen sind indirekt und der Pointer ist immer konstant gross (der Speicherbereich, worauf gezeigt wird, muss es nicht sein). Daher kann man unter C mit realloc() den Speicherbereich in der Grösse verändern.

Ein C++ vector ist nun eigentlich nur ein Heaparray mit der Funktionalität, den Speicherbereich automatisch zu vergrössern und verkleinern, je nachdem, wie viele Elemente gerade drin sind.
Allerdings sind vectors sehr teuer, wenn man oft Elemente einfügen will, da sie de facto Arrays sind.

Als Grundregel (Kein echtes Flussdiagramm, aber fast):
Code:
(Daten <= 4096 bytes) - Nein -> (Grösse konstant?) -Nein-> Vector
     | Ja                                  | Ja
     V                                     V
(Grösse konstant?) -Nein-> Vector      Heaparray
     | Ja
     V
Stackarray
In manchen Fällen machen auch andere Grössen Sinn, aber das muss in jedem Fall erneut eruiert werden.

Diese Überprüfung verstehe ich nicht. Was ist mit einem nicht druckbaren Zeichen gemeint?
Und warum wird das auf < ' ' überprüft?
Woher hast du denn den Code?

Sieh dir mal die ASCII-Tabelle an:
http://www.asciitable.com/index/asciifull.gif
Die erste Spalte (0-31) sind alles spezielle, teils nicht-druckbare Zeichen.
Diese sind heute grösstenteils veraltet. Sie hatten damals den Vorteil, dass man eine Datei mit dem EOF-Zeichen beenden konnte, also nicht wissen musste, wie gross die Datei ist, als man mit Lesen begann. Diese Art der Dateispeicherung ist aber sehr unsicher und wurde zurecht verworfen.
Andere nicht-druckbare Zeichen wie LF ('\n') werden noch rege verwendet, auch wenn sie eigentlich kein Zeichen sind. Überlege dir doch mal, dass dieses Zeichen eine neue Zeile erstellt, und daher stark von den anderen Zeichen abweicht (normalerweise wird ein Zeichen eingefügt und die x-Position des Cursors um 1 nach rechts verschoben. Wie ist es beim LF? Und: Warum gibt es die drei "Philosophien", dass eine neue Zeile "\n" bzw. "\r\n" bzw. "\r" ist?)

Grundsätzlich ist es durchaus möglich, mit dieser Anweisung alle nicht-druckbaren Zeichen auszuschliessen, aber es gibt auch die fertigen Funktionen isalnum() (für Buchstaben/Zahlen/Ausgewählte Sonderzeichen), isspace() (Für alle whitespaces), isdigit() für alle Ziffern, isalpha() für alle Buchstaben.
 

coder111

Mitglied
Ich bin gerade am verzweifeln. seit Stunden sitze ich an der gleichen Aufgabe und weiß immer noch nicht, was mein Fehler ist. Meine Lösung habe ich
nochmal überarbeitet. Mich stört die Formatierung der Ausgabe.
Ich will es so haben, dass zuerst die Buchstaben und nebendran die Hexziffern ausgegeben werden.

C++:
#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
using namespace std;

int main() {
    const int ZEILENLAENGE = 16;
    const string hexadezimal{ "0123456789ABCDEF" };

    string dateiname = "";
    string buchstaben ="";
    string hexcode = "";
    char c = ' ';
    int ctr = 0;
   

    cout << "Dateiname: ";
    cin >> dateiname;
    ifstream ifs (dateiname,ios::binary);

    if (! ifs.good()) {
        cout << "Datei konnte nicht geoeffnet werden";
    }
    while (ifs.get(c)) {
        if (ctr== ZEILENLAENGE) {
            cout << buchstaben << "    " << hexcode << "\n";
            buchstaben = "";
            hexcode = "";
            ctr = 0;
        }
        buchstaben += c;
        hexcode = hexcode + hexadezimal.at(c / 16) + hexadezimal.at(c % 16) + " ";
        ctr++;
    }
    getchar();
    ifs.close();
    getchar();
    return 0;
}
 

Anhänge

  • Unbenannt.PNG
    Unbenannt.PNG
    7,9 KB · Aufrufe: 5

cwriter

Erfahrenes Mitglied
Mich stört die Formatierung der Ausgabe.
Ich will es so haben, dass zuerst die Buchstaben und nebendran die Hexziffern ausgegeben werden.
Nebendran oder direkt darunter oder interleaving, also Zeichen - Hex - Zeichen - Hex?
Nebendran hast du ja schon (ausser natürlich, dass du auch nicht-druckbare Zeichen auszugeben versuchst,
was natürlch im Falle eines '\n' auch eine neue Zeile erzeugt).

C++:
while (ifs.get(c)) {
        if (ctr== ZEILENLAENGE) {
            cout << buchstaben << "    " << hexcode << "\n";
            buchstaben = "";
            hexcode = "";
            ctr = 0;
        }
        buchstaben += isprint(c) ? c : ' '; //Dasselbe wie if(isprint(c)) buchstaben += c; else buchstaben += ' ';
        hexcode += hexadezimal.at(c / 16) + hexadezimal.at(c % 16) + " ";
        ctr++;
    }
Die hexadezimale Ausgabe erzeugt nur darum eine neue Zeile, weil dein Fenster nicht breit genug ist (daher gibt man unterschiedliche Darstellungen oft auch untereinander an).

Gruss
cwriter
 

cwriter

Erfahrenes Mitglied
Ich habe in der Datei aber nur Buchstaben, Leerzeichen und Zeilenumbrüche. Das sind doch alles druckbare Zeichen?
Definitionssache.
Der C-Standard sagt nein:
http://www.cplusplus.com/reference/cctype/isprint/
For the standard ASCII character set (used by the "C" locale), printing characters are all with an ASCII code greater than 0x1f (US), except 0x7f (DEL).
Da 0x1f = 16 + 15 = 31 und '\n' = LF = 10 = 0x0A -> Nope, newline ist nicht "printable".
Im Prinzip heisst "printable", dass a) eine unbestimmte Anzahl Pixel (auch 0) gesetzt werden müssen, b) das nächste Zeichen eine Position weiter rechts kommen muss, und c) dass kein Nebeneffekt (z.B. Tonausgabe bei BELL) entstehen darf.

//Bevor ich noch gelyncht werde: cplusplus.com hat nicht immer Recht (und ist auch nicht der Standard), aber bei der Effektbeschreibung stimmt es meistens.

Gruss
cwriter