'char'- Werte mit 'int'- Werten multiplizieren.
-
Gut, ich habe es nun verändert. Der Quellcode sieht jetzt so aus:
#include <iostream> #include <cstring> #include <cstdlib> using namespace std; int main() { char Eingabe[10]; int Zufallswert, i; char Passwort[10]; cout << "Geben Sie ein Passwort ein:" << endl; cin >> Passwort; for (i = 0; i != 9; i++) { srand (time (0)); { Zufallswert = rand() % 50; } Passwort[i] = Eingabe[i] * Zufallswert; } cout << Passwort << endl; cin >> Zufallswert; }
Zwar wird mir ein Zufälliger Wert ausgegeben, allerdings nur ein Zeichen. Das sieht dann so aus:
Geben Sie ein Passwort ein:
Wort
Òwie kann ich das beheben?
-
Kennst du die ASCII-Tabelle (nicht alle Zeichen sind druckbar
?
PS: srand(time(0)) solltest du nur einmalig am Anfang deines Programmes aufrufen, damit du nicht immer die gleiche Zufallszahl erhältst...
-
<ctime>
fehlt.benimus schrieb:
// ... cin >> Passwort; // du willst wahrscheinlich in Eingabe einlesen? for (i = 0; i != 9; i++) { srand (time (0)); { // <<================================+ Zufallswert = rand() % 50; // |-- wofuer das? } // <<================================+ Passwort[i] = Eingabe[i] * Zufallswert; } cout << Passwort << endl; cin >> Zufallswert; // weil? }
Was, wenn der Benutzer nicht genau 9 Zeichen eingibt?
Wie kommt die einen C-String abschließende'\0'
nachPasswort
?
-
Danke, ich habe es jetzt verändert, habe mich zu sehr auf die Schleife konzentriert, und dabei die fehlerhafte Eingabe übersehen. Aber wie genau meinst du das mit der abschließenden '\0' nach "Passwort"? Ich weiß zwar, dass sie an das Ende eines char Arrays gehört, falls die maximale Länmge nicht erreicht wird, aber wo genau fehlt sie denn im Code?
Neuer Quellcode:
#include <ctime> #include <iostream> #include <cstring> #include <cstdlib> using namespace std; int main() { char Eingabe[10]; int Zufallswert, i; char Passwort[10]; cout << "Geben Sie ein Passwort ein:" << endl; cin >> Eingabe; for (i = 0; i != 9; i++) { srand (time (0)); Zufallswert = rand() % 50; Passwort[i] = Eingabe[i] * Zufallswert; } cout << Passwort << endl; cin >> Zufallswert; // ist einfach da, damit sich das Programm nicht sofort schließt, sobald das Ende erreicht ist. }
-
for (i = 0; i != 9; i++) { srand (time (0)); // Jedes Mal initialisieren ? Das macht man genau einmal! Zufallswert = rand() % 50; Passwort[i] = Eingabe[i] * Zufallswert; // Wenn der Nutzer nur 1 Zeichen eingegeben hat greifst du auf nichtexistente Daten zu }
Außerdem müssen C-Strings 0-Terminiert werden. D.h. du hast 10 chars Passwort, und wo ist das '\0' ?
-
Und wie genau mach ich das in diesem Fall am besten? in meinem Buch steht dazu folgendes, allerdings bin ich mir nicht ganz sicher, ob das in diesem Falle wirklich auf diese Weise anwendbar ist.
Um auch Texte in einem Array ablegen zu können, die kürzer sind als Größe des Arrays, muss das Textende markiert werden. Dazu wird eine 0 abgelegt. Dabei handelt es sich nicht um eine Ziffer '0', sondern um ein Nullbyte, das als '\0' oder als Zahlenkonstante 0 dargestellt wird. Eine Stringkonstante impliziert immer als letztes Zeichen diese Abschluss-Null. Damit ist der leere String ("") nicht wirklich leer. Er enthält bereits ein Element, die 0. Im folgenden Beispiel wird ein C-String definiert und mit einer String-Konstanten initialisiert:
char Vorname[6] = "Kai";
-
Wir sind hier in C++. Also bin ich für eine Lösung in C++:
#include <ctime> #include <iostream> #include <vector> using namespace std; int main() { srand (time (0)); //Initilisierung der Zufallsfunktion am Anfang std::string Eingabe; cout << "Geben Sie ein Passwort ein:" << endl; cin >> Eingabe; //Eingabe in einen C++ String statt in ein C-Array std::vector<char> PasswordArray(Eingabe.length()); //Vector (Array) der die selbe Länge wie unsere Eingabe hat int Zufallswert; for (int i = 0; i < Eingabe.length(); i++) //Schleife nur so oft laufen lassen wie wir Zeichen haben, keine fixe Anzahl { Zufallswert = rand() % 50; PasswordArray[i] = Eingabe[i] * Zufallswert; //Array füllen } std::string Passwort(PasswordArray.begin(), PasswordArray.end()); //Array in string umwandeln cout << Passwort << endl; //Ausgabe des Passworts cin >> Zufallswert; // ist einfach da, damit sich das Programm nicht sofort schließt, sobald das Ende erreicht ist. }
-
DarkShadow44 schrieb:
#include <ctime> #include <iostream> #include <vector> #include <cstdlib> // fehlte (srand) #include <string> // fehlte using namespace std; // den ganzen namensraum ausschuetten?? int main() { srand (time (0)); // conversion from 'time_t' to 'unsigned int', possible loss of data std::string Eingabe; cout << "Geben Sie ein Passwort ein:" << endl; // du meinst: [...]ein:\n"; - nicht: << endl; cin >> Eingabe; std::vector<char> PasswordArray(Eingabe.length()); // warum nicht gleich length() + 1, // '\0' anhaengen und direkt ausgeben // statt umweg spaeter ueber string? // besser: warum nicht gleich einen string?? int Zufallswert; // warum nicht dort deklarieren, wo gebraucht? for (int i = 0; i < Eingabe.length(); i++) // der Typ fuer Objektgroeszen ist std::size_t, nicht int { Zufallswert = rand() % 50; PasswordArray[i] = Eingabe[i] * Zufallswert; } std::string Passwort(PasswordArray.begin(), PasswordArray.end()); cout << Passwort << endl; // du willst: << '\n', nicht: << endl; cin >> Zufallswert; // siehe http://www.c-plusplus.net/forum/111042-full }
DarkShadow44 schrieb:
Wir sind hier in C++. Also bin ich für eine Lösung in C++:
Warum verwendest du dann
time()
,srand()
undrand()
aus der C-Stdlib? Warum nicht gleich aufEingabe
anstatt der umständlichen herumkopiererei?#include <chrono> #include <random> #include <functional> #include <algorithm> #include <array> #include <string> #include <iostream> int main() { std::array< int, std::mt19937::state_size > seeds; std::random_device random_device; std::generate_n( seeds.begin(), seeds.size(), std::ref( random_device ) ); std::seed_seq seed_sequence( seeds.begin(), seeds.end() ); std::mt19937 mt( seed_sequence ); std::uniform_int_distribution<> distribution( 1, 50 ); std::cout << "Geben Sie ein Passwort ein:\n"; std::string password; std::cin >> password; for( std::string::size_type i = 0; i < password.length(); ++i ) password[ i ] = password[ i ] * distribution( mt ); std::cout << "Passwort:\n" << password << "\n\n"; }
nee, eigentlich
// ... for( auto & i : password ) i = i * distribution( mt ); // ...
Deine Probleme beginnen schon bei
cin >> Eingabe;
denn deristream& operator>>(istream &is, char *s)
weiß nicht, wie groß der Speicherbereich ist, in den er Zeichen aus dem Stream schreiben soll:Extracts characters from
is
and stores them ins
as a c-string, stopping as soon as either a whitespace character is encountered or(width()-1)
characters have been extracted (if width is not zero) [<<== !!]. A null character (charT()
) is automatically appended to the written sequence. The function then resetswidth
to zero.Da
width
mit Lebensbeginn vonstd::cin
gleich0
ist, wird dieseroperator>>()
so lange Zeichen aus dem Stream extrahieren und in den Speicher beginnend bei*s
schreiben, solange kein Whitespace (Space' '
, Tab'\t'
, Newline'\n'
, ...) daherkommt. Gibt der Benutzer nun mehr Zeichen ein, als Platz in deinem Array ist, hast du einen klassischen Buffer overflow.
Also musst du entweder vor dem Lesen mitoperator>>()
die Feldgröße des Eingabestreams auf die Länge des Arrays in das du lesen willst mitstd::ios_base::width()
oderstd::setw()
setzten oder du verwendest zum Einlesenstd::istream::getline()
, bei der du die Größe des zur Verfügung stehenden Speichers direkt als Parameter angeben kannst, also:#include <iostream> int main() { char input[ 10 ]; std::cin.width( 10 ); std::cin >> input; std::cout << '\"' << input << "\"\n"; }
oder
#include <iostream> #include <iomanip> // std::setw() int main() { char input[ 10 ]; std::cin >> std::setw( 10 ) >> input; std::cout << '\"' << input << "\"\n"; }
oder
#include <iostream> int main() { char input[ 10 ]; std::cin.getline( input, 10 ); std::cout << '\"' << input << "\"\n"; }
Bei
getline()
landen aber auch eventuell eingegebene Whitespace character in deinem Array. Ich weiß nicht, ob du das willst.Das nächste Problem ist die Bedingung deiner for-Schleife. Sie läuft immer stur von
0
bis inklusive8
. Das geht nur gut, wenn der Benutzer weniger als9
Zeichen eingibt. Ich mals dir auf:Beispiel "12345678" Eingabe Passwort nach Schleife 0 bis 8 [0] '1' [0] '1' * Zufallswert [1] '2' [1] '2' * Zufallswert [2] '3' [2] '3' * Zufallswert [3] '4' [3] '4' * Zufallswert [4] '5' [4] '5' * Zufallswert [5] '6' [5] '6' * Zufallswert [6] '7' [6] '7' * Zufallswert [7] '8' [7] '8' * Zufallswert [8] '\0' [8] '\0' * Zufallswert == 0 * Zufallswert == '\0' [9] müll [9] müll, der vor der Schleife schon dort war Beispiel "123456789" Eingabe Passwort nach Schleife 0 bis 8 [0] '1' [0] '1' * Zufallswert [1] '2' [1] '2' * Zufallswert [2] '3' [2] '3' * Zufallswert [3] '4' [3] '4' * Zufallswert [4] '5' [4] '5' * Zufallswert [5] '6' [5] '6' * Zufallswert [6] '7' [6] '7' * Zufallswert [7] '8' [7] '8' * Zufallswert [8] '9' [8] '9' * Zufallswert [9] '\0' [9] müll, der vor der Schleife schon dort war
Bei der Eingabe von
"12345678"
(und kürzeren Eingaben) wird also praktisch durchPasswort[8] = Eingabe[8] * Zufallswert
(= '\0' * Zufallswert = 0 * Zufallswert = 0 * egalwas = '\0'
) sichergestellt, daß die für Passwort errechneten Zeichen mit einem'\0'
enden. Bei"123456789"
hingegen wird die0
inEingabe[9]
in der Schleife nicht bearbeitet und inPasswort[9]
steht dasselbe wie vor der Schleife - Das kann, da das Array uninitialisiert ist, sonstwas sein und die chancen für'\0'
stehen nicht gut ;).Deine Schleife sollte also entweder
- über das gesamte Array laufen
wobei abi > strlen(Eingabe)
auch Werte verarbeitet werden, die der Benutzer nicht eingegeben hat, also sinnlos gearbeitet wird, - solange
i <= strlen(Eingabe)
wobei die Abschließende'\0'
verrechnet wird, oder - solange
i < strlen(Eingabe)
wobeiPasswort
vorher zB. durch Initialisierung bereits mit Nullen gefüllt ist (char Eingabe[10] = {};
) oder die Abschließende'\0'
nach der Schleife gesetzt werden muss (Passwort[strlen(Eingabe)] = '\0';
)
Weitere Unschönheiten:
- Initialisierung des Zufallsgenerators mit
srand()
wurde zwar schon genannt, aber der Vollständigkeit halber: Da du den Zufallsgenerator in der Schleife immer wieder mit der Systemzeit (die sich zwischen den Schleifendurchläufen sehr unwahrscheinlich ändern wird) initialisierst, bekommt Zufallswert sehr wahrscheinlich immer den selben Wert zugewiesen. Initialisiere also den Zufallsgenerator nur einmal am Programmstart. - Du deklarierst Variablen nicht dort, wo sie gebraucht werden. Variablen sollte man immer so lokal wie möglich halten.
- Für Objektgrößen verwendet man
std::size_t
aus<cstddef>
, nichtint
denn fürsize_t
ist garantiert, daß der Wertebereich für jedes mögliche Objekt eines C++-Programms ausreicht. Fürint
gilt das nicht. - Du verwendest Magic numbers.
Bei jeder Änderung an den Arraydimensionen musst du in deiner jetzigen Version zwei Größenangaben und die Laufbedingung der for-Schleife ändern. Hättest du dafür eine Konstante, müsstest du nur eine einzige Stelle ändern, was weniger fehleranfällig ist.
Bei der Berechnung vonZufallswert
verwendest du die Konstante50
. Hast du dir bei der Auswahl dieses Wertes irgendetwas spezielles überlegt? Hätte der Wert einen Namen, so könnte man seine Herkunft eventuell nachvollziehen. So muss irgendwo dokumentiert werden, weshalb er gewählt wurde. - Du verwendest
std::endl
wo ein Newline ('\n'
) reicht
std::endl
schreibt nicht nur ein Newline in den Stream, sondern führt zusätzlich auch noch einen flush. Für weiteres siehe dort. - Du schüttest mit
using
den gesamten Namensraumstd
aus
wodurch es schonmal zu Namenskonflikten kommen kann. Noch dazu ist in einem solch kleinen Programm das Mehr an Schreibaufwand durch die explizite Angabe des Namespace nicht der Rede wert.
Auf deinen letzten Code übertragen:
#include <limits> #include <cstddef> #include <ctime> #include <cstdlib> #include <cstring> #include <iostream> #include <iomanip> int main() { std::size_t constexpr array_size = 10; std::srand( static_cast< unsigned >( std::time( nullptr ) ) ); std::cout << "Geben Sie ein maximal " << array_size - 1 << " Zeichen langes Passwort ein:\n"; char input[ array_size ]; std::cin >> std::setw( array_size ) >> input; char output[ array_size ] = {}; for( int i = 0; i < strlen( input ); ++i ) output[ i ] = input[ i ] * ( rand() % 50 ); std::cout << output << '\n'; std::cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' ); }
Darüber, daß davon aber sehr, sehr unhandliche Passwörter ausgespuckt werden, sag' ich mal nichts.
- über das gesamte Array laufen
-
Wenn du schon einen C++-RNG vorschlaegst, dann initialisier ihn auch wenigstens ordentlich mit std::random_device und std::seed_seq.
-
Jawohl!
-
benimus schrieb:
Man gibt eine ein Wort, zahlen oder eine Kombination aus beidem ein dessen Ziffern dann mit einem zufälligem Wert addiert werden.
Und jetzt liest Du diesen Satz mal laut vor. Wenn Dir nichts auffällt, dann lies ihn jemand anderen vor. Das nächste mal machst Du das bevor Du auf "Absenden" drückst. Das ist ja sonst eine Zumutung, das zu lesen.
-
Swordfish schrieb:
nee, eigentlich
// ... for( auto & i : password ) i = i * distribution( mt ); // ...
Warum nicht gleich:
// ... std::transform(std::begin(password), std::end(password), std::begin(password), [&, gen=[&](){return distribution(mt);}](decltype(password)::value_type c){return c * gen();}); // ...
Oder um sich das doppelte begin zu sparen besser gleich:
// ... ([&](auto b, auto e, auto f){std::transform(b,e,b,f);})(std::begin(password), std::end(password), [&, gen=[&](){return distribution(mt);}](decltype(password)::value_type c){return c * gen();}); // ...
Swordfish schrieb:
std::array< int, std::mt19937::state_size > seeds;
Das ist nicht generisch genug, besser das hier draus machen:
std::array< std::random_device::result_type, std::mt19937::state_size > seeds;
-
Swordfish schrieb:
Warum verwendest du dann
time()
,srand()
undrand()
aus der C-Stdlib? Warum nicht gleich aufEingabe
anstatt der umständlichen herumkopiererei?Ich hab der Einfachheit halber das meiste 1:1 übernommen und den Code nur so umgeschrieben dass das Programm das macht was es soll.
Der Rest ist nicht zwangläufig falsch, nur kein guter Stil, aber das ist eine andere Baustelle.//Gängige Praxis so. Der Verlust macht nichts, eventuell sollte man die Warnung noch unterdrücken srand (time (0)); /* ... */ //Man sollte die Änderung direkt am Passwort machen ja, hab vergessen dass C++ strings mutable sind std::vector<char> PasswordArray(Eingabe.length()); /* ... */ //Mal von der Warnung abgesehen reicht int vollkommen aus. //Wenn dein Passwort so lang ist dass es nicht mehr in den Wertebereich von int passt hast du eh andere Probleme for (int i = 0; i < Eingabe.length(); i++) /* ... */ }
Im übrigen würde ich immer noch dazu raten einfach ein Zufallspasswort zu generieren.
Da braucht du kein Usereingabe und kannst sicherstellen dass das passwort nur aus gewollten Zeichen besteht.
-
Trollfish schrieb:
Swordfish schrieb:
nee, eigentlich
// ... for( auto & i : password ) i = i * distribution( mt ); // ...
Warum nicht gleich:
// ... std::transform(std::begin(password), std::end(password), std::begin(password), [&, gen=[&](){return distribution(mt);}](decltype(password)::value_type c){return c * gen();}); // ...
Um sich das doppelte
begin()
zu sparen.Trollfish schrieb:
Swordfish schrieb:
std::array< int, std::mt19937::state_size > seeds;
Das ist nicht generisch genug, besser das hier draus machen:
std::array< std::random_device::result_type, std::mt19937::state_size > seeds;
Da hast du natürlich Recht.
DarkShadow44 schrieb:
Im übrigen würde ich immer noch dazu raten einfach ein Zufallspasswort zu generieren.
-
Swordfish schrieb:
[...]
Mein Post war selbstverständlich sarkastisch weil mich deine Verschlimmbesserung dazu verleitet hat. Jemand der Integer Promotion nicht kennt wird deinen Code erst recht nicht verstehen. DarkShadow44s Lösung ist besser weil verständlicher.
-
Das ist mir schon klar, troll, aber meine Philosophie dazu ist vielmehr "Friss (=lerne!) oder stirb!".
-
Swordfish schrieb:
Das ist mir schon klar, troll, aber meine Philosophie dazu ist vielmehr "Friss (=lerne!) oder stirb!".
Also als mich mein Papi ins Wasser warf, stand ich Todesängste aus. Mehr ist nicht passiert. Kein Vertrauen zu dem Arschloch mehr, auch nie wieder gefunden. Schwimmen habe ich davon nicht gelernt. Kennt einer von dem Jungen Gemüse Todesangst?
Es mag Leute geben, die eh schon im Prinzip schwimmen konnten, was deren Papi schon gesehen hat und ganz genau analysiert hat, und Papi hat da sinnvoll abgekürzt.
Wenn Du den Fragesteller genug analysieren konntest, daß "Friss (=lerne!) oder stirb!" passt, dann kriegste einen *daumenhoch*
Wenn Du aus den Daten wähnen kannst, daß es passt, dann auch.Wenn Du aus "Philosophie" so berätst, weil er einfach in Dein Bild zu passen hat, dann schweig lieber.
edit: Das war schon ein ganz schöner Klopper, dem Du da gekackt hast. Hast ja recht wie meistens. Bißchen kuscheliger geht auch. Dem Opfa nutzt die Bretthärte oft wenig. Dann kommt er immer wieda. Gib ihm, was er verdauen kann und alle vier Woche einen Brocken und er nerft nicht und wir sind ihn nach 3-4 Fragen als Frager los, weil er dann langsam, Antworter wird, statt vor einer unerklimmbaren Wand zu stehen.
edit2: Ja, ich müßte mich auch mehr zurückhalten. Voller Begeisterung, aus 55 Jahren Programmiererfahrung den neuen Leuten was zeigen zu können, wenn sie mir ach bloß ein wenig zuhören würden, könnte ich ihnen Jahre des Lernens abkürzen… Geht aber nicht, Fehler muss man selber machen. Arcoth wäre jetzt 4-5 Jahre weiter zum Beispiel.
-
volkard, ich hab' Dich auch lieb!
Wissenserwerb ist in meinen Augen eine eine Holschuld. Sollte jemand nicht verstehen, was man einem gibt, kann er immer noch nachfragen ...