Initial commit
This commit is contained in:
parent
04959332f2
commit
835d928e5e
4 changed files with 216 additions and 0 deletions
18
Workbook/.auctex-auto/Workbook.el
Normal file
18
Workbook/.auctex-auto/Workbook.el
Normal file
|
@ -0,0 +1,18 @@
|
|||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
(TeX-add-style-hook
|
||||
"Workbook"
|
||||
(lambda ()
|
||||
(TeX-add-to-alist 'LaTeX-provided-class-options
|
||||
'(("article" "")))
|
||||
(TeX-add-to-alist 'LaTeX-provided-package-options
|
||||
'(("listings" "") ("xcolor" "") ("fontspec" "")))
|
||||
(TeX-run-style-hooks
|
||||
"latex2e"
|
||||
"article"
|
||||
"art10"
|
||||
"listings"
|
||||
"xcolor"
|
||||
"fontspec"))
|
||||
:latex)
|
||||
|
185
Workbook/Workbook.org
Normal file
185
Workbook/Workbook.org
Normal file
|
@ -0,0 +1,185 @@
|
|||
#+title: Workbook
|
||||
#+AUTHOR: Laskar Alexander Theuer/11378947
|
||||
|
||||
* Workbook für das Praktikum Algorithmen und Programmierung 1
|
||||
** Fachfragenkatalog
|
||||
*** Was ist der Unterschied zwischen ~Float~ und ~Double~?
|
||||
~Float~ und ~Double~ werden beide genutzt, um Fließkommazahlen zu speichern. Der Unterschied liegt darin, dass ~Double~, wie der Name schon sagt, doppelte Genauikeit hat. Bei modernen Systemen hat ein ~float~ üblicherweise 32 bit, ein Double ~64~ bit.
|
||||
|
||||
*** Wofür werden ~structs~ verwendet, und welche Bestandteile werden benötigt?
|
||||
~Structs~ werden verwendet, um zusammenhängende Daten an einem Ort zu speichern. Genauer gesagt wird der Code im Computer dadurch in direkt nebeneinander liegenden Teilen des Arbeitsspeichers gespeichert. Structs bestehen immer aus dem Keyword ~struct~, gefolgt von den Namen, einer geöffneten geschweiften Klammer ~{~, Inhalten des struct, getrennt durch Kommata, einer schließenden geschweiften Klammer ~}~, abgeschlossen mit einem Semikolon. Struct selbst können, anders als Klassen, keine Methoden oder Funktionen enthalten, wohl aber function pointers. Hier ein Beispiel:
|
||||
#+begin_src C
|
||||
#include <stdbool.h> // Für den bool typedef, sowie true und false
|
||||
|
||||
struct Person {
|
||||
char* name; // Hier ein char Pointer, um strings darzustellen, da die größe zum Zeitpunkt der Definition noch nicht bekannt ist.
|
||||
int alter; // Ganze Zahl, die das Alter der Person repräsentiert
|
||||
bool lebendig; // Bool, der angibt, ob die Person lebt
|
||||
void (*lebe)(); // pointer zu einer lebe funktion
|
||||
};
|
||||
|
||||
void lebe_Person(struct Person* person) { // Implementation der Lebe Funktion aus dem Person struct.
|
||||
if (!person->lebendig) { // Führe den Code nur aus, wenn die Person nicht lebt
|
||||
person->lebendig = true; // Macht die Person lebendig
|
||||
}
|
||||
}
|
||||
#+end_src
|
||||
Oft wird auch ein ~typedef struct StructName StructName~ verwendet, damit man nicht immer struct davor schreiben muss.
|
||||
|
||||
*** Was ist der Unterschied zwischen Syntax und Semantik?
|
||||
Syntax bezeichnet die korrekte Formulierung von Code auf einer rein Formellen Basis. Sind alle Semikolons da, wo sie sein sollen? Sind alle (geschweiften) Klammern richtig gesetzt? Wurden korrekte Typen angegeben? Diese Dinge werden überprüft während das Programm kompiliert wird. Bei Fehlern wird der Compiler die Kompilation beenden und einen Fehler ausgeben.
|
||||
Semantik hingegen bezeichnet die Logik, die das Programm ausführt. Werden die richtigen Konditionen überprüft? Werden Variablen oder Konstanten die richtigen Werte zugewiesen? Werden die richtigen Funktionen aufgerufen?
|
||||
Beispiel für ein Programm mit einem Syntaxfehlern:
|
||||
#+begin_src C
|
||||
#include <stdio.h>
|
||||
|
||||
void main() { //Deklaration von main als void funktion ist falsch
|
||||
printf("Hello Word!\n") // Fehldes Semikolon
|
||||
return 0; // Void Funktion gibt einen Wert aus.
|
||||
}
|
||||
#+end_src
|
||||
Beispiel für ein Programm mit einem Semantikfehler:
|
||||
#+begin_src C
|
||||
#include <ctype.h> // Inkludiert für isdigit()
|
||||
#include <stdbool.h> // Inkludiert für den Bool typedef
|
||||
#include <stdio.h> // Für printf
|
||||
|
||||
bool is_int(char* str) { // Funktion, die überprüft, ob ein gegebener String eine ganze Zahl ist oder nicht
|
||||
if (str == NULL) {return false;} // Funktion gibt falsch zurück, wenn der gegebene String NULL ist. Sicherheitsmaßnahme um zu verhindern, dass ein NULL pointer dereferenziert wird.
|
||||
while (str != '\0') { // Code wird ausgeführt, bis der NULL Terminator erreicht wird
|
||||
if (!isdigit(str) && !(str == '0')) { // Überprüft, ob der aktuelle char entweder nicht 1-9 oder nicht 0 ist.
|
||||
return true; // Die Funktion gibt hier true zurück, obwohl ein Zeichen außer 0-9 vorkommt.
|
||||
}
|
||||
str++; // Geht zum nächsten Character in str.
|
||||
}
|
||||
return false; // Gibt false zurück, obwohl es eigentlich ein Integer ist.
|
||||
}
|
||||
|
||||
// Zum verdeutlichen:
|
||||
int main() {
|
||||
char[10] num = "0123456789"; // Definitiv eine ganze Zahl
|
||||
if (is_int(&num)) {
|
||||
printf("Es ist eine Zahl.\n");
|
||||
} else {
|
||||
printf("Es ist keine Zahl.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#+end_src
|
||||
Dieser Code würde ~Es ist keine Zahl.~ ausgeben, obwohl es definitiv eine Zahl ist. Die meisten bugs werden durch Semantikfehler verursacht, selten sind diese auch in der Standardbibliothek vorhanden. Durch Semantikfehler entstehen auch größere Probleme, wie z. B. NULL-ptr dereference, free-after-use etc.
|
||||
|
||||
*** Was versteht man unter dem ~fall-through~? Wie ist diese verhalten zu verhindern, wann könnte es nützlich sein?
|
||||
~fall-through~ beschreibt ein Verhalten bei ~switch~ statements, wenn nach einem ~case~ Block kein ~break;~ kommt und dadurch der nächste Fall bis zu einem ~break~ ausgeführt wird. Als Beispiel:
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
int wert = 1;
|
||||
switch (wert) {
|
||||
case 1:
|
||||
printf("Der Wert ist 1.\n"); // Kein break; in diesem Block
|
||||
case 2:
|
||||
printf("Der Wert ist 2.\n");
|
||||
break;
|
||||
case 3:
|
||||
printf("Der Wert ist 3.\n");
|
||||
break;
|
||||
default:
|
||||
printf("Der Wert ist weder 1 noch 2 noch 3.\n"); // Hier braucht es kein break; weil das der letzte Block ist.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#+end_src
|
||||
Hier tritt ein ~fall-through~ auf, weil in dem Block von ~case 1~ kein ~break;~ folgt. Verhindert werden kann dieses Verhalten, indem nach jedem Block ein ~break;~ geschrieben wird, wodurch das ~switch~ statement sofort beendet wird.
|
||||
Ein ~fall-through~ kann aber auch nützlich sein, z. B. wenn man möchte, dass Code ab einem bestimmten Punkt ausgefürt wird. Ein Beispiel:
|
||||
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
|
||||
int berechne_semester_bis_studienende(int bestandene_semester) { // Hier wird die Regelstudienzeit angenommen
|
||||
switch (bestandene_semester) {
|
||||
case 0:
|
||||
bestande_semester++;
|
||||
case 1:
|
||||
bestande_semester++;
|
||||
case 2:
|
||||
bestande_semester++;
|
||||
case 3:
|
||||
bestande_semester++;
|
||||
case 4:
|
||||
bestande_semester++;
|
||||
case 5:
|
||||
bestande_semester++;
|
||||
}
|
||||
return bestande_semester; // Eigentlich könnte man diese ganze Funktion mit 6-bestandene_semester ersetzen
|
||||
}
|
||||
|
||||
int main() {
|
||||
int buffer;
|
||||
printf("Gib ein, wie viele Fachsemester du bereits bestanden hast: ");
|
||||
scanf("%d", &buffer);
|
||||
printf("Dein Studium wäre in Regelstudienzeit in %d Monaten abgeschlossen.\n", berechne_semester_bis_studienende(buffer));
|
||||
return 0;
|
||||
}
|
||||
#+end_src
|
||||
|
||||
Man könnte diesen Code zwar grundsätzlich anders einfacher formulieren, aber das ist ein Beispiel, wo ~switch~ und ~fall-through~ als Einstiegspunkt genutzt werden.
|
||||
Oft sind ~fall-throughs~ unabsichtlich. Ein Entwickler hat ein ~break;~ vergessen, weshalb sie in solchen Fällen /Semantikfehler/ sind.
|
||||
Dies ist aber nicht immer der Fall. ~Fall-throughs~ sind eine durchaus nützliche Programmierpraxis, wenn der geswitchte Wert als Einstiegspunkt verstanden wird und damit in eine Menge von immer gleich bleibende Operationen auch erst an einem späteren Punkt eingestiegen werden kann, zum Beispiel wenn bestimmte Bedingungen bereits erfüllt sind.
|
||||
|
||||
*** Erklären Sie den konditionalen (ternären) Operator Anhand eines Beispieles.
|
||||
Der /ternäre Operator/ folgt der Syntax ~Bedingung ? ausdruck_wenn_war : ausdruck_wenn_falsch;~. Hier ein Beispiel, wo zugewiesen wird, ob jemand Erwachsen oder Minderjährig ist, abhängig vom Alter.
|
||||
#+BEGIN_SRC C
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
int buffer;
|
||||
printf("Gib dein Alter ein. ");
|
||||
scanf("%d", &buffer);
|
||||
char *person = ((buffer < 18) ? "Minderjähriger" : "Erwachsener"); // Klammern helfen bei der Leserlichkeit. String literals sind eigentlich nur const char pointers.
|
||||
printf("Du bist ein %s.\n", person);
|
||||
return 0;
|
||||
}
|
||||
#+END_SRC
|
||||
In diesem Beispiel gibt der Nutzer ein numerisches Alter ein und Mithilfe des ternären Operators wird dem Nutzer entweder Erwachsener oder Minderjähriger zugewisen. Minderjähriger falls Alter < 18 ist, in allen anderen Fällen Erwachsener.
|
||||
|
||||
*** Was ist der Unterschied zwischen Pre- und Postinkrementation und wie werden sie notiert?
|
||||
Beide nutzen den /Inkrementationsoperator/, das ~++~. Bei der Preinkrementation wird Die Variable sofort inkrementiert, und der alte Wert kann nicht noch anderswo zwischengespeichert werden. Pre, also vor, steht dabei für /vor dem zuweisen/. Sie wird mit ~++a~ notiert, wenn a die Variable ist, die inkrementiert werden soll. Bei der Postinkrementation, post steht hier für nach, wird die Variable inkrementiert, *nachdem* der Wert einer anderen Variable zugewiesen wurde, wenn man das machen möchte. Die Notation hierfür ist ~a++~, wenn a die Variable ist, die inkrementiert werden soll. Zur Verdeutlichung kann dieser Code helfen:
|
||||
#+BEGIN_SRC C
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
int a = 5;
|
||||
int b = 5;
|
||||
int c = a++;
|
||||
int d = ++b;
|
||||
printf("A: %d\n", a);
|
||||
printf("B: %d\n", b);
|
||||
printf("C: %d\n", c);
|
||||
printf("D: %d\n", d);
|
||||
return 0;
|
||||
}
|
||||
#+END_SRC
|
||||
Die Ausgabe hiervon ist:
|
||||
#+BEGIN_SRC
|
||||
A: 6
|
||||
B: 6
|
||||
C: 5
|
||||
D: 6
|
||||
#+END_SRC
|
||||
Daran kann man das Verhalten gut erkennen.
|
||||
|
||||
*** Was ist der Unterschied zwischen einer ~while~ und einer ~do-while~ Schleife?
|
||||
Beide Schleifen führen Bedingungen aus, solange eine bestimmte Bedingung erfüllt ist. Der große Unterschied dabei ist, dass bei der while-Schleife, der Code unter Umständen gar nicht ausgeführt wird, wenn die Eingangsbedingung nicht wahr ist. Bei der do-while Schleife wird der Code mindestens einmal ausgeführt. Hier beide Schleifen in einem Code Beispiel:
|
||||
#+BEGIN_SRC C
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
while (false) { // Wird niemals ausgeführt.
|
||||
printf("Ausgabe aus der while-Schleife.\n");
|
||||
}
|
||||
|
||||
do { // Wird ausgeführt
|
||||
printf("Ausgabe aus der do-while Schleife.\n");
|
||||
} while (false); // Ab hier wird nicht mehr ausgeführt
|
||||
return 0;
|
||||
}
|
||||
#+END_SRC
|
BIN
Workbook/a.out
Executable file
BIN
Workbook/a.out
Executable file
Binary file not shown.
13
Workbook/test.c
Normal file
13
Workbook/test.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
int a = 5;
|
||||
int b = 5;
|
||||
int c = a++;
|
||||
int d = ++b;
|
||||
printf("A: %d\n", a);
|
||||
printf("B: %d\n", b);
|
||||
printf("C: %d\n", c);
|
||||
printf("D: %d\n", d);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue