Aufgabe 1
a)
- deklarieren Sie zuerst eventuelle benutzerdefinierte Typen:
struct haus {
int zimmer;
char* art;
char gebaeude;
};
typedef struct haus haus;
- definieren Sie dann die fünf Variablen ohne Initialisierung:
int a;
int* b;
double c[2];
haus d;
haus* e;
- weisen Sie zum Schluss allen Variablen die in der Grafik gezeigten Werte zu:
#include <stdlib>
...
...
...
a = 1;
b = &a;
c[0] = 2.3;
c[1] = 4.5;
//d = {109, "Hoersaal", 'C'};
d.zimmer = 109;
d.art = "Hoersaal";
d.gebaeude = 'C';
e = (haus*) malloc(sizeof(haus));
e->zimmer = 124;
e->art = "Buero";
e->gebaeude = 'F';
Unterschied zwischen stack und heap
C Language Data Type Models: LP64 and ILP32
C Type | ILP32 | LP64 |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
pointer | 4 | 8 |
b)
Ohne Ausrichtung/Padding
ILP32:
Zimmer: (int) 4 Bytes
Art: (pointer) 4 Bytes
Gebaeude: (char) 1 byte
Also: Bytes
LP64:
Zimmer: (int) 4 Bytes
Art: (pointer) 8 Bytes
Gebaeude: (char) 1 Byte
Also: Bytes
Mit Ausrichtung/Padding
ILP32:
Zimmer: (int) 4 Bytes
Art: (pointer) 4 Bytes
Gebaeude: (char) 1 Byte
Padding: 3 Bytes
Also: Bytes
LP64:
Zimmer: (int) 4 Bytes
Padding nach Zimmer: 4 Bytes
Art: (pointer) 8 Bytes
Gebaeude: (char) 1 Byte
Padding nach Gebaeude: 7 Bytes
Also: Bytes
Aufgabe 2
Eingabeparameter | Ausgabeparameter | |
---|---|---|
Erklärung | Eingabeparameter sind Werte, die einer Funktion beim Aufruf übergeben werden. | Ausgabeparameter sind Parameter, die verwendet werden, um Ergebnisse oder Ausgaben aus einer Funktion herauszugeben. Dies geschieht typischerweise durch die Übergabe von Zeigern oder Referenzen, die von der Funktion modifiziert werden können. |
Beispiel | int add(int zahl){ return zahl + zahl; } | void add(int a, int b, int* res) { *res = a + b;} |
Aufgabe 3
a)
Wichtig
- In header ifndef, define und endif nicht vergessen
- In header files nur öffentliche Funktionen und Variablen.
- Funktionennamen mit Klassennamen definieren
- Eingabeparameter immer
const
- In Quelldatei (
.c
) include header nicht vergessen- In Quelldateien befinden sich private Funktionen
static
heißt privat, also static in c ≠ static in java
#ifndef QUADRAT_H
#define QUADRAT_H
double quadrat_flaeche(const double);
#endif
#include "quadrat.h"
static double zumquadrat(const double d)
{
return d * d;
}
double quadrat_flaeche(const double seitenlaenge)
{
return zumquadrat(seitenlaenge);
}
#ifndef WUERFEL_H
#define WUERFEL_H
double wuerfel_oberflaeche(const double);
double wuerfel_volumen(const double);
#endif
#include "wuerfel.h"
#include "quadrat.h"
double wuerfel_oberflaeche(const double kantenlaenge)
{
return quadrat_flaeche(kantenlaenge) * 6;
}
double wuerfel_volumen(const double kantenlaenge)
{
return quadrat_flaeche(kantenlaenge) * kantenlaenge;
}
b)
#include <stdio.h>
#include "wuerfel.h"
int main(int argc, char *argv[])
{
double k;
if (sscanf(argv[1], "%lf", &k) == 0)
{
printf("falsche eingabe!");
return 1;
}
double f = wuerfel_oberflaeche(k);
double v = wuerfel_volumen(k);
printf("kantenlaenge %f, Oberflaeche %f, Volumen %f\n", k, f, v);
return 0;
}
c)
Das C-Programm greift auf argv[1]
zu, um das Argument zu lesen. Wenn das Programm jedoch ohne Argumente gestartet wird, ist argc
gleich 1 und argv[1]
existiert nicht. Der Zugriff auf argv[1]
führt zu einem nicht definierten Verhalten, das in der Regel einen Absturz (Segmentation Fault) des Programms zur Folge hat.
Aufagabe 4
#include <string.h> // strcpy, strcat
#include <stdlib.h> // malloc, free
int main(int argc, char *argv[])
{
const char* s = argv[1];
const char* t = argv[2];
char* st = (char*) malloc(strlen(s) + strlen(t) + 1);
strcpy(st, s);
strcat(st, t);
free(st);
return 0;
}
Aufgabe 5
a)
~fuzzy() = default; // destructor
fuzzy(const fuzzy&) = default; // copy constructor
fuzzy(fuzzy&&) = default; // move constructor
fuzzy& operator=(const fuzzy&) = default; // copy assignment operator
fuzzy& operator=(fuzzy&&) = default; // move assignment operator
Grund für = default
:
- Da
truth
ein einfacher Datentyp (double
) ist, funktioniert die Standardkopie und Verschieben korrekt. - Da
truth
keine komplexen Ressourcen wie dynamischen Speicher oder Datei-Handles enthält, ist der Standard-Destruktor ausreichend.
b)
int main ()
{
const fuzzy eher_ja{ 0.8 };
const fuzzy eher nein = !eher ja;
fuzzy f;
f = eher ja || false;
}
- Zeile 3:
explicit fuzzy(double);
~
- Zeile 4:
fuzzy operator!();
~fuzzy(fuzzy&&) = default;
~
- Zeile 5:
fuzzy();
~
- Zeile 6:
fuzzy(bool);
~friend fuzzy operator||(const fuzzy&, const fuzzy&);
~fuzzy& operator=(fuzzy&&) = default;
- Zeile 7:
~fuzzy()
für f, eher_ja, eher_nein, false, und beide von move Konstruktor erstellten Objekte. Also 6 Destrokturen.
c)
friend std::ostream& operator<<(std::ostream& os, const fuzzy& f)
{
os << f.truth;
return os;
}
d)
#include <array>
std::array<fuzzy, 4> a;
In modernem C++ sollte man stattdessen std::array
oder std::vector
verwenden. Der Grund dafür ist, dass diese Container mehr Funktionalität und Sicherheit bieten als rohe Arrays. Sie verwalten ihre eigene Speicherverwaltung und bieten Methoden zur Größenabfrage, Bounds-Checking und mehr
Aufagbe 6
Makefile cheatsheet
PDFLATEX = pdflatex
DATEI = HELLO
RM = rm -f
%.pdf: %.tex
$(PDFLATEX) $<
.PHONY: all clean
all: $(DATEI).pdf
clean:
$(RM) $(DATEI).pdf $(DATEI).aux $(DATEI).log
Aufgabe 7
POSIX-Funktionen melden Fehler durch Rückgabewerte und die globale Variable errno
. Es ist wichtig, nach jedem kritischen Funktionsaufruf die Rückgabewerte zu überprüfen und bei Fehlern errno
auszuwerten, um den genauen Fehler zu bestimmen und geeignete Maßnahmen zu ergreifen.
Wenn eine POSIX-Funktion einen Fehler meldet, wird die globale Variable errno
auf einen Fehlercode gesetzt, der den spezifischen Fehler beschreibt.
errno
muss direkt nach dem fehlerhaften Funktionsaufruf überprüft werden, da nachfolgende Funktionsaufrufe errno
überschreiben können.