Dies ist Inf-Einf-B – Block 2.5

Zeit zum Durchschnaufen (92 Abgaben in VC; 115 letzte Woche).

(1) Wir wiederholen C-Konzepte. (2) Dann beantworten Sie 20 Fragen.

Nach der Pause bearbeiten wir (3) eine Live-Coding-Aufgabe.

Später kreuzen Sie auf einem Zettel an, welche Aufgaben sie lösen konnten. Die Zettel sammeln wir am Ende anonym ein. Reißen Sie das Pseudonym ab oder machen Sie ein Foto. Nach dieser Vorlesung werten wir Ihre bisherigen Kreuze aus. Dann können wir Ihren Lernfortschritt auswerten, wenn Sie uns Ihre Pseudonyme mitteilen. Details folgen im Lauf der Woche.

Schleifenkontrolle: continue und break

Zwei wichtige Befehle zur Kontrolle von Schleifen:

  • continue - Springe zum nächsten Durchlauf
  • break - Beende die Schleife sofort

continue - Nächster Durchlauf

Überspringt den Rest des aktuellen Durchlaufs und macht mit dem nächsten weiter.

for (int i = 0; i < 5; i++)
{
    if (i == 2)
    {
        continue;  // überspringt Rest des Durchlaufs
    }
    printf("%i ", i);
}

Ausgabe: 0 1 3 4

break - Schleife beenden

Beendet die Schleife sofort und komplett.

for (int i = 0; i < 5; i++)
{
    if (i == 2)
    {
        break;  // Bricht Schleife ab
    }
    printf("%i ", i);
}

Ausgabe: 0 1

Der Modulo-Operator %

Was ist Modulo?

Der Modulo-Operator (%) gibt den Rest einer Division zurück.

17 % 5  = 2    // 17 ÷  5 = 3 Rest 2
23 % 10 = 3    // 23 ÷ 10 = 2 Rest 3
10 % 2  = 0    // 10 ÷  2 = 5 Rest 0

Gerade oder Ungerade prüfen

int n = 17;
if (n % 2 == 0)
{
    printf("gerade\n");
}
else
{
    printf("ungerade\n");
}

Ausgabe: ungerade

Wenn n % 2 gleich 0 ist, ist die Zahl gerade!

Letzte Ziffer extrahieren

int number = 1234;
int last_digit = number % 10;

Ergebnis: last_digit = 4

number % 10 gibt immer die letzte Ziffer zurück!

Wechselgeld berechnen (Cash)

int cents = 67;
int quarters = cents / 25;
int remaining = cents % 25;

Ergebnis: quarters = 2, remaining = 17

Integer-Division vs. Float-Division

Das Problem:

int x = 1;
int y = 3;
printf("%f\n", x / y);  // Ausgabe: 0.000000 – falsch

Warum? Beide Operanden sind intInteger-Division!

Lösung 1: Float-Variablen verwenden

float x = 1.0;
float y = 3.0;
printf("%f\n", x / y);

Ausgabe: 0.333333

Lösung 2: Type Casting

int x = 1;
int y = 3;
printf("%f\n", (float) x / y);

Ausgabe: 0.333333

Mindestens ein Operand muss float sein!

Lösung 3: Mit Dezimalzahl dividieren

int x = 1;
printf("%f\n", x / 3.0);

Ausgabe: 0.333333

3.0 ist ein float → Float-Division!

Häufiger Fehler: Durchschnitt berechnen

FALSCH:

int a = 3, b = 3, c = 4;
float avg = (a + b + c) / 3;  // 3.000000 (falsch!)

RICHTIG:

int a = 3, b = 3, c = 4;
float avg = (a + b + c) / 3.0;  // 3.333333 (richtig)

Array-Grenzen

Ein Array mit Größe n hat Indizes von 0 bis n-1

Bei arr[n] ist n-1 der letzte gültige Index!

Verwende i < n, nicht i <= n

Beispiel: Array mit Größe 3

int arr[3];  // Größe 3

arr[1] = 10;
arr[2] = 20;
arr[3] = 30;

Problem: arr[3] existiert nicht!

Array mit Größe 3 → Indizes 0, 1, 2

FALSCH:

int arr[3];
for (int i = 0; i <= 3; i++)  // ← <= ist falsch!
{
    arr[i] = i;  // arr[3] → Fehler!
}

RICHTIG:

int arr[3];
for (int i = 0; i < 3; i++)   // ← < ist richtig!
{
    arr[i] = i;
    // Nur 0, 1, 2
}

String-Struktur: Der NUL-Terminator \0

Was ist ein String?

Ein String ist ein char-Array mit einem speziellen Endzeichen: \0

Beispiel: "Hi!" im Speicher

string s = "Hi!";

Im Speicher:

[H] [i] [!] [\0]
 0   1   2   3

4 Bytes im Speicher: 3 sichtbare Zeichen + 1 Terminator

String = char-Array + \0 am Ende

String-Länge mit strlen

#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string s = "Hello";
    int n = strlen(s);
    printf("Länge: %i\n", n);
}

Ausgabe: Länge: 5

Wichtig:

  • strlen() gibt die Anzahl der Zeichen zurück (ohne \0)
  • String "Hello" hat 5 Zeichen, aber belegt 6 Bytes (inkl. \0)

Groß-/Kleinschreibung: toupper und tolower

Funktionen aus <ctype.h> zum Umwandeln von Zeichen

↓ Beispiele

toupper und tolower - Einzelne Zeichen

#include <ctype.h>
#include <stdio.h>

int main(void)
{
    char c = 'a';
    printf("%c\n", toupper(c));

    char d = 'Z';
    printf("%c\n", tolower(d));
}

Ausgabe: A und z

Ganze Strings umwandeln

#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(void)
{
    string word = "hello";
    for (int i = 0; i < strlen(word); i++)
    {
        printf("%c", toupper(word[i]));
    }
    printf("\n");
}

Ausgabe: HELLO

Exit-Codes: Erfolg oder Fehler?

Was sind Exit-Codes?

Jedes Programm gibt beim Beenden einen Wert zurück: den Exit-Code

return 0 - Erfolg

int main(void)
{
    printf("Hello!\n");
    return 0;  // Alles OK
}

return 0 bedeutet: Programm erfolgreich beendet!

return 1 - Fehler

int main(void)
{
    printf("Error!\n");
    return 1;  // Fehler
}

return 1 (oder andere Werte ≠ 0) bedeutet: Fehler aufgetreten!

Exit-Code in der Shell prüfen

$ ./programm
$ echo $?
0

Der Befehl echo $? zeigt den Exit-Code des letzten Programms

Merksatz

return 0 = Erfolg

return 1 = Fehler

printf: Gut zu wissen

Float-Formatierung: Dezimalstellen kontrollieren

float x = 3.14159265;

printf("%f\n", x);
printf("%.2f\n", x);
printf("%.0f\n", x);

Ausgaben: 3.141593 / 3.14 / 3

%.2f → 2 Dezimalstellen, %.0f → keine Dezimalstellen

Prozent-Zeichen ausgeben

float avg = 0.75;

// FALSCH:
printf("%.0f%\n", avg * 100);  // Fehler! % ist reserviert

// RICHTIG:
printf("%.0f%%\n", avg * 100);  // 75%

Ausgabe: 75%

%% wird benötigt, da % ein Formatzeichen ist!

20 Fragen

Bearbeiten Sie die folgenden 20 Aufgaben alleine (ca. 120 Sekunden pro Aufgabe) auf Ihrem Zettel. Notieren Sie sich nach der Auflösung, ob Sie richtig lagen. Machen Sie dann ein Kreuz (☑︎). Die Zettel sammeln wir am Ende anonym ein.

Reißen Sie sich das Pseudonym ab oder machen Sie ein Foto.

Lösen Sie zusätzlich die Bonus-Fragen, falls Sie Zeit übrig haben (irrelevant für das Korrekt-Kreuz).

Ziel: Feedback zu eigenen Fähigkeiten: "Was weiß ich, was muss ich mir noch einmal ansehen?" Zusätzlich: Rückmeldung für uns ("Was wissen sie?")

Navigation mit Pfeiltasten (links/rechts, zur Lösung: nach unten, Esc für Übersicht).

Strings

Was gibt dieser Code aus?

// hier stehen alle benötigten includes

string s = "Inf-Einf-B!";
int n = strlen(s);
for (int i = 0; i < n; i++)
{
    printf("%c", toupper(s[i]));
}
printf("\n");

Lösung

Der Code gibt INF-EINF-B! aus.

strlen(s) gibt String-Length zurück, die Schleife läuft über alle Zeichen, toupper() wandelt jeden Buchstaben in Großbuchstaben um.

Hi

Wie oft wird "Hi" ausgegeben?

int i = 0;
do {
    printf("Hi\n");
    i++;
} while (i < 0);
printf("Bye\n");

Bonus: Was würde sich ändern, wenn man statt do-while eine while-Schleife verwendet?

Lösung

"Hi" wird einmal ausgegeben, da do-while immer mindestens einmal ausführt.

Bonus: Bei einer while-Schleife würde nichts ausgegeben, da die Bedingung von Anfang an false ist.

1 bis 10

Schreiben Sie Code, der folgendes prüft: Enthält die int-Variable x eine Zahl zwischen 1 und 10 (inklusive)? Dann "Gültig" ausgeben, sonst nichts. Auf #includes und die main-Funktion können Sie verzichten.

Fertig? Bitte 3x auf den Tisch klopfen.

Lösung

if (x >= 1 && x <= 10)
{
    printf("Gültig\n");
}

Syntaxfehler

Wie viele Syntaxfehler enthält dieser Code?

#include <stdio.h>

int main(void)
{
    int x = get_int("x: ")
    if (x == 5)
    {
        printf("x ist fünf")
    }
    return 0
}

Lösung

Der Code enthält 4 Syntaxfehler:

  • Fehlender Include: #include <cs50.h>
  • Fehlendes Semikolon nach get_int("x: ")
  • Fehlendes Semikolon nach printf("x ist fünf")
  • Fehlendes Semikolon nach return 0

Großbuchstaben-Prozent

Was ist das Problem mit diesem Code?

// hier stehen alle benötigten includes

// in irgendeiner Funktion
string s = "MeinText";
int uppercase = 0;
for (int i = 0; i < strlen(s); i++)
{
    if (s[i] >= 'A' && s[i] <= 'Z')
    {
        uppercase++;
    }
}

float anteil = uppercase / strlen(s);
printf("Uppercase: %f%%\n", anteil * 100.0);

Lösung

Problem: Integer-Division!

Der Text "MeinText" hat 2 Großbuchstaben (M, T) von 8 Zeichen = 25%

Aber: 2 / 8 = 0 (Integer-Division), dann 0 * 100 = 0

Das Programm gibt 0% aus statt 25%!

Lösung: float anteil = (float) uppercase / strlen(s);

Schleife

Schreiben Sie eine Schleife ohne die Modulo-Operation zu verwenden, die die ersten 100 geraden Zahlen ausgibt, also 2, 4, 6, ... 200.

Bonus: Schreiben Sie eine Alternative mit Modulo.

Lösung

Lösung 1:
for (int i = 1; i <= 100; i++)
{
    printf("%i\n", i * 2);
}
Lösung 2 (Bonus mit Modulo):
for (int i = 1; i <= 200; i++)
{
    if (i % 2 == 0)
    {
        printf("%i\n", i);
    }
}

Manuell strlen

Schreiben Sie Code, der die Länge eines Strings ohne strlen() berechnet.

Lösung

string s = get_string("Text: ");
int n = 0;
while (s[n] != '\0')
{
    n++;
}
printf("Länge: %i\n", n);

Die Schleife läuft durch den String bis zum \0-Terminator und zählt die Zeichen.

Odd Exit

Wie viele Syntaxfehler? Welcher Exit-Code bei Eingabe von 10?

int main(void)
{
    int num = get_int("Number: ");
    printf("You entered %i\n", num)
    if (num % 2)
    {
        return 1;
    }
    return 0;
}

Lösung

1 Syntaxfehler: Fehlendes Semikolon nach printf

Exit-Code bei 10: 0

Erklärung:

  • num % 2 bei einer geraden Zahl (10) ergibt 0
  • if (0) ist false → der if-Block wird nicht ausgeführt
  • return 0; wird ausgeführt → Exit-Code ist 0

Weiter!

Was gibt folgender Code aus?

const int N = 6;
for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
        if (i == j) {
            printf("  ");
            continue;
        }
        printf("# ");
    }
    printf("\n");
}

Bonus: Was wäre bei break statt continue?

Lösung

continue bricht Durchlauf ab, setzt beim nächsten fort:

  # # # # #
#   # # # #
# #   # # #
# # #   # #
# # # #   #
# # # # #   

Bonus: Nur das untere Dreieck wird ausgegeben:


