Aufgabenblock — AFB III
Hier zählt das Begründen: eigene Funktionen entwerfen, Fehler erklären, Entwürfe beurteilen. Schreib deine Lösung erst vollständig auf und klapp dann die Musterlösung auf — sie zählt als erledigt.
Für eine Prüfziffer braucht die Mensa-Kasse die Quersumme einer Zahl, z. B. 4 + 0 + 9 + 6 = 19 für 4096.
Implementiere eine Funktion int quersumme(int n) für n ≥ 0 ohne Zeichenketten, nur mit %, / und einer Schleife. Teste sie mit 4096, 7 und 0.
n % 10 ist die letzte Ziffer, n / 10 schneidet sie ab.Warum so? Die Ganzzahldivision, die sonst eine Falle ist, ist hier genau das Werkzeug.return s;Musterlösung anzeigen (zählt als erledigt)
#include <stdio.h> int quersumme(int n) { int s = 0; while (n > 0) { s += n % 10; // letzte Ziffer addieren n = n / 10; // letzte Ziffer abschneiden } return s; } int main(void) { printf("%d\n", quersumme(4096)); printf("%d\n", quersumme(7)); printf("%d\n", quersumme(0)); return 0; }
19 7 0
Für 4096 läuft die Schleife viermal: 6, 9, 0, 4 werden addiert. Für 0 läuft sie gar nicht — while prüft vorher, deshalb stimmt auch das Ergebnis 0. Mit do … while wäre es hier ebenfalls richtig, aber unnötig.
char a[] = "Rot"; char b[] = "Rot"; if (a == b) { printf("gleich\n"); } else { printf("verschieden\n"); }
Das Programm gibt verschieden aus, obwohl beide Arrays „Rot“ enthalten. Begründe das Verhalten mit dem, was ein Array-Name in C bedeutet, und gib eine korrekte Abfrage an.
== vergleicht immer genau die beiden Werte links und rechts — hier sind das keine Buchstaben.a und b stehen für die Adressen ihrer ersten Elemente; zwei Arrays liegen an verschiedenen Stellen. Vergleich mit strcmp.Musterlösung anzeigen (zählt als erledigt)
Ein Array-Name steht für die Adresse seines ersten Elements. a == b vergleicht also zwei Adressen. a und b sind zwei getrennte Arrays mit je 4 Byte an verschiedenen Speicherstellen — die Adressen sind nie gleich, egal was darin steht. gcc warnt mit -Wall sogar: comparison between two arrays.
Den Inhalt vergleicht strcmp aus <string.h> Zeichen für Zeichen; bei Gleichheit liefert es 0:
if (strcmp(a, b) == 0) { printf("gleich\n"); }
In C++ mit std::string funktioniert == dagegen wie erwartet.
Eine Funktion soll zu einem Array das Minimum und das Maximum liefern. Mit return kann sie aber nur einen Wert zurückgeben.
Entwirf eine Lösung mit Zeigerparametern: Gib den Funktionskopf an, implementiere die Funktion und zeige den Aufruf für int werte[5] = {7, 3, 9, 1, 5};
scanf: Es bekommt eine Adresse, weil es in eine fremde Variable schreiben muss.void minmax(int a[], int n, int *min, int *max); Aufruf minmax(werte, 5, &kl, &gr);Musterlösung anzeigen (zählt als erledigt)
#include <stdio.h> void minmax(int a[], int n, int *min, int *max) { *min = a[0]; *max = a[0]; for (int i = 1; i < n; i++) { if (a[i] < *min) *min = a[i]; if (a[i] > *max) *max = a[i]; } } int main(void) { int werte[5] = {7, 3, 9, 1, 5}; int kl, gr; minmax(werte, 5, &kl, &gr); printf("%d %d\n", kl, gr); return 0; }
1 9
Das Array wird ohne & übergeben — sein Name ist schon eine Adresse. Die Länge n muss mitgegeben werden, weil die Funktion sie sonst nicht kennt. Startwert ist a[0], nicht 0: Bei lauter negativen Zahlen wäre 0 sonst ein falsches Maximum.
int punkte[5]; for (int i = 0; i <= 5; i++) { punkte[i] = 0; }
Jan sagt: „Mein Programm läuft ohne Absturz und ohne Fehlermeldung. Also ist die Schleife korrekt.“ Widerlege diese Aussage.
punkte[5] liegt außerhalb.Musterlösung anzeigen (zählt als erledigt)
Das Array hat 5 Elemente mit den Indizes 0 bis 4. Wegen i <= 5 schreibt der letzte Durchlauf in punkte[5] — ein Byte-Bereich hinter dem Array, der einer anderen Variablen gehören kann.
C prüft die Grenzen nicht, weder beim Übersetzen noch beim Ausführen. Das Ergebnis ist undefiniertes Verhalten: Es kann zufällig gut gehen, eine andere Variable überschreiben oder abstürzen — je nach Rechner, Compiler und Tag. „Läuft bei mir“ beweist also nichts.
Korrekt: i < 5 oder besser i < sizeof(punkte) / sizeof(punkte[0]).
Für die Flurbeleuchtung im Modellhaus soll eine LED an Pin 9 nach einem Druck auf den Taster an Pin 2 genau 3 Sekunden leuchten und dann ausgehen. Der Taster ist ohne Widerstand gegen GND geschaltet.
Entwickle den Sketch. Erkläre außerdem, warum ein zweiter Tastendruck während der 3 Sekunden nichts bewirkt.
setup() beide Pins fest, prüfe in loop() den Taster.Warum so? Ohne externen Widerstand braucht der Eingang den eingebauten Pull-up — dann bedeutet gedrückt LOW.pinMode(2, INPUT_PULLUP); in loop: wenn digitalRead(2) == LOW → LED an, delay(3000), LED aus.Musterlösung anzeigen (zählt als erledigt)
const int TASTER = 2; const int LED = 9; void setup() { pinMode(TASTER, INPUT_PULLUP); pinMode(LED, OUTPUT); } void loop() { if (digitalRead(TASTER) == LOW) { // gedrückt = LOW digitalWrite(LED, HIGH); delay(3000); // 3 s leuchten digitalWrite(LED, LOW); } }
delay(3000) hält den Prozessor 3 s lang an: In dieser Zeit wird loop() nicht erneut durchlaufen, also wird auch digitalRead nicht aufgerufen — ein Tastendruck wird schlicht nicht gelesen. Wird der Taster nach Ablauf noch gehalten, startet sofort die nächste Leuchtphase.
Mia meint: „C++ ist doch nur C mit cout statt printf. Für unsere Projekte ist es egal, welche Sprache wir nehmen.“
Nimm Stellung zu dieser Aussage. Nenne mindestens drei Unterschiede mit Beispiel und beziehe dich auf ein konkretes Projekt.
std::string statt char-Array, Referenzen statt Zeiger, Klassen mit private/public; C-Code läuft meist trotzdem als C++; Urteil je nach Projekt.Musterlösung anzeigen (zählt als erledigt)
Richtig an der Aussage: C++ ist aus C entstanden; fast jeder C-Code lässt sich auch mit g++ übersetzen, und für ein kurzes Rechenprogramm ist der Unterschied klein.
Aber C++ ergänzt deutlich mehr als die Ausgabe:
std::stringwächst selbst mit,+verkettet,==vergleicht den Inhalt — keinstrcpy, keine zu kleinen Arrays.- Referenzparameter (
int &a) ändern Variablen des Aufrufers ohne*und&beim Aufruf. - Klassen bündeln Daten und Methoden;
privateschützt z. B. einen Punktestand vor ungültigen Werten (Kapselung). boolmittrue/falsestatt 0/1.
Urteil: Für einen Vokabeltrainer mit vielen Wörtern und Spielständen ist C++ klar im Vorteil (string, Klassen). Für hardwarenahe Aufgaben mit wenig Speicher ist schlankes C oft ausreichend — der Arduino nutzt übrigens C++. Die Aussage ist also falsch: Die Wahl hängt vom Projekt ab.
class Punktestand { private: int punkte; public: Punktestand() { punkte = 0; } void treffer() { punkte += 10; } };
Erweitere die Klasse für ein Spiel um eine Methode strafe(int p), die p Punkte abzieht — der Stand darf aber nie unter 0 fallen, und negative Strafen sollen ignoriert werden. Ergänze einen Getter und teste mit zwei Treffern, Strafe 5 und Strafe 40.
punkte private ist, sind die Methoden die einzige Stelle, an der die Regel „nie unter 0“ eingehalten werden muss.if (p < 0) return; punkte -= p; if (punkte < 0) punkte = 0; Getter int getPunkte(). Test: 20 − 5 = 15, dann 0.Musterlösung anzeigen (zählt als erledigt)
#include <iostream> using namespace std; class Punktestand { private: int punkte; public: Punktestand() { punkte = 0; } void treffer() { punkte += 10; } void strafe(int p) { if (p < 0) return; // negative Strafe ignorieren punkte -= p; if (punkte < 0) punkte = 0; // nie unter 0 } int getPunkte() { return punkte; } }; int main() { Punktestand s; s.treffer(); s.treffer(); s.strafe(5); cout << s.getPunkte() << endl; // 15 s.strafe(40); cout << s.getPunkte() << endl; // 0 return 0; }
15 0
Kein new wie in Java: Punktestand s; legt das Objekt direkt an. Das Semikolon nach der schließenden Klammer der Klasse nicht vergessen.
Die Schul-Wetterstation braucht eine Funktion double celsiusZuFahrenheit(double c) mit F = C · 9 / 5 + 32. Sie soll auch in anderen Programmen nutzbar sein.
Lege fest, was in temperatur.h, temperatur.c und main.c steht, und gib den Befehl zum Übersetzen an. Begründe den Include-Guard.
#ifndef/#define/#endif; .c: Definition; main.c: #include "temperatur.h"; gcc main.c temperatur.c -o temperatur.Musterlösung anzeigen (zählt als erledigt)
#ifndef TEMPERATUR_H #define TEMPERATUR_H double celsiusZuFahrenheit(double c); #endif
#include "temperatur.h" double celsiusZuFahrenheit(double c) { return c * 9.0 / 5.0 + 32.0; }
#include <stdio.h> #include "temperatur.h" int main(void) { printf("%.1f\n", celsiusZuFahrenheit(21.5)); return 0; }
$ gcc main.c temperatur.c -o temperatur $ ./temperatur 70.7
Eigene Header stehen in Anführungszeichen (Suche im Projektordner), Bibliotheks-Header in spitzen Klammern. 9.0 / 5.0 verhindert die Ganzzahldivision (9 / 5 wäre 1).
Include-Guard: Binden mehrere Dateien den Header ein (auch indirekt über andere Header), würde sein Inhalt mehrfach eingefügt. #ifndef TEMPERATUR_H sorgt dafür, dass der Präprozessor ihn nur beim ersten Mal übernimmt.
int geheim, tipp; int versuche = 0; do { srand(time(0)); geheim = rand() % 50; printf("Dein Tipp: "); scanf("%d", &tipp); versuche++; } while (tipp != geheim);
Das Spiel soll eine Zahl von 1 bis 50 zufällig wählen, die bis zum Treffer geraten wird. Beurteile den Entwurf: Finde zwei Fehler, erkläre ihre Wirkung beim Spielen und gib eine verbesserte Fassung an.
rand() % 50 liefert 0 … 49. Außerdem fehlen die Hinweise größer/kleiner.Musterlösung anzeigen (zählt als erledigt)
Fehler 1: srand und rand stehen in der Schleife — nach jedem Tipp wird eine neue Geheimzahl gewählt. Gezieltes Eingrenzen ist so unmöglich, das Spiel wird reines Glück. (Innerhalb derselben Sekunde liefert srand(time(0)) sogar immer dieselbe Zahl.)
Fehler 2: rand() % 50 liefert 0 … 49: Die 50 kommt nie vor, dafür die 0. Richtig: 1 + rand() % 50.
Außerdem fehlen die Hinweise „größer/kleiner“ — ohne sie hilft auch die Halbierungsstrategie (höchstens 6 Tipps, da 26 = 64 ≥ 50) nicht.
srand(time(0)); // einmal vor der Schleife int geheim = 1 + rand() % 50; // 1 ... 50 int tipp; int versuche = 0; do { printf("Dein Tipp: "); scanf("%d", &tipp); versuche++; if (tipp < geheim) printf("groesser\n"); else if (tipp > geheim) printf("kleiner\n"); } while (tipp != geheim); printf("Treffer nach %d Versuchen\n", versuche);
Dazu gehört #include <time.h> für time.
alter = int(input("Alter: ")) if alter >= 18: print("Zugang erlaubt ab", altr) else: print("zu jung")
int alter; scanf("%d", &alter); if (alter >= 18) { printf("Zugang erlaubt ab %d\n", altr); } else { printf("zu jung\n"); }
Beide Programme enthalten denselben Tippfehler altr. Diskutiere anhand dieses Beispiels Vor- und Nachteile des Übersetzens vor dem Start (C) gegenüber dem Ausführen Zeile für Zeile (Python).
undeclared vor dem Start. Python: Fehler erst bei Eingabe ≥ 18. Dafür: C muss nach jeder Änderung neu übersetzen.Musterlösung anzeigen (zählt als erledigt)
Python: Bei der Eingabe 15 läuft alles („zu jung“). Erst wenn jemand 18 oder mehr eingibt, bricht das Programm mit NameError: name 'altr' is not defined ab — der Fehler kann also bis zum Einsatz unentdeckt bleiben.
C: Der Compiler prüft das ganze Programm, bevor es überhaupt startet: error: 'altr' undeclared (first use in this function); did you mean 'alter'?. Ohne Korrektur entsteht keine ausführbare Datei. Dazu kommen feste Typen, die weitere Fehler früh aufdecken, und schneller Maschinencode.
Nachteile von C: Nach jeder Änderung neu übersetzen, mehr Schreibarbeit (Deklarationen, Typen), Fehler wie Array-Grenzen findet auch der Compiler nicht.
Ergebnis: Für schnelles Ausprobieren ist der Interpreter bequemer; für Programme, die zuverlässig und schnell laufen müssen — etwa auf einem Mikrocontroller —, überwiegen die Vorteile des Übersetzens.
