An deiner zweiten Version ist u. a. besser, dass die Funktionen scanf_s und printf_s entfallen, denn sie gehören nicht zu C++, sondern optional zu C (zum C11-Standard). Ein standardkonformer C11-Compiler darf sie weglassen, und kein offizieller C++-Standard kennt sie. Demnach sind sie unter C++ grundsätzlich nicht vorhanden, denn was über den Umfang von C++ hinaus mitgegeben wird, kann nicht vorausgesetzt werden. Deine erste Version erfüllt also nicht die Anforderung nach einem portablen C++-Programm. So bringt mein GNU-Compiler berechtigterweise kein scanf_s mit, weswegen er den Code nicht übersetzen kann.
Wie lassen sich solche Verwechslungen vermeiden?
Abhilfe:
Verwende bitte nicht dieses (was ein schlechtes Vorbild aus Tutorials ist, um Platz, Tipp- oder Lesearbeit zu sparen oder Anfänger nicht zu verwirren; aber der Schuss geht nach hinten los (du hattest mehr Arbeit, nicht weniger und wurdest verwirrt!), und bessere Tutorials tun das eher nicht)):
using namespace std;
Denn deswegen^ hast du dir angewöhnt, den Namensraum std nicht mit anzugeben, weswegen dir nicht rechtzeitig aufgefallen ist (wofür du nichts kannst, das Tutorial schon), dass es weder std::scanf_s noch std::printf_s gibt.
Alle Namen, die der Standardbibliothek von C++ angehören, befinden sich im Namensraum (namespace) std.
Wenn ein Name (Schlüsselwörter ausgenommen) trotz Einbindung der richtigen Header-Datei (ohne .h am Ende, also z. B. cstdio und nicht das veraltete stdio.h) nicht in std enthalten ist, dann ist das ein starkes Indiz dafür, dass er nicht implementiert ist (Compiler erfüllt den Standard nicht ganz) oder gar nicht im Standard enthalten ist, also nicht zu C++ gehört.
Wenn der Name im globalen Namensraum verfügbar ist, nicht aber std angehört, dann kannst du ziemlich sicher sein, dass er nicht zu C++ gehört.
Also lässt du (wie es erfahrene C++-Programmierer in richtigen Projekten machen) diese using-Direktive weg und stellst den Namen von C++ (Schlüsselwörter ausgenommen) stets std:: für den Zugriff auf den Namensraum std voran, also:
std::cin, std::cout, std::string, std::vector, std::istringstream, std::ostringstream . . .
Falls dich das nicht überzeugt (du kannst noch nicht alle Gründe wissen, es gibt weitere), verwende dieses Schema:
using std::scanf_s; // schlägt fehl (beabsichtigter Zweck)
using std::printf_s; // schlägt fehl (beabsichtigter Zweck)
using std::cin; // funktioniert
using std::cout; // funktioniert
using std::string; // funktioniert
using std::vector; // funktioniert
using std::istringstream; // funktioniert
using std::ostringstream; // funktioniert
// usw.
Dabei importierst du genau diejenigen Namen, die du tatsächlich gebrauchst, keinen weniger, keinen mehr! Es ist aber nur die zweitbeste Lösung, denn an der Stelle der Verwendung des jeweiligen Namens würde nichts auffallen, weil du kein std:: voranstellst. Der erwünschte Hinweis ergäbe sich also nur bei der entsprechenden using-Direktive - falls du sie nicht vergisst! Um was wollen wir wetten, dass früher oder später...
Innerhalb von Header-Dateien verbieten sich in der Regel Importe in den globalen Namensraum ganz. Denn der Benutzer, der die Datei einbindet, hätte die Namen automatisch mit am Hals. Er könnte sie im selben Gültigkeitsbereich nicht mehr verwenden, während innerhalb einer darin enthaltenen Klammerungsebene jedoch Verdeckung erlaubt ist, was ganz schön verwirren kann. Es könnte also leicht zu folgenreichen Verwechslungen kommen. Das Problem mit den hier diskutierten Verwechslungen von Namensräumen (z. B. dem globalen mit std) bestünde natürlich ebenfalls, aber auf subtilere Weise, indem der Benutzer nicht wissen kann, was importiert wurde und was nicht, was das Problem verschärfen würde. Eine einzige faule Header-Datei würde also genügen, um den globalen Namensraum zuzumüllen. Man nennt den Effekt auch Namespace Pollution.
Die einfachste und beinahe idiotensichere Lösung, bei der man am wenigsten erklären muss, lautet daher: Stelle immer std:: voran, wenn du etwas von C++ verwenden willst (ausgenommen Schlüsselwörter). Aber ein Anfänger kommt dabei leicht auf den Gedanken, man wolle es ihm unnötig schwer machen. Das Gegenteil ist der Fall, wie du nun gesehen hast. Die Probleme kommen regelmäßig von denjenigen, die sich einbilden, sie könnten es noch leichter machen, was bei C++ aber nicht geht. Deswegen darf man dann ausführliche und zugleich nicht mit ihrer Länge überfordernde Abhandlungen wie diese schreiben, was ein anstrengender und kaum zu bewältigender Spagat ist. Hier oder dort ein Wort weniger, und die Aussage wird irreführend oder falsch.
Hier kannst du nachlesen, wie (zumeist professionelle) C++-Programmierer über using namespace std; denken. Es handelt sich also nicht bloß um eine Einzelmeinung von mir.
Dein zweiter Code hat noch einige Bugs.
Ich fange mit einem an (die anderen hebe ich für später auf), finde ihn:
Code:
for (int i = 0; i < 1;){
cin >> westen;
if (westen[1] == 0 && westen[0] >= 'A' && westen[0] <= 'J') {
spalte = westen[0] - 'A';
i++;
}
if (westen[1] == 0 && westen[0] >= 'a' && westen[0] <= 'j') {
spalte = westen[0] - 'a';
i++;
else{
cout << "Das liegt ausserhalb der Schussweite!\nNeue Koordinate:";
}
}
Was passiert, wenn du einfach einen Wert aus ['A', 'J'] eingibst? Warum passiert das?
Zitat von
Momo
okay danke für den tip, aber der quelltext aus dem ersten post ist jetzt eh überholt.. ;P
Das^ verstehe ich nicht. Denn strukturell ist es doch dasselbe geblieben. In beiden Fällen verwendest du diesen irritierenden Schleifenkopf, welcher suggeriert, es gäbe etwas hochzuzählen. Als Experiment mag das in Ordnung sein, aber nicht zum Ab- oder Weitergeben. Denn Code sollte die Absicht seines Autors ausdrücken. Vielleicht kommst du schon selber darauf, warum er das sollte.
Also könntest du dem (sinnvollen) Vorschlag von ThiccWookie folgen...
Code:
while (true) {
if (Hier_steht_ein_konditionaler_Ausdruck) {
//...
break;
}
}
...wobei diese Variante gleichwertig ist:
Code:
for (;;) {
if (Hier_steht_ein_konditionaler_Ausdruck) {
//...
break;
}
}
In beiden Fällen ist schon am Schleifenkopf ablesbar, dass recht wahrscheinlich mit break herausgesprungen wird, bevor man also das break überhaupt sieht (return oder void std::exit(int exit_code); ist hier nicht sinvoll, und goto meidet man).
Das ist gut, weil man das Konzept versteht, sobald man mit dem Lesen anfängt!
Wenn du unbedingt an deiner Variante festhalten willst, dann solltest du auch hier das Konzept verdeutlichen (da war dein erster Code sogar näher dran, aber nicht nah genug):
Code:
// wie leicht vertut man sich hier bei der Abbruchbedingung...
//
for (bool eingabeRichtig = false; !eingabeRichtig;) {
if (Hier_steht_ein_konditionaler_Ausdruck) {
//...
eingabeRichtig = true;
}
}
Wenn du mich fragst, so finde ich das^ etwas unübersichtlich (aber immerhin kompakt!). Da fände ich dieses vielleicht besser (aber dennoch unbefriedigend):
Code:
bool eingabeRichtig = false;
do {
if (Hier_steht_ein_konditionaler_Ausdruck) {
//...
eingabeRichtig = true;
}
} while (!eingabeRichtig);
Das^ drückt durchaus klar aus, was gemeint ist: Es soll in jedem Fall in die Schleife eingetreten werden, und am Ende wird überprüft, ob eine Wiederholung nötig ist, was den beabsichtigten Programmfluss schon leichter erkennen lässt. Es stört dabei jedoch, dass die Variable für die Abbruchbedingung außerhalb des Gültigkeitsbereichs (scope) der Schleife definiert werden muss, was richtig nerven kann, wenn mehr Code drumherum steht. Ja, alles gleichzeitig bekommt man nicht so leicht unter einen Hut, außer beinahe mit break. Dafür fehlt dort die implizite Dokumentation über eingabeRichtig. Doch das lässt sich handhaben.
Finde erst mal den Bug (s. o.). Denn das wirft nebenher ein neues Licht auf die Verwendung von break. Danach können wir weitere Bugs behandeln.