#
# #
# # #
# # # #
# # # # #   

Details: s. Short zu Schleifen

Exit-Code

Welchen Exit-Code gibt dieses Programm zurück, wenn es ohne Argumente mit ./programm aufgerufen wird?

int main(int argc, string argv[])
{
    if (argc != 2)
    {
        printf("Usage: ./programm <name>\n");
        return 1;
    }
    printf("Hello, %s\n", argv[1]);
    return 0;
}

Bonus: Was wäre der Exit-Code bei ./programm Alice?

Lösung

Exit-Code: 1 (Fehler)

Erklärung:

  • Bei Aufruf ./programm ist argc = 1 (nur Programmname)
  • Bedingung argc != 2 ist true → if-Block wird ausgeführt
  • return 1; signalisiert einen Fehler

Bonus: Bei ./programm Alice wäre der Exit-Code 0 (Erfolg), da argc = 2

Nimm Zwei!

Schreiben Sie eine for-Schleife, die jede zweite Zahl des Arrays ausgibt (also 4, 7, 3).

Array: int zahlen[] = {4, 2, 7, 1, 3};

Bonus: Wie würden Sie die Zahlen in umgekehrter Reihenfolge ausgeben?

Lösung

for (int i = 0; i < 5; i += 2) {
    printf("%i ", zahlen[i]);
}

// Alternative mit Modulo
for (int i = 0; i < 5; i++) {
    if (i % 2 == 0) {
        printf("%i ", zahlen[i]);
    }
}

Ausgabe beider Lösungen: 4 7 3 (Indizes 0, 2, 4)

Bonus: Um die Zahlen in umgekehrter Reihenfolge auszugeben:

for (int i = 4; i >= 0; i--) {
    printf("%i ", zahlen[i]);
}

Vertippt

Sie haben versehentlich eine Datei namens "Hello.c" (mit großem H) erstellt. Welche Befehle führen Sie nacheinander aus um:

  • zu überprüfen ob die Datei existiert
  • sie in "hello.c" umzubenennen
  • zu prüfen ob es geklappt hat

Bonus: Wie können Sie sich den Inhalt der Datei anzeigen lassen?

Lösung

Konsole:
$ ls
$ mv Hello.c hello.c
$ ls

Bonus:
cat hello.c

The Smiths

In der Datei names.txt steht:

1: Alice Smith
2: Bob Smith
3: Alice Brown

Welche Variante ermittelt, welcher Nachname am häufigsten vorkommt?

Variante A:
cat names.txt | cut -d' ' -f3 | sort | uniq -c | sort -n
Variante B:
cat names.txt | cut -d':' -f2 | cut -d' ' -f3 | sort | uniq -c

Lösung

Richtig ist Variante A:
cat names.txt | cut -d' ' -f3 | sort | uniq -c | sort -n

Details: siehe Short zu Kommandozeile.

Ticketautomat

Welches Problem bzw. welche Probleme hat das Programm?

// benötigte includes sind vorhanden
int main(void)
{
    float sum = 0;
    const float TICKET_PRICE = 0.8;
    for (int i = 0; i < 8; i++) {
        sum += 0.10;
        printf("Eingeworfen: %.2f €\n", sum);
        if (sum == TICKET_PRICE) {
           printf("Danke! Das Ticket wird gedruckt.\n");
        } else {
            printf("Bitte werfen Sie 0.80 EUR ein!\n");
        }
    }
}

Problem: Float-Vergleich mit ==

Eingeworfen werden 8× 10 Cent; Ticket sollte gedruckt werden.

Aber: sum wird nie exakt 0.8, sondern z.B. 0.8000000715
sum == TICKET_PRICE ist immer false!

Besser mit int-Cent-Beträgen arbeiten:

int sum = 0;
const int TICKET_PRICE = 80;  // 80 Cent
for (int i = 0; i < 8; i++) {
    sum += 10;  // 10 Cent Münze
    printf("Eingeworfen: %i Cent\n", sum);
    if (sum == TICKET_PRICE) {
        printf("Danke! Das Ticket wird gedruckt.\n");
    } else {
        printf("Bitte werfen Sie mehr Münzen ein!\n");
    }
}

Ihgitt

Überarbeiten Sie dieses fehlerhafte Programm, sodass es ausführbar ist und die gewünschte Funktionalität hat:

// benötigte includes sind vorhanden

int main(int argc, string argv[])
{
    for (int i = 0; i < argc; i++)
    {
        printf("%i ", i);
        if(i == 3) { break; }
    }
    printf("\nLetzter Durchlauf war: %i\n", i);
}

Lösung

Problem: i ist nur in der for-Schleife gültig (Scope) → Compiler-Fehler

Lösung: i vor der Schleife deklarieren:

// benötigte includes sind vorhanden

int main(int argc, string argv[])
{
    int i;
    for (i = 0; i < argc; i++)
    {
        printf("%i ", i);
        if(i == 3) { break; }
    }
    printf("\nLetzter Durchlauf war: %i\n", i);
}

Zähler

Was genau (genauen Wert und allgemein) gibt der Code aus?

string text = "Hello, World!";
int x = 0;

for (int i = 0; i < strlen(text); i++)
{
    if ((text[i] >= 'a' && text[i] <= 'z') ||
        (text[i] >= 'A' && text[i] <= 'Z'))
    {
        x++;
    }
}
printf("x: %i\n", x);

Lösung

Ausgabe: x: 10

Der Code zählt nur Buchstaben (a-z, A-Z), keine Sonderzeichen.

"Hello, World!" hat 10 Buchstaben: H, e, l, l, o, W, o, r, l, d

Komma, Leerzeichen und Ausrufezeichen werden nicht gezählt.

Mystery

Welchen Exit-Code gibt dieser Code zurück?

int mystery(int x)
{
    if (x <= 0)
        return 0;
    return x + mystery(x - 1);
}

int main(void)
{
    return mystery(3);
}

Bonus: Mit welchem arithmetischen Ausdruck könnte man den Code in mystery ersetzen, um das Ergebnis effizienter zu berechnen?

Lösung

Der Code gibt den Wert 6 zurück (Summe von 1 bis x).

Bonus: return x * (x – 1) / 2

c, c, c

Was macht dieser Code?

char c = 'a';
while (c <= 'z')
{
    printf("%c: %i %i\n", c, c, c - 'a');
    c++;
}

Lösung

Der Code gibt alle Kleinbuchstaben von 'a' bis 'z' zusammen mit ihren ASCII-Werten aus gefolgt von den Ziffern 0 bis 25.

Ausgabe:

a: 97 0
b: 98 1
...
z: 122 25

Dreiertausch

Schreiben Sie Code, der die ersten beiden Zeichen eines Strings s (Länge: 10) vertauscht.

Beispiel: Aus "CS" wird "SC".

Lösung

Code:
char temp = s[0];
s[0] = s[1];
s[1] = temp;

Dieser Code tauscht die ersten beiden Zeichen des Strings s.

Parsons Problem

Ordnen Sie die Zeilen so an, dass das Programm das Maximum im Array findet. Achtung: Nicht alle Zeilen werden benötigt.

if (scores[i] >= max)                    // A
{                                        // B
if (scores[i] == max)                    // C
}                                        // D
{                                        // E
if (scores[i] < max)                     // F
int max = scores[0];                     // G
max = scores[i];                         // H
printf("Maximum: %i\n", max);            // I
}                                        // J
if (scores[i] > max)                     // K
int scores[] = {72, 85, 91, 68, 77};     // L
for (int i = 1; i < 5; i++)              // M

Lösung 1/2

int scores[] = {72, 85, 91, 68, 77};     // L
int max = scores[0];                     // G
for (int i = 1; i < 5; i++)              // M
{                                        // B
    if (scores[i] > max)                 // K
    {                                    // E
        max = scores[i];                 // H
    }                                    // D
}                                        // J
printf("Maximum: %i\n", max);            // I

Der Code findet: Maximum: 91

Lösung 2/2

Nicht benötigte Zeilen:

  • if (scores[i] < max) - sucht Minimum, nicht Maximum
  • if (scores[i] >= max) - überschreibt bei Gleichheit (unnötig)
  • if (scores[i] == max) - findet nur gleiche Werte, nicht größere

Hinweis:

Die Schleife startet bei i = 1 (nicht 0), weil wir max bereits mit scores[0] initialisiert haben.

Vielen Dank fürs Mitmachen!

Bitte zählen Sie nun, wie viele Aufgaben Sie korrekt gelöst haben.

Geben Sie dann die Zettel nach außen; wir möchten die Zettel gerne anonym statistisch auswerten.