MINT lernen

Übung — AFB III (Verallgemeinern und Reflektieren)

Zehn Aufgaben zum Entwerfen, Begründen und Beurteilen — hier zählt, dass du erklären kannst, warum dein Code funktioniert.

Dein Fortschritt:
0 / 0 Aufgaben
3

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.

A1
Die Quersumme berechnen
AFB III

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.

Strategie: Zerlege die Zahl von hinten: 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.
Lösungsskizze: Summe s = 0; solange n > 0: s += n % 10; n = n / 10; danach return s;
Musterlösung anzeigen (zählt als erledigt)
C
#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;
}
Ausgabe
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.

A2
Zwei gleiche Wörter — „verschieden“?
AFB III
C
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.

Strategie: Überlege, wofür der Name eines Arrays in einem Ausdruck steht.Warum so? == vergleicht immer genau die beiden Werte links und rechts — hier sind das keine Buchstaben.
Lösungsskizze: 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:

C
if (strcmp(a, b) == 0) {
    printf("gleich\n");
}

In C++ mit std::string funktioniert == dagegen wie erwartet.

A3
Zwei Ergebnisse aus einer Funktion
AFB III

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};

Strategie: Die Funktion bekommt zusätzlich die Adressen zweier Variablen des Aufrufers und schreibt die Ergebnisse dorthin.Warum so? So arbeitet auch scanf: Es bekommt eine Adresse, weil es in eine fremde Variable schreiben muss.
Lösungsskizze: void minmax(int a[], int n, int *min, int *max); Aufruf minmax(werte, 5, &kl, &gr);
Musterlösung anzeigen (zählt als erledigt)
C
#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;
}
Ausgabe
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.

A4
Läuft doch — also richtig?
AFB III
C
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.

Strategie: Zähle die Durchläufe und vergleiche mit den erlaubten Indizes.Warum so? Eine Behauptung ist widerlegt, sobald ein einziger Grund zeigt, dass sie nicht allgemein gilt.
Lösungsskizze: i läuft von 0 bis 5 → 6 Zugriffe; erlaubt sind 0 … 4. 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]).

A5
Nachtlicht mit Taster
AFB III

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.

Strategie: Lege in 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.
Lösungsskizze: pinMode(2, INPUT_PULLUP); in loop: wenn digitalRead(2) == LOW → LED an, delay(3000), LED aus.
Musterlösung anzeigen (zählt als erledigt)
Arduino-Sketch
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.

A6
„C++ ist nur C mit cout“
AFB III

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.

Strategie: Sammle, was in C++ neu dazukommt, und überlege jeweils, welches C-Problem es löst.Warum so? Eine Stellungnahme braucht Argumente für beide Seiten und am Ende ein eigenes Urteil.
Lösungsskizze: 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::string wächst selbst mit, + verkettet, == vergleicht den Inhalt — kein strcpy, keine zu kleinen Arrays.
  • Referenzparameter (int &a) ändern Variablen des Aufrufers ohne * und & beim Aufruf.
  • Klassen bündeln Daten und Methoden; private schützt z. B. einen Punktestand vor ungültigen Werten (Kapselung).
  • bool mit true/false statt 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.

A7
Eine Klasse erweitern
AFB III
C++ · Ausgangsklasse
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.

Strategie: Prüfe erst die Eingabe, ziehe dann ab und korrigiere zum Schluss.Warum so? Weil punkte private ist, sind die Methoden die einzige Stelle, an der die Regel „nie unter 0“ eingehalten werden muss.
Lösungsskizze: 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)
C++
#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;
}
Ausgabe
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.

A8
Ein Programm auf drei Dateien verteilen
AFB III

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.

Strategie: Trenne das „Was gibt es?“ (Prototyp) vom „Wie funktioniert es?“ (Definition).Warum so? Andere Programme brauchen nur die Header-Datei einzubinden und die .c-Datei mitzuübersetzen.
Lösungsskizze: .h: Prototyp mit #ifndef/#define/#endif; .c: Definition; main.c: #include "temperatur.h"; gcc main.c temperatur.c -o temperatur.
Musterlösung anzeigen (zählt als erledigt)
C · temperatur.h
#ifndef TEMPERATUR_H
#define TEMPERATUR_H

double celsiusZuFahrenheit(double c);

#endif
C · temperatur.c
#include "temperatur.h"

double celsiusZuFahrenheit(double c) {
    return c * 9.0 / 5.0 + 32.0;
}
C · main.c
#include <stdio.h>
#include "temperatur.h"

int main(void) {
    printf("%.1f\n", celsiusZuFahrenheit(21.5));
    return 0;
}
Konsole
$ 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.

A9
Ein Zahlenraten-Entwurf
AFB III
C
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.

Strategie: Prüfe, welche Anweisungen in der Schleife stehen, die nur einmal ausgeführt werden dürften — und rechne den Bereich von rand() % 50 nach.Warum so? Ein Spiel wird nicht an einem Testlauf beurteilt, sondern daran, ob es für jede Zahl richtig funktioniert.
Lösungsskizze: 1) Neue Geheimzahl bei jedem Versuch. 2) 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.

C
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.

A10
Compiler oder Interpreter?
AFB III
Python
alter = int(input("Alter: "))
if alter >= 18:
    print("Zugang erlaubt ab", altr)
else:
    print("zu jung")
C · derselbe Ablauf
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).

Strategie: Überlege, wann jeweils der Fehler bemerkt wird — und für welche Eingaben.Warum so? Ein Fehler, der nur in einem selten erreichten Zweig steckt, kann bei Tests lange unbemerkt bleiben.
Lösungsskizze: C: gcc meldet 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.