Der Bit-Editor
in der Konsole
Ich möchte hier jedem einen leichten Einstieg in die Programmierung ermöglichen. Jedes gängige Tutorial ist entweder "staubtrocken" oder "schwer geschrieben" (für Studierende). Außerdem befasst sich so gut wie jedes Tutorial nur mit theoretischer Programmierung, die man aber niemals so umsetzen würde. Hier möchte ich einhaken und jedem Projekte nahebringen, die nicht einfach anzueignen sind, aber schon recht schnell Erfolge zeigen.
Trotzdem ist dieses Tutorial an alle gerichtet die zumindest die klassischen Kontrollstrukturen beherrschen.
https://www.w3schools.com/cpp/ -englisch
http://www.online-tutorials.net/c-c++-c/c++-tutorial-teil-1/tutorials-t-1-58.html
Ich gehe hier aber trotzdem nicht auf alles ein und möchte hier schonmal weiterführende Thematik verlinken.
Dualsystem:
https://de.wikipedia.org/wiki/Dualsystem
https://www.inf-schule.de/informati...on/binaerdarstellungzahlen/konzept_dualsystem
Hexadezimalsystem:
https://de.wikipedia.org/wiki/Hexadezimalsystem
https://www.inf-schule.de/informati...erdarstellungzahlen/konzept_hexadezimalsystem
Oktalsystem:
https://de.wikipedia.org/wiki/Oktalsystem
Bitmanipulation:
https://www.c-howto.de/tutorial/variablen/bitmanipulation/ C
https://www.monsterli.ch/blog/programmierung/cpp/c-c-bitmanipulation-mit-bitweise-operatoren/ C++
Warum sollte ich mich überhaupt damit befassen?
Zum einen ist das absolutes Grundwissen und wer den Bit-Editor oder die Bitoperationen verstanden hat, kann daran aufbauen. Weiterführende Projekte wären z.B.:
- Kryptographie für Dateien oder Netzwerktechnik
- Ein Hex-Editor
Eine Einführung in die Stellenwertsysteme
Das Binär- oder Dualsystem hat die Basis 2 und 2 Zustände: 0 und 1. Es hat nur 1 Bit.
Das Hexadezimalsystem wird im Stellenwertsystem mit der Basis 16 dargestellt. Das heißt ein Hexadezimalsystem besteht aus 4 Bits. Binär:1111=15. Mit 0000 sind das 16 Zustände. Da wir in der Programmierung aber nur mit Byte-Datentypen arbeiten, müssen wir diese immer mindestens in einem Paar darstellen. Ein 1Byte großer Datentyp wäre zum Beispiel ein char. Daher seht ihr normalerweise immer die Darstellung A1(10100001) oder bei Farben z.B. #FF0000 (rot) [3Bytes = 11111111 00000000 00000000]. Wie ihr vielleicht schon seht hat das Hexadezimalsystem eine weitere Besonderheit. Es stellt außer Zahlen auch Buchstaben dar. Das liegt daran, dass das Hexadezimalsystem alle Zahlen nach 9 mit Buchstaben darstellt. Also besteht das Hexadezimalsystem aus folgenden Ziffern: 0123456789ABCDEF.
Das Oktalsystem hat die Basis 8. Ich kann also ein Oktal in 3 Bits darstellen. Binär:111=7. Wie oben schon genannt haben wir also mit 000 wieder 8 Zustände.
Das Dezimalsystem hat die Basis 10 und ist soweit jedem bekannt, darauf muss ich hier jetzt nicht weiter eingehen.
Zur Veranschaulichung nochmal eine Tabelle mit einer 4-Bit-Darstellung zum besseren Verständnis.
Dezimal | Oktal | Hexadezimal | Dual |
---|---|---|---|
0 | 0 | 0 | 0000 |
1 | 1 | 1 | 0001 |
2 | 2 | 2 | 0010 |
3 | 3 | 3 | 0011 |
4 | 4 | 4 | 0100 |
5 | 5 | 5 | 0101 |
6 | 6 | 6 | 0110 |
7 | 7 | 7 | 0111 |
8 | 10 | 8 | 1000 |
9 | 11 | 9 | 1001 |
10 | 12 | A | 1010 |
11 | 13 | B | 1011 |
12 | 14 | C | 1100 |
13 | 15 | D | 1101 |
14 | 16 | E | 1110 |
15 | 17 | F | 1111 |
Programmierparadigma
Es gibt verschiedene "Wege" zu programmieren. Wie z.B. die prozedurale, modulare oder objektorientierte (OOP) Programmierung. Ich gehe hier zwar auf alle ein, werde mich aber auf die objektorientierte und modulare konzentrieren, da sie eine bessere Wartbarkeit gewährleisten. Wenn wir später unseren Code bearbeiten möchten, können wir beispielsweise direkt in das Modul "springen" und diesen Teil verändern.
Weiterführende Literatur:
https://de.wikipedia.org/wiki/Programmierparadigma
https://www.it-talents.de/blog/it-talents/prozedurale-vs-objektorientierte-programmierung
prozedural:
https://de.wikipedia.org/wiki/Prozedurale_Programmierung
https://www.itwissen.info/Prozedurale-Programmierung-procedural-programming.html
https://softech.cs.uni-kl.de/homepage/teaching/SeIWS0910/SeIWS0910Material/SE4_4.pdf -code
OOP:
https://de.wikipedia.org/wiki/Objektorientierte_Programmierung
https://programmieren-starten.de/blog/objektorientierte-programmierung/
https://www.kompf.de/cplus/tutor.html -code
modular:
https://de.wikipedia.org/wiki/Modulare_Programmierung
Coding
Anmerkung: Die hier gezeigten Codebeispiele entsprechen nicht "gutem" Programmierstil. Ich habe einen eigenen Programmierstil, der mir persönlich gefällt. Bitte nehmt euch kein Beispiel daran!Programmierstil:
https://medium.com/@tristan.lins/programmierstil-34a12c1b7820
http://users.informatik.uni-halle.de/~abgnr/stuff/guterprogrammstil.pdf
Prozeduale Programmierung
Die Standardbibliothek - Teil 1
Jetzt beschäftigen wir uns mit dem eigentlichen Code. Doch wie realisieren wir dies? Die einfachste und vielleicht beste Methode ist einfach die Standardbibliothek zu verwenden. Die Vorteile von bestehenden Bibliotheken sind:
-sie sind stabil, da sie mehrfach durchgetestet sind
-sie sind schnell, da sie daraufhin programmiert und getestet sind
https://de.wikipedia.org/wiki/C++-Standardbibliothek
bitset
Um unsere Variable binär darzustellen gibt es die bitset-Funktion. Diese benötigt den bitset-Header. Diesen bindet ihr über
#include <bitset>
ein. Wir können die Binärdarstellung also einfach über folgenden Code realisieren:
C++:
#include <bitset>
#include <iostream>
int main()
{
int i = 254;
std::cout << std::bitset<32>(i) << std::endl;
return 0;
}
Output:
Code:
00000000000000000000000011111110
bitset:
https://docs.microsoft.com/de-de/cpp/standard-library/bitset-class?view=msvc-160
https://en.cppreference.com/w/cpp/utility/bitset -englisch
Ausgabe-Manipulatoren
Die Standard-Bibliothek
#include <iostream>
hat Ausgabe-Manipulatoren um das Dezimalsystem std::dec
, Hexadezimalsystemstd::hex
und Oktalsystem std::oct
darzustellen. Wir erweitern unser vorherigen Code mit der Binärdarstellung also wie folgt:
C++:
#include <bitset>
#include <iostream>
int main()
{
int i= 254;
std::cout << "bin:" << std::bitset<32>(i) << std::endl;
std::cout << "dec:" << std::dec << i << std::endl;
std::cout << "hex:" << std::hex << i << std::endl;
std::cout << "oct:" << std::oct << i << std::endl;
return 0;
}
Output:
Code:
bin:00000000000000000000000011111110
dec:254
hex:fe
oct:376
Stream-Manipulatoren:
https://www.kompf.de/cplus/artikel/stream2.html
https://en.cppreference.com/w/cpp/io/manip -englisch
Bitoperationen
des bitset-Containers
bitset<4> bs;
Bit setzen:
bs.set(2,1);
-setzt das 2. Bit auf 1Bit löschen:
bs.reset(1);
-löscht das 1. BitBit invertieren:
bs.flip(2);
-invertiert das 2. BitEs wird dabei von rechts nach links und von 0 (0-sequenziert) gezählt.
Die klassischen Bitoperationen:
http://www.willemer.de/informatik/cpp/sysprog.htm
https://cpluspluspedia.com/de/tutorial/2572/bitoperatoren
https://de.wikibooks.org/wiki/C++-Programmierung/_Nützliches/_Logische_Bitoperatoren
Der erste Bit-Editor
C++:
#include <bitset>
#include <iostream>
int main() {
int i, iPos;
char c;
std::bitset<32> bs;
while(i!=1) {
std::cout << "input:";
std::cin >> i >> c >> iPos;
bs = i;
if(c == '+') { bs.set(iPos, 1); }
else if (c == '-') { bs.reset(iPos); }
else if (c == '~') { bs.flip(iPos); }
i = bs.to_ulong();
std::cout << "bin:" << bs << std::endl;
std::cout << "dec:" << std::dec << i << std::endl;
std::cout << "hex:" << std::hex << i << std::endl;
std::cout << "oct:" << std::oct << i << std::endl;
std::cout << std::endl; }
return 0;
}
unsigned long
.Output:
Code:
input:254 + 0
bin:00000000000000000000000011111111
dec:255
hex:ff
oct:377
input:1022 + 0
bin:00000000000000000000001111111111
dec:1023
hex:3ff
oct:1777
input:24 + 8
bin:00000000000000000000000100011000
dec:280
hex:118
oct:430
input:0 + 0
bin:00000000000000000000000000000001
dec:1
hex:1
oct:1
Jetzt könnten wir das Tutorial hier beenden, alle Funktionen sind erklärt worden...
Das Problem dabei ist, wir haben hier einen prozeduralen Ansatz verfolgt. Mit einer Menge Fallstricke wie z.B.
- Der Bitset-Container verlangt eine konstante Größe vor der Laufzeit. Was aber, wenn wir diese selbst nicht kennen?
- er sollte also je nach Variablengröße angepasst werden.
- Es fehlen wichtige Routineprüfungen - z.B. prüfen auf richtige Usereingaben.
Modulare Programmierung
Jetzt versuchen wir das ganze mal mit Funktionen.
C++:
#include <bitset>
#include <iostream>
void binDisplay(int var) { std::cout << "bin:" << std::bitset<32>(var) << std::endl; }
void decDisplay(int var) { std::cout << "dec:" << std::dec << var << std::endl; }
void hexDisplay(int var) { std::cout << "hex:" << std::hex << var << std::endl; }
void octDisplay(int var) { std::cout << "oct:" << std::oct << var << std::endl; }
std::bitset<32> bitMan(int var, int iPos, char c) {
std::bitset<32> bs(var);
switch(c) {
case '+':bs.set(iPos,1); break;
case '-':bs.reset(iPos); break;
case '~':bs.flip(iPos); break; }
return bs;
}
int main() {
int i, iPos;
char c;
while(i!=1) {
std::cout << std::endl << "input:";
std::cin >> i >> c >> iPos;
i = bitMan(i,iPos,c).to_ulong();
binDisplay(i);
decDisplay(i);
hexDisplay(i);
octDisplay(i); }
return 0;
}
Output:
Code:
input:5 + 5
bin:00000000000000000000000000100101
dec:37
hex:25
oct:45
input:97 + 0
bin:00000000000000000000000001100001
dec:97
hex:61
oct:141
input:6 + 13
bin:00000000000000000010000000000110
dec:8198
hex:2006
oct:20006
input:0 + 0
bin:00000000000000000000000000000001
dec:1
hex:1
oct:1
Funktionen besitzen 3 Bestandteile. Den Rückgabewert, die Funktionsbezeichnung und die Parameter.
Der Rückgabewert
[->]void[<-] binDisplay(int var)
(void heißt in diesem Fall das es keinen Rückgabewert hat, da wir nur Text in der Konsole ausgeben). Der Rückgabewert kann dabei jeder Datentyp oder Container sein (theoretisch auch Zeiger oder Referenzen, diese werden dann aber als Parameter übergeben).Die Funktionsbezeichnung.
void [->]binDisplay[<-](int var)
Parameter
void binDisplay([->]int var[<-])
. Diesen haben wir hier mit dem copy-constructor übergeben. Das heißt alle Variablen werden beim übergeben nur kopiert. Die eigentlich Variable in der main-Funktion ändert sich nicht, außer wir übergeben den Rückgabewert direkt der ursprünglichen Variable. Wenn wir Variablen per Referenzen übergeben(dabei wird die Speicheradresse der Variable übergeben), können wir die ursprüngliche Variable direkt verändern.copy-constructor:
https://de.wikipedia.org/wiki/Kopierkonstruktor
http://www.willemer.de/informatik/cpp/copyconst.htm
Referenzen:
https://de.wikipedia.org/wiki/Referenzen
https://de.wikibooks.org/wiki/C++-Programmierung/_Weitere_Grundelemente/_Referenzen
Zeiger:
https://de.wikipedia.org/wiki/Zeiger_(Informatik)
https://de.wikibooks.org/wiki/C++-Programmierung/_Weitere_Grundelemente/_Zeiger
http://www.willemer.de/informatik/cpp/pointer.htm
Referenzparameter
C++:
#include <bitset>
#include <iostream>
void binDisplay(int &var) { std::cout << "bin:" << std::bitset<32>(var) << std::endl; }
void decDisplay(int &var) { std::cout << "dec:" << std::dec << var << std::endl; }
void hexDisplay(int &var) { std::cout << "hex:" << std::hex << var << std::endl; }
void octDisplay(int &var) { std::cout << "oct:" << std::oct << var << std::endl; }
std::bitset<32> bitMan(int &var, int &iPos, char &c) {
std::bitset<32> bs(var);
switch(c) {
case '+':bs.set(iPos,1); break;
case '-':bs.reset(iPos); break;
case '~':bs.flip(iPos); break; }
return bs;
}
int main() {
int i, iPos;
char c;
while(i!=1) {
std::cout << std::endl << "input:";
std::cin >> i >> c >> iPos;
i = bitMan(i,iPos,c).to_ulong();
binDisplay(i);
decDisplay(i);
hexDisplay(i);
octDisplay(i); }
return 0;
}
Kopierkonstruktor:
std::bitset<32> bitMan(int var, int iPos, char c)
Referenzparameter:
std::bitset<32> bitMan(int &var, int &iPos, char &c)
Bis auf einen Unterschied: die Paramater haben ein
&
. Das ist ein Referenzparameter.Bei Referenzen wird die Speicheradresse der Variable übergeben. So wird der Code im Speicher quasi reduziert, dadurch dass die Variable nicht kopiert, sondern direkt verändert wird.
TIPP: Jeder (C++-)Programmierer sollte sich es angewöhnen Parameter per Referenz zu übergeben.
Referenzen:
https://de.wikibooks.org/wiki/C++-Programmierung/_Weitere_Grundelemente/_Referenzen
Die Header
Programmcode der in Funktionen oder Klassen steht, kann genauso ausgelagert werden (Klassen müssen sogar ausgelagert werden, das werde ich aber im nächsten Kapitel behandeln). Das heißt dieser wird in eine separate Datei geschrieben und im Hauptprogramm wieder eingebunden. Das kann sehr vorteilhaft sein, wenn man große Projekte mit verschiedenen Funktionen hat.Ein Beispiel:
C++:
// bit.h
#ifndef BIT_H
#define BIT_H
#include <iostream>
void binDisplay(int &var) { std::cout << "bin:" << std::bitset<32>(var) << std::endl; }
void decDisplay(int &var) { std::cout << "dec:" << std::dec << var << std::endl; }
void hexDisplay(int &var) { std::cout << "hex:" << std::hex << var << std::endl; }
void octDisplay(int &var) { std::cout << "oct:" << std::oct << var << std::endl; }
std::bitset<32> bitMan(int &var, int &iPos, char &c) {
std::bitset<32> bs(var);
switch(c) {
case '+':bs.set(iPos,1); break;
case '-':bs.reset(iPos); break;
case '~':bs.flip(iPos); break; }
return bs;
}
#endif
.h
oder .hpp
.
C++:
//bit.cpp
#include <iostream>
#include "bit.h"
int main() {
int i, iPos;
char c;
while(i!=1) {
std::cout << std::endl << "input:";
std::cin >> i >> c >> iPos;
i = bitMan(i,iPos,c).to_ulong();
binDisplay(i);
decDisplay(i);
hexDisplay(i);
octDisplay(i); }
return 0;
}
#include <iostream>
aufgerufen. Wird eine Header-Datei aufgerufen die man selbst geschrieben hat, wird diese z.B. mit include "bit.h"
aufgerufen(sofern sich diese im selben Ordner befindet, wie die Hauptdatei). Eigene Header-Dateien können auch über relative Pfadangaben aufgerufen werden, wenn sich diese in einem anderen Ordner befinden z.B.: #include "../h/bit.h"
.Header:
https://docs.microsoft.com/de-de/cpp/cpp/header-files-cpp
Was als erstes in dem Beispiel auffällt ist in Zeile 2
#ifndef BIT_H
und in Zeile 3 #define BIT_H
. Abgeschlossen wird das Ganze in Zeile 24 mit #endif
. Das sind Präprozessor-Anweisungen, die verhindern das Header-Dateien mehrfach eingebunden werden.Diese Programmiertechnik nennt man einen Include-Guard. Wird diese Präprozessor-Direktive nicht eingesetzt, kann es passieren, dass die Header mehrfach kompiliert und das Programm größer ist als es sein sollte. Das geschieht, wenn man z.B. mehrere Header-Dateien(Module) hat, die aufeinander aufbauen, aber im Hauptprogramm genauso benötigt werden.
Präprozessor:
https://de.wikipedia.org/wiki/Präprozessor
https://docs.microsoft.com/de-de/cpp/preprocessor/preprocessor-directives
http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/010_c_praeprozessor_002.htm -C
Include-Guard:
https://de.wikipedia.org/wiki/Include-Guard
Ersetzen kann man das auch mit der Anweisung
#pragma once
. Wird weitestgehend auch von allen allen Compilern unterstützt ist aber kein Standard.#pragma once:
https://en.wikipedia.org/wiki/Pragma_once
TIPP: Den Include-Guard in jeder Header-Datei einsetzen.
-----------------------------------------------------------------------------------------------------------------------------------------------
Ich werde hier das Projekt weiter vorantreiben. Allerdings bin ich zeitlich gerade eingeschränkt.
Ihr könnt mir aber gerne schonmal Kritik dalassen. Ich freue mich auf jeden dem ich helfen konnte oder der etwas hierzu beitragen kann. Hoffe das ganze ist nicht zu ausführlich gehalten

Nur zum sichergehen. Ich habe bereits einen funktionierenden Bit-Editor mit einer modularen, objektorientierten Herangehensweise. Außerdem berechnet dieser die verschiedenen Darstellungen selbst. Den werde ich natürlich ganz zum Schluss vorstellen.
Ich möchte nur zeigen, wie man mit Bibliotheken und wie quasi "selbst" programmiert.
Ich will hier nur eine bestimmte Herangehensweise verdeutlichen, wie man als Anfänger an so was herangehen kann(sollte?).
Außerdem werde ich, wenn das hier gut ankommen sollte mich weiteren Projekten widmen und hier vorstellen, wie z.B.
-Kryptographie
-Android-Apps
-Netzwerkprogramme
...
Zuletzt bearbeitet: