Grundlagenpraktikum: Programierung - Exam.pdf

Grundlagenpraktikum: Programierung - Exam
Aufgabe 1) Ein Unternehmen möchte eine Basis für ein Automatisierungsprojekt in der IT-Infrastruktur mit der Programmiersprache Python entwickeln. Das Projekt umfasst das Einlesen von CSV-Daten, die Analyse dieser Daten sowie das Generieren eines Berichts. Die Daten enthalten Informationen zu Servern in einem Rechenzentrum, wie z.B. „Host-Name“, „IP-Adresse“, „CPU-Auslastung“ und „Speicherverbrauc...

© StudySmarter 2024, all rights reserved.

Aufgabe 1)

Ein Unternehmen möchte eine Basis für ein Automatisierungsprojekt in der IT-Infrastruktur mit der Programmiersprache Python entwickeln. Das Projekt umfasst das Einlesen von CSV-Daten, die Analyse dieser Daten sowie das Generieren eines Berichts. Die Daten enthalten Informationen zu Servern in einem Rechenzentrum, wie z.B. „Host-Name“, „IP-Adresse“, „CPU-Auslastung“ und „Speicherverbrauch“.Schreibe ein Python-Skript, das folgende Anforderungen erfüllt:

a)

Erstelle ein Python-Programm, das die CSV-Datei einliest und in ein DataFrame (verwende dazu die Bibliothek \texttt{Pandas}) umwandelt. Das DataFrame soll die Struktur der CSV-Datei vollständig widerspiegeln, d.h., jede Spalte der CSV-Datei wird zu einer Spalte des DataFrames. Überprüfe, ob die CSV-Datei korrekt eingelesen wurde, indem Du die ersten fünf Zeilen des DataFrames mithilfe der Methode \texttt{head()} ausgibst.

import pandas as pddef read_csv_file(file_path):    # Lies die CSV-Datei ein    df = pd.read_csv(file_path)    # Gib die ersten fünf Zeilen aus    print(df.head())    return df# Beispielaufruffile_path = 'server_data.csv'data_frame = read_csv_file(file_path)

Lösung:

Um ein Python-Programm zu schreiben, das eine CSV-Datei einliest und in ein DataFrame umwandelt, kannst Du die Pandas-Bibliothek verwenden. Das folgende Programm liest die CSV-Datei ein und gibt die ersten fünf Zeilen aus, um zu überprüfen, ob die Datei korrekt eingelesen wurde. Folge diesen Schritten:

  • Importiere die Pandas-Bibliothek.
  • Definiere eine Funktion read_csv_file(file_path), die eine CSV-Datei einliest und in ein DataFrame umwandelt.
  • Lies die CSV-Datei mit pd.read_csv(file_path) ein.
  • Gib die ersten fünf Zeilen des DataFrames mit df.head() aus.
  • Rufe die Funktion mit dem Dateipfad der CSV-Datei auf.

Hier ist das vollständige Skript:

import pandas as pddef read_csv_file(file_path):    # Lies die CSV-Datei ein    df = pd.read_csv(file_path)    # Gib die ersten fünf Zeilen aus    print(df.head())    return df# Beispielaufruffile_path = 'server_data.csv'data_frame = read_csv_file(file_path)

b)

Erweitere das Skript um eine Analyse, die die durchschnittliche CPU-Auslastung und den durchschnittlichen Speicherverbrauch aller Server ermittelt. Verwende dabei die Methoden \texttt{mean()} der Pandas-Bibliothek, um die Berechnungen durchzuführen. Gib anschließend die berechneten Mittelwerte auf der Konsole aus.

import pandas as pddef read_csv_file(file_path):    df = pd.read_csv(file_path)    print(df.head())    return dfdef analyze_data(df):    # Berechne die durchschnittliche CPU-Auslastung    avg_cpu_usage = df['CPU-Auslastung'].mean()    # Berechne den durchschnittlichen Speicherverbrauch    avg_memory_usage = df['Speicherverbrauch'].mean()    # Gib die Ergebnisse aus    print('Durchschnittliche CPU-Auslastung: ', avg_cpu_usage)    print('Durchschnittlicher Speicherverbrauch: ', avg_memory_usage)# Beispielaufruffile_path = 'server_data.csv'data_frame = read_csv_file(file_path)analyze_data(data_frame)

Lösung:

Um das Python-Skript zu erweitern, sodass es die durchschnittliche CPU-Auslastung und den durchschnittlichen Speicherverbrauch aller Server berechnet, kannst Du die Methode mean() der Pandas-Bibliothek verwenden. Folge diesen Schritten:

  • Definiere eine zusätzliche Funktion analyze_data(df), die die Analyse durchführt.
  • In dieser Funktion berechnest Du die durchschnittliche CPU-Auslastung und den durchschnittlichen Speicherverbrauch mit der Methode mean().
  • Gib die berechneten Mittelwerte auf der Konsole aus.
  • Rufe die Analyselfunktion mit dem DataFrame auf, das aus der CSV-Datei eingelesen wurde.

Hier ist das vollständige Skript:

import pandas as pddef read_csv_file(file_path):    df = pd.read_csv(file_path)    print(df.head())    return dfdef analyze_data(df):    # Berechne die durchschnittliche CPU-Auslastung    avg_cpu_usage = df['CPU-Auslastung'].mean()    # Berechne den durchschnittlichen Speicherverbrauch    avg_memory_usage = df['Speicherverbrauch'].mean()    # Gib die Ergebnisse aus    print('Durchschnittliche CPU-Auslastung: ', avg_cpu_usage)    print('Durchschnittlicher Speicherverbrauch: ', avg_memory_usage)# Beispielaufruffile_path = 'server_data.csv'data_frame = read_csv_file(file_path)analyze_data(data_frame)

Mit diesem erweiterten Skript kannst Du nun die CSV-Datei einlesen und eine grundlegende Analyse der Serverdaten durchführen. Die berechneten Mittelwerte für die CPU-Auslastung und den Speicherverbrauch werden auf der Konsole ausgegeben.

Aufgabe 2)

In einem Objektorientierten System für eine Bibliothek werden Bücher und Zeitschriften verwaltet. Jedes Buch und jede Zeitschrift hat gemeinsame Eigenschaften wie Titel, Autor und Erscheinungsjahr. Zusätzlich haben Bücher Attribute wie ISBN und Seitenzahl, während Zeitschriften Attribute wie ISSN und Ausgabe haben. Implementiere ein Java-Programm, um diese Anforderungen zu erfüllen.

a)

Definiere eine abstrakte Klasse \textcode{Publication}, welche die gemeinsamen Attribute \textcode{title}, \textcode{author} und \textcode{yearPublished} enthält. Schreibe auch die notwendigen Getter- und Setter-Methoden sowie einen abstrakten Methodenkopf für eine Methode \textcode{printDetails()}, die später in den Unterklassen überschrieben wird.

Lösung:

Um die Anforderungen zu erfüllen, kannst Du eine abstrakte Klasse Publication in Java definieren. Diese Klasse enthält die gemeinsamen Attribute title, author, und yearPublished sowie die notwendigen Getter- und Setter-Methoden. Außerdem wird ein abstrakter Methodenkopf für die Methode printDetails() deklariert. Hier ist ein Beispiel, wie Du dies in Java umsetzen kannst:

  public abstract class Publication {       private String title;      private String author;      private int yearPublished;       // Getter-Methoden      public String getTitle() {          return title;      }       public String getAuthor() {          return author;      }       public int getYearPublished() {          return yearPublished;      }       // Setter-Methoden      public void setTitle(String title) {          this.title = title;      }       public void setAuthor(String author) {          this.author = author;      }       public void setYearPublished(int yearPublished) {          this.yearPublished = yearPublished;      }       // Abstrakte Methode printDetails      public abstract void printDetails();  }  

b)

Implementiere die Klasse \textcode{Book}, welche von der abstrakten Klasse \textcode{Publication} erbt. Zusatzlich zu den geerbten Attributen, füge die spezifischen Attribute \textcode{isbn} und \textcode{numPages} hinzu. Implementiere den Konstruktor und die Methode \textcode{printDetails()}, sodass sie alle Informationen des Buches ausgibt.

Lösung:

Um die Klasse Book zu implementieren, welche von der abstrakten Klasse Publication erbt, müssen wir die notwendigen spezifischen Attribute isbn und numPages hinzufügen. Außerdem sollten wir den Konstruktor und die Methode printDetails() implementieren, sodass sie alle Informationen des Buches ausgibt. Hier ist ein Beispiel, wie dies in Java umsetzen kannst:

  public class Book extends Publication {      private String isbn;      private int numPages;       // Konstruktor      public Book(String title, String author, int yearPublished, String isbn, int numPages) {          setTitle(title);          setAuthor(author);          setYearPublished(yearPublished);          this.isbn = isbn;          this.numPages = numPages;      }       // Getter-Methoden      public String getIsbn() {          return isbn;      }       public int getNumPages() {          return numPages;      }       // Setter-Methoden      public void setIsbn(String isbn) {          this.isbn = isbn;      }       public void setNumPages(int numPages) {          this.numPages = numPages;      }        // Implementierung der abstrakten Methode printDetails      @Override      public void printDetails() {          System.out.println("Title: " + getTitle());          System.out.println("Author: " + getAuthor());          System.out.println("Year Published: " + getYearPublished());          System.out.println("ISBN: " + isbn);          System.out.println("Number of Pages: " + numPages);      }  }  

c)

Implementiere die Klasse \textcode{Magazine}, welche ebenfalls von der abstrakten Klasse \textcode{Publication} erbt. Zusatzlich zu den geerbten Attributen, füge die spezifischen Attribute \textcode{issn} und \textcode{issueNumber} hinzu. Implementiere den Konstruktor und die Methode \textcode{printDetails()}, sodass sie alle Informationen der Zeitschrift ausgibt.

Lösung:

Um die Klasse Magazine zu implementieren, welche von der abstrakten Klasse Publication erbt, müssen wir die notwendigen spezifischen Attribute issn und issueNumber hinzufügen. Außerdem sollten wir den Konstruktor und die Methode printDetails() implementieren, sodass sie alle Informationen der Zeitschrift ausgibt. Hier ist ein Beispiel, wie Du dies in Java umsetzen kannst:

  public class Magazine extends Publication {      private String issn;      private int issueNumber;       // Konstruktor      public Magazine(String title, String author, int yearPublished, String issn, int issueNumber) {          setTitle(title);          setAuthor(author);          setYearPublished(yearPublished);          this.issn = issn;          this.issueNumber = issueNumber;      }       // Getter-Methoden      public String getIssn() {          return issn;      }       public int getIssueNumber() {          return issueNumber;      }       // Setter-Methoden      public void setIssn(String issn) {          this.issn = issn;      }       public void setIssueNumber(int issueNumber) {          this.issueNumber = issueNumber;      }       // Implementierung der abstrakten Methode printDetails      @Override      public void printDetails() {          System.out.println("Title: " + getTitle());          System.out.println("Author: " + getAuthor());          System.out.println("Year Published: " + getYearPublished());          System.out.println("ISSN: " + issn);          System.out.println("Issue Number: " + issueNumber);      }  }  

d)

Schreibe eine Main-Klasse, in der du ein Buch und eine Zeitschrift instanziierst und die Methode \textcode{printDetails()} für jedes Objekt aufrufst, um die erwartete Ausgabe zu überprüfen. Zeige dabei auch wie du in Java Fehler handhabst, indem du z.B. überprüfst, ob Felder wie \textcode{title} nicht leer sind und im Fehlerfall eine Exception wirfst.

Lösung:

Um die Anforderungen zu erfüllen, kannst Du eine Main-Klasse schreiben, die ein Buch und eine Zeitschrift instanziiert und die Methode printDetails() für jedes Objekt aufruft. Dabei zeige ich auch, wie Du in Java Fehler handhabst, indem Du überprüfst, ob Felder wie title nicht leer sind und im Fehlerfall eine Exception wirfst. Hier ist ein Beispiel:

  public class Main {      public static void main(String[] args) {          try {              // Buch instanziieren              Book book = new Book("Effective Java", "Joshua Bloch", 2008, "978-0134685991", 416);               // Zeitschrift instanziieren              Magazine magazine = new Magazine("National Geographic", "Various Authors", 2021, "0027-9358", 3);               // Details des Buches und der Zeitschrift ausgeben              book.printDetails();              magazine.printDetails();           } catch (Exception e) {              System.out.println("Fehler: " + e.getMessage());          }      }       // Fehlerüberprüfungen in den Settermethoden      public void setTitle(String title) {          if (title == null || title.isEmpty()) {              throw new IllegalArgumentException("Titel darf nicht leer sein");          }              this.title = title;      }       public void setAuthor(String author) {          if (author == null || author.isEmpty()) {              throw new IllegalArgumentException("Autor darf nicht leer sein");          }              this.author = author;      }       public void setYearPublished(int yearPublished) {          if (yearPublished < 0) {              throw new IllegalArgumentException("Erscheinungsjahr darf nicht negativ sein");          }              this.yearPublished = yearPublished;      }  }  

Aufgabe 3)

Schreibe ein C-Programm, das eine einfache Datenbank für Studenteninformationen verwaltet. Die Datenbank soll Studentendaten wie Matrikelnummer, Name und Studienfach speichern und grundlegende Funktionen wie Hinzufügen, Suchen und Löschen eines Studenten ermöglichen. Implementiere die Datenbank mithilfe von Arrays und Zeigern. Deine Lösung soll aus mehreren Dateien bestehen und Speicher dynamisch verwalten. Denke daran, zur Fehlerbehandlung entsprechende Maßnahmen zu implementieren und dein Programm auf Speicherlecks zu prüfen.

a)

Teilaufgabe 1: Erstelle die Header-Dateien und Quellcodes für Dein Programm. Definiere in der Header-Datei eine Struktur struct Student, die die Matrikelnummer, den Namen (C-String) und das Studienfach (C-String) enthält. Schreibe in der Quellcode-Datei die Funktionen zum Hinzufügen eines neuen Studenten und zum Anzeigen aller gespeicherten Studenten. Achte darauf, die Speicheralokation korrekt durchzuführen.

Lösung:

Um diese Teilaufgabe zu lösen, folgen wir den Anweisungen Schritt für Schritt:

  • Erstellen einer Header-Datei zur Definition der Struktur struct Student.
  • Erstellen einer Quellcode-Datei, die die Funktionen zur Verwaltung der Studenteninformationen enthält.

Hier ist der Code für die Header-Datei student.h:

'#ifndef STUDENT_H#define STUDENT_H#include <stdlib.h>#define NAME_LEN 100#define SUBJECT_LEN 100typedef struct {    int matrikelnummer;    char name[NAME_LEN];    char studienfach[SUBJECT_LEN];} Student;Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach);void student_zeige_alle(Student** studenten, int anzahl);void student_loesche(Student* student);#endif'

Dieser Header enthält:

  • Definition des struct Student, welches Matrikelnummer, Name und Studienfach als Attribute enthält.
  • Funktionsprototypen für student_erstelle, student_zeige_alle und student_loesche.

Hier ist der Code für die Quellcode-Datei student.c:

'#include "student.h"#include <stdio.h>#include <string.h>Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach) {    Student* s = (Student*)malloc(sizeof(Student));    if (s == NULL) {        fprintf(stderr, "Speicher konnte nicht zugewiesen werden");        exit(EXIT_FAILURE);    }    s->matrikelnummer = matrikelnummer;    strncpy(s->name, name, NAME_LEN-1);    s->name[NAME_LEN-1] = '\0';    strncpy(s->studienfach, studienfach, SUBJECT_LEN-1);    s->studienfach[SUBJECT_LEN-1] = '\0';    return s;}void student_zeige_alle(Student** studenten, int anzahl) {    for (int i = 0; i < anzahl; i++) {        Student* s = studenten[i];        printf("Matrikelnummer: %d, Name: %s, Studienfach: %s", s->matrikelnummer, s->name, s->studienfach);    }}void student_loesche(Student* s) {    free(s);}'

Diese Quellcode-Datei enthält:

  • Die Funktion student_erstelle, die einen neuen Studenten erstellt und den Speicher dynamisch alloziert.
  • Die Funktion student_zeige_alle, die alle gespeicherten Studenten anzeigt.
  • Die Funktion student_loesche, die den Speicher eines Studenten freigibt.

Vergiss nicht, alle Funktionen aufzurufen und zu testen, um sicherzustellen, dass sie wie erwartet funktionieren und keine Speicherlecks auftreten.

b)

Teilaufgabe 2: Implementiere eine Funktion Student* find_student(int mat_nr), die einen Zeiger auf den Studenten mit der angegebenen Matrikelnummer zurückgibt. Wenn der Student nicht gefunden wird, soll die Funktion NULL zurückgeben. Teste die Funktion, indem Du mehrere Studenten hinzufügst und dann nach deren Matrikelnummer suchst.

Lösung:

Um diese Teilaufgabe zu lösen, implementieren wir die Funktion find_student in der Quellcode-Datei student.c. Dazu benötigen wir auch eine Anpassung der Header-Datei student.h, um die Funktion zu deklarieren. Auch sollten wir sicherstellen, dass wir eine Testfunktion haben, um die Funktionsweise zu überprüfen.

  • Erweitern der Header-Datei, um die neue Funktion zu deklarieren.
  • Implementieren der Funktion in der Quellcode-Datei.
  • Erstellen eines einfachen Tests, um die Funktion zu überprüfen.

Hier ist der erweiterte Code für die Header-Datei student.h:

'#ifndef STUDENT_H#define STUDENT_H#include <stdlib.h>#define NAME_LEN 100#define SUBJECT_LEN 100typedef struct {    int matrikelnummer;    char name[NAME_LEN];    char studienfach[SUBJECT_LEN];} Student;Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach);void student_zeige_alle(Student** studenten, int anzahl);void student_loesche(Student* student);Student* find_student(Student** studenten, int anzahl, int mat_nr);#endif'

Hier ist der erweiterte Code für die Quellcode-Datei student.c:

'#include "student.h"#include <stdio.h>#include <string.h>Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach) {    Student* s = (Student*)malloc(sizeof(Student));    if (s == NULL) {        fprintf(stderr, "Speicher konnte nicht zugewiesen werden");        exit(EXIT_FAILURE);    }    s->matrikelnummer = matrikelnummer;    strncpy(s->name, name, NAME_LEN-1);    s->name[NAME_LEN-1] = '\0';    strncpy(s->studienfach, studienfach, SUBJECT_LEN-1);    s->studienfach[SUBJECT_LEN-1] = '\0';    return s;}void student_zeige_alle(Student** studenten, int anzahl) {    for (int i = 0; i < anzahl; i++) {        Student* s = studenten[i];        printf("Matrikelnummer: %d, Name: %s, Studienfach: %s", s->matrikelnummer, s->name, s->studienfach);    }}void student_loesche(Student* s) {    free(s);}Student* find_student(Student** studenten, int anzahl, int mat_nr) {    for (int i = 0; i < anzahl; i++) {        if (studenten[i]->matrikelnummer == mat_nr) {            return studenten[i];        }    }    return NULL;}'

Nun fügen wir eine Testfunktion in die Quellcode-Datei main.c ein, um die Funktion zu testen:

'#include "student.h"#include <stdio.h>#include <stdlib.h>int main() {    const int max_students = 3;    Student* studenten[max_students];    studenten[0] = student_erstelle(1, "Alice", "Informatik");    studenten[1] = student_erstelle(2, "Bob", "Mathematik");    studenten[2] = student_erstelle(3, "Carlos", "Physik");    student_zeige_alle(studenten, max_students);    int such_matrikelnummer = 2;    Student* gefunden = find_student(studenten, max_students, such_matrikelnummer);    if (gefunden) {        printf("Gefundener Student: Matrikelnummer %d, Name: %s, Studienfach: %s", gefunden->matrikelnummer, gefunden->name, gefunden->studienfach);    } else {        printf("Student mit Matrikelnummer %d nicht gefunden.", such_matrikelnummer);    }    for (int i = 0; i < max_students; i++) {        student_loesche(studenten[i]);    }    return 0;}'

Dieser Testcode:

  • Erstellt drei Studenten.
  • Zeigt alle Studenten an.
  • Sucht nach einem Studenten mit einer bestimmten Matrikelnummer.
  • Gibt die Suche aus und überprüft, ob der Student gefunden wurde.
  • Gibt den für die Studenten allokierten Speicher frei.

Damit ist die Teilaufgabe erfüllt und die Funktion find_student korrekt implementiert und getestet.

c)

Teilaufgabe 3: Implementiere eine Funktion void delete_student(int mat_nr), die den Studenten mit der angegebenen Matrikelnummer aus der Datenbank löscht. Stelle sicher, dass der freigegebene Speicher korrekt verwaltet wird. Ergänze Dein Programm mit einem Beispiel, das zeigt, wie ein Student erfolgreich gelöscht wird.

Lösung:

Um diese Teilaufgabe zu lösen, implementieren wir die Funktion delete_student in der Quellcode-Datei student.c. Dazu benötigen wir auch eine Anpassung der Header-Datei student.h, um die Funktion zu deklarieren. Ebenso fügen wir den entsprechenden Code in die Testfunktion ein.

  • Erweitern der Header-Datei, um die neue Funktion zu deklarieren.
  • Implementieren der Funktion in der Quellcode-Datei.
  • Erstellen eines Beispiels zur Demonstration der Funktionalität.

Hier ist der erweiterte Code für die Header-Datei student.h:

'#ifndef STUDENT_H#define STUDENT_H#include <stdlib.h>#define NAME_LEN 100#define SUBJECT_LEN 100typedef struct {    int matrikelnummer;    char name[NAME_LEN];    char studienfach[SUBJECT_LEN];} Student;Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach);void student_zeige_alle(Student** studenten, int anzahl);void student_loesche(Student* student);Student* find_student(Student** studenten, int anzahl, int mat_nr);void delete_student(Student** studenten, int* anzahl, int mat_nr);#endif'

Hier ist der erweiterte Code für die Quellcode-Datei student.c:

'#include "student.h"#include <stdio.h>#include <string.h>Student* student_erstelle(int matrikelnummer, const char* name, const char* studienfach) {    Student* s = (Student*)malloc(sizeof(Student));    if (s == NULL) {        fprintf(stderr, "Speicher konnte nicht zugewiesen werden");        exit(EXIT_FAILURE);    }    s->matrikelnummer = matrikelnummer;    strncpy(s->name, name, NAME_LEN-1);    s->name[NAME_LEN-1] = '\0';    strncpy(s->studienfach, studienfach, SUBJECT_LEN-1);    s->studienfach[SUBJECT_LEN-1] = '\0';    return s;}void student_zeige_alle(Student** studenten, int anzahl) {    for (int i = 0; i < anzahl; i++) {        Student* s = studenten[i];        printf("Matrikelnummer: %d, Name: %s, Studienfach: %s", s->matrikelnummer, s->name, s->studienfach);    }}void student_loesche(Student* s) {    free(s);}Student* find_student(Student** studenten, int anzahl, int mat_nr) {    for (int i = 0; i < anzahl; i++) {        if (studenten[i]->matrikelnummer == mat_nr) {            return studenten[i];        }    }    return NULL;}void delete_student(Student** studenten, int* anzahl, int mat_nr) {    for (int i = 0; i < *anzahl; i++) {        if (studenten[i]->matrikelnummer == mat_nr) {            student_loesche(studenten[i]);            for (int j = i; j < *anzahl - 1; j++) {                studenten[j] = studenten[j + 1];            }            *anzahl -= 1;            return;        }    }    printf("Student mit Matrikelnummer %d nicht gefunden.", mat_nr);}'

Dieser Code enthält eine neue Funktion delete_student, die einen Studenten anhand der Matrikelnummer aus der Datenbank löscht und den Speicher korrekt verwaltet.

Nun erweitern wir die Testfunktion in der Quellcode-Datei main.c:

'#include "student.h"#include <stdio.h>#include <stdlib.h>int main() {    int anzahl_studenten = 3;    const int max_students = 3;    Student* studenten[max_students];    studenten[0] = student_erstelle(1, "Alice", "Informatik");    studenten[1] = student_erstelle(2, "Bob", "Mathematik");    studenten[2] = student_erstelle(3, "Carlos", "Physik");    printf("Alle Studenten:");    student_zeige_alle(studenten, anzahl_studenten);    printf("Student mit Matrikelnummer 2 suchen:");    Student* gefunden = find_student(studenten, anzahl_studenten, 2);    if (gefunden) {        printf("Gefundener Student: Matrikelnummer %d, Name: %s, Studienfach: %s", gefunden->matrikelnummer, gefunden->name, gefunden->studienfach);    } else {        printf("Student mit Matrikelnummer 2 nicht gefunden.");    }    printf("Student mit Matrikelnummer 2 löschen:");    delete_student(studenten, &anzahl_studenten, 2);    printf("Alle Studenten nach dem Löschen:");    student_zeige_alle(studenten, anzahl_studenten);    for (int i = 0; i < anzahl_studenten; i++) {        student_loesche(studenten[i]);    }    return 0;}'

Dieser Testcode:

  • Erstellt drei Studenten.
  • Zeigt alle Studenten an.
  • Sucht nach einem Studenten mit der Matrikelnummer 2.
  • Gibt das Ergebnis der Suche aus.
  • Löscht den Studenten mit der Matrikelnummer 2.
  • Zeigt erneut alle verbleibenden Studenten an.
  • Gibt den Speicher der verbleibenden Studenten frei.

Damit ist die Teilaufgabe erfüllt und die Funktion delete_student korrekt implementiert und getestet.

d)

Teilaufgabe 4: Verwende gdb und valgrind, um Dein Programm auf Fehler und Speicherlecks zu prüfen. Erkläre, wie Du gdb benutzt hast, um einen Segmentation Fault zu identifizieren, und wie Du valgrind verwendet hast, um sicherzustellen, dass keine Speicherlecks vorhanden sind. Füge auch relevante Ausgaben und Debugging-Informationen hinzu.

Lösung:

Die Werkzeuge gdb und valgrind sind sehr nützlich, um Fehler in C-Programmen aufzuspüren und Speicherlecks zu identifizieren. Folgend beschreibe ich, wie diese Werkzeuge verwendet wurden, um das Studenten-Datenbank-Programm zu debuggen und zu überprüfen.

Verwendung von gdb zur Fehlerdiagnose

Schritte zur Verwendung von gdb:

  • Kompilieren des Programms mit Debug-Informationen:
    'gcc -g -o student_db main.c student.c'
  • Starten von gdb:
    'gdb ./student_db'
  • Setzen eines Breakpoints:
    '(gdb) break main'
    Dies setzt einen Breakpoint am Anfang der main-Funktion.
  • Starten des Programms:
    '(gdb) run'
    Der Debugger startet das Programm und hält an der main-Funktion.
  • Ausführen einzelner Schritte:
    '(gdb) next'
    Dies führt die nächste Zeile im Programm aus.
  • Überprüfen von Variablenwerten:
    '(gdb) print variable_name'
    Dies zeigt den aktuellen Wert einer Variable an.
  • Fortsetzen des Programms:
    '(gdb) continue'
    Dies setzt die Ausführung des Programms fort, bis der nächste Breakpoint oder Fehler erreicht wird.

Beispiel:

'(gdb) runStarting program: /path/to/student_dbBreakpoint 1, main () at main.c:5(gdb) next(gdb) print anzahl_studenten$1 = 3(gdb) continue'

Angenommen, es tritt ein Segmentation Fault auf, zeigt gdb die Zeile im Quellcode, wo der Fehler auftritt. Durch Überprüfen der Variablenwerte und Stapelverfolgungen kann der Fehler identifiziert und behoben werden.

Verwendung von valgrind zur Überprüfung auf Speicherlecks

Schritte zur Verwendung von valgrind:

  • Kompilieren des Programms (wie oben):
    'gcc -g -o student_db main.c student.c'
  • Ausführen mit valgrind:
    'valgrind --leak-check=full ./student_db'
    valgrind führt das Programm aus und überwacht den Speicherverbrauch.

Beispielausgabe:

'==12345== Memcheck, a memory error detector==12345== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.==12345== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info==12345== Command: ./student_db==12345====12345== Invalid read of size 4==12345==    at 0x401F3B: main (main.c:20)==12345==  Address 0x4a3a068 is 0 bytes inside a block of size 8 free'd==12345==    at 0x4C29BC3: free (vg_replace_malloc.c:530)==12345==    by 0x401EAB: delete_student (student.c:36)==12345==    by 0x401F1B: main (main.c:18)==12345==  Block was alloc'd at==12345==    at 0x4C27FFF: malloc (vg_replace_malloc.c:299)==12345==    by 0x401D76: student_erstelle (student.c:10)==12345==    by 0x401E16: main (main.c:8)==12345====12345== HEAP SUMMARY:==12345==     in use at exit: 0 bytes in 0 blocks==12345==   total heap usage: 6 allocs, 6 frees, 1,048 bytes allocated==12345====12345== All heap blocks were freed -- no leaks are possible==12345== For counts of detected and suppressed errors, rerun with: -v==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)'

Die valgrind-Ausgabe zeigt, dass ein ungültiger Lesevorgang einer bereits freigegebenen Speicheradresse stattgefunden hat und stellt sicher, dass alle Speicherblöcke korrekt freigegeben wurden.

Fazit

Die Verwendung von gdb hilft dabei, Segmentation Faults zu lokalisieren und zu beheben, indem man den Speicher und die Variablenwerte im Detail überprüft. valgrind ermöglicht eine umfassende Überprüfung auf Speicherlecks und hilft sicherzustellen, dass alle Speicherressourcen korrekt verwaltet werden. Durch sorgfältige Anwendung dieser Tools können wir sicherstellen, dass das Studenten-Datenbank-Programm robust und speichereffizient ist.

Aufgabe 4)

Du wirst ein einfaches Softwaresystem entwerfen, das verschiedene Rechnungsarten berechnen kann (z.B. Bruttorechnung, Nettorechnung). In diesem Zusammenhang wirst Du Entwurfsmuster und Architekturprinzipien anwenden, um sicherzustellen, dass das System wartbar und erweiterbar bleibt. Betrachte die folgenden Anforderungen und entwickle eine Lösung.

a)

Implementiere das Strategy-Entwurfsmuster, um die unterschiedlichen Rechnungsberechnungen (z.B. Bruttorechnung, Nettorechnung) zur Laufzeit austauschen zu können. Definiere Schnittstellen und konkrete Strategien für die einzelnen Berechnungsarten. Zeige anhand eines Codebeispiels, wie die Berechnung zur Laufzeit gewechselt werden kann. Achte darauf, das KISS-Prinzip anzuwenden.

Lösung:

Um das Strategy-Entwurfsmuster zu implementieren und die verschiedenen Rechnungsberechnungen zur Laufzeit austauschen zu können, folgen wir diesen Schritten:

  • Definiere eine Schnittstelle für die Strategie.
  • Erstelle konkrete Strategien für die verschiedenen Rechnungsarten (z.B. Bruttorechnung, Nettorechnung).
  • Implementiere eine Kontextklasse, die die aktuelle Strategie verwendet und zur Laufzeit die Berechnung durchführt.

Hier ist ein Beispiel in Python:

 from abc import ABC, abstractmethod  # Schritt 1: Definiere eine Schnittstelle für die Strategie  class Rechnungsstrategie(ABC):      @abstractmethod      def berechne(self, betrag):          pass   # Schritt 2: Erstelle konkrete Strategien   class BruttoRechnung(Rechnungsstrategie):      def berechne(self, betrag):          return betrag * 1.19  # Beispiel mit 19% MwSt.   class NettoRechnung(Rechnungsstrategie):      def berechne(self, betrag):          return betrag / 1.19  # Beispiel mit 19% MwSt.   # Schritt 3: Implementiere die Kontextklasse  class Rechnungskontext:      def __init__(self, strategie: Rechnungsstrategie):          self.strategie = strategie       def set_strategie(self, strategie: Rechnungsstrategie):          self.strategie = strategie       def berechne_betrag(self, betrag):          return self.strategie.berechne(betrag)   # Beispiel für die Nutzung  betrag = 100   # Benutze Bruttorechnung  rechnungs_strategie = BruttoRechnung()  rechnung = Rechnungskontext(rechnungs_strategie)  print(f'Bruttorechnung: {rechnung.berechne_betrag(betrag)}')   # Wechsle zu Nettorechnung  rechnungs_strategie = NettoRechnung()  rechnung.set_strategie(rechnungs_strategie)  print(f'Nettorechnung: {rechnung.berechne_betrag(betrag)}')  

In diesem Beispiel haben wir:

  • Eine abstrakte Klasse Rechnungsstrategie erstellt, die die Methode berechne deklariert.
  • Zwei konkrete Klassen BruttoRechnung und NettoRechnung erstellt, die von Rechnungsstrategie erben und die berechne-Methode implementieren.
  • Eine Kontextklasse Rechnungskontext erstellt, die eine Instanz einer Rechnungsstrategie enthält und die Methode berechne_betrag verwendet, um den Betrag zu berechnen.
  • Zur Laufzeit die Strategie gewechselt und den Betrag berechnet.

Dieses einfache Beispiel folgt dem KISS-Prinzip und zeigt, wie man das Strategy-Entwurfsmuster in Python implementieren kann.

b)

Verwende das Singleton-Entwurfsmuster, um eine einzige Instanz der Klasse zu garantieren, die für die Berechnungslogik zuständig ist. Implementiere diese Klasse und erkläre, wie Du sicherstellst, dass nur eine Instanz während der Laufzeit existiert. Achte darauf, das Prinzip der Single Responsibility zu wahren und erkläre, welche Verantwortung die Klasse trägt.

Lösung:

Um das Singleton-Entwurfsmuster zu verwenden und sicherzustellen, dass nur eine einzige Instanz der Klasse für die Berechnungslogik während der Laufzeit existiert, folge diesen Schritten:

  • Implementiere eine Singleton-Klasse für die Berechnungslogik.
  • Stelle sicher, dass die Instanz dieser Klasse nur einmal erstellt wird.
  • Erkläre die Verantwortlichkeit der Klasse gemäß dem Prinzip der Single Responsibility.

Hier ist ein Beispiel in Python:

 class BerechnungsSingleton:      _instance = None       def __new__(cls):          if cls._instance is None:              cls._instance = super(BerechnungsSingleton, cls).__new__(cls)          return cls._instance       def brutto_berechnen(self, betrag):          return betrag * 1.19       def netto_berechnen(self, betrag):          return betrag / 1.19  

In diesem Beispiel haben wir:

  • Die Klasse BerechnungsSingleton erstellt, die eine einzige Instanz während der Laufzeit sicherstellt.
  • Die Methode __new__ überschrieben, um zu prüfen, ob die Instanz bereits existiert; wenn nicht, wird sie erstellt.
  • Zwei Methoden brutto_berechnen und netto_berechnen hinzugefügt, die die Berechnungslogik kapseln.

Single Responsibility Principle: Die Klasse BerechnungsSingleton trägt die Verantwortung, die Berechnungslogik bereitzustellen. Sie ist ausschließlich für die Umsetzung der Berechnungslogik (Bruttoberechnung und Nettoberechnung) zuständig und ihre einzige Aufgabe besteht darin, diese Berechnungen durchzuführen. Andere Aufgaben, wie die Verwaltung von Benutzeroberflächen oder Datenzugriffe, gehören nicht zu ihrem Verantwortungsbereich, was die Einhaltung des Single Responsibility Principle gewährleistet.

Hier ein Beispiel, wie Du diese Singleton-Klasse verwenden kannst:

 betrag = 100   berechner = BerechnungsSingleton()  print(f'Bruttobetrag: {berechner.brutto_berechnen(betrag)}')  print(f'Nettobetrag: {berechner.netto_berechnen(betrag)}')   # Überprüfen, ob dieselbe Instanz verwendet wird  ein_weiterer_berechner = BerechnungsSingleton()  print(berechner is ein_weiterer_berechner)  # Ausgabe: True  

In diesem Beispiel sehen wir, dass jede Instanz der Klasse BerechnungsSingleton die gleiche Instanz ist, was die Einhaltung des Singleton-Entwurfsmusters garantiert.

Sign Up

Melde dich kostenlos an, um Zugriff auf das vollständige Dokument zu erhalten

Mit unserer kostenlosen Lernplattform erhältst du Zugang zu Millionen von Dokumenten, Karteikarten und Unterlagen.

Kostenloses Konto erstellen

Du hast bereits ein Konto? Anmelden