ÜberblickProjektverwaltungGrundlagenEinsprungpunktVariablen und KonstantenSpeicherklassenSichtbarkeitKontrollstrukturenBedingte AnweisungenSchleifenSprungbefehleFunktionenSelbstaufrufeKoroutinenVariadische FunktionenMehrfache FunktionsdefinitionenDatentypenGanzzahlenGleitkommazahlen ZeichenWahrheitstypNichttypZeigerTypumschreibungTypkonvertierungZusammengesetzte DatentypenFelderZeichenkettenAufzählungen: enumVerbünde: structVarianten: unionPräprozessorMakrosKopfdateienModulare ProgrammierungDynamische SpeicherverwaltungSpeicherfunktionenDynamische DatenstrukturenEinfach verkettete ListeEin- und AusgabenKonsoleDateienFehlerbehandlungNebenläufige ProgrammierungAnhangOperatorenSchlüsselwörter und SyntaxLösen von NamenskonfliktenTypische ProgrammierfehlerAnmerkungen
Merkmal | — |
---|---|
Grundparadigma | imperativ: strukturiert, prozedural |
Syntax | Schweifklammern, Semikolons |
Speicherverwaltung | händisch – Systemprogrammiersprache |
Typsystem | schwach, statisch, ausdrücklich, nominal |
Übersetzungsmodell | kompilierend |
Dateiendungen | C: Quelldatei; H: Kopfdatei – en header |
Ersterscheinungsjahr | 1972 |
Erfinder | Dennis Ritchie, Bell Labs |
Vorläufer | Assembler, Algol 68, BCPL > B (Syntax) |
Standardisierungen | ISO: C89 / C90 (aus ANSI C), C95, C99, C11, C18 |
freie Umsetzungen | AOCC, GCC, CLang, TCC, CINT (Interpreter) |
proprietäre Umsetzungen | Borland C++Builder, Intel C++, Visual C++ (MS), Watcom C/C++, XL C (IBM) |
Projektverwalter | GNU Make, CMake, QMake, Qbs, … |
Paketverwalter | Conan (empfohlen), prinzipiell jede Linux-Distribution |
weitere Werkzeuge | ldconfig, pkg-config |
freie IDE | Anjuta, CodeBlocks, CodeLite, Geany, KDevelop |
proprietäre IDE | CLion, Visual Studio (Windows, macOS), Xcode (macOS) |
Data & OOP
GLib • GObject
2D / 3D
Cairo • OpenGL • Vulkan
GUI
GTK • Clutter • GLUT – OpenGL Utility Toolkit • Pango
Multimedia
SDL – Simple DirectMedia Layer
mittels make
xexe = Test
obj = main.o bsp1.o bsp2.o
cc = gcc
flags = -g -Wall -Wextra
$(name): $(obj)
$(cc) $(flags) -o $(exe) $(obj)
%.o: %.c
$(cc) -c $<
run:
./$(exe)
Innerhalb des Projektverzeichnis kann mit make das Programm samt aller Module kompiliert werden:
xxxxxxxxxx
USER@SYSTEM:PATH$ make
…
USER@SYSTEM:PATH$ make run
…
xxxxxxxxxx
int main()
{
printf("Hallo Welt!\n");
return 0;
}
Gemäß dem Standard muss ein erfolgreich beendetes Hauptprogramm 0
zurückliefern, während alle übrigen Ganzzahlen einen Problemfall signalisieren. Jedoch besteht kein Standard zum Deuten der Zahlenwerte.
Alternativ kann auch void
der Rückgabetyp von main
sein, wobei dennoch das Programm mit 0
abgeschlossen wird.
In C++ ist void
als Rückgabetyp nicht erlaubt.
xxxxxxxxxx
// VARIABLEN
extern int a; // Deklaration
int b; // Definition
int c = 9; // Definition und Initialisierung
// KONSTANTEN
// symbolische Konstante; typunsicher!
printf("%f\n", EULER);
const double euler = pow(1. + (1. / 10000000), 10000000); // typsicher
printf("%f\n", euler);
Konstanten sollten mittels const
festgelegt werden, da der Präprozessor nicht typsicher ist, zumal moderne Übersetzer wie der GCC mittlerweile mit const
umzugehen wissen und sinnvoll optimieren.
Bei einer Zuweisung wird der äußerste rechte Ausdruck (Wert) allen Variablen links von diesem zugewiesen. Ist die betreffende Variable bereits mit einem Wert belegt, erfolgt eine Überschreibung der Speicherstelle:
xxxxxxxxxx
int
a = 1,
b = 1;
// Einzelzuweisungen:
a = 2
b = 3
// Gruppenzuweisungen:
a = b = 4;
a = (b = 4); // »b = 4« ist ein Ausdruck mit 4 als Ergebnis!
Die Klammerung ist statthaft, da Zuweisungen in C ungewöhnlicherweise Ausdrücke darstellen!
xxxxxxxxxx
/*
allgemeine Syntax: NAME OP= AUSDRUCK
entspricht: NAME = NAME OP AUSDRUCK
mit OP in {+, -, *, /, %, &, |, ^, <<, >>}
*/
x += 5; // entspricht: x = x + 5
Ebenfalls zu den kombinierten Zuweisungen zählen die Inkrement- und Dekrement-Operatoren, da diese sowohl Ausdruck als auch Anweisung darstellen:
xxxxxxxxxx
int x = 0;
int a = x++;
// a = 0, da erst Rückgabe als Ausdruck, dann Inkrementierung von x
printf("\n x: %i", x);
printf("\n a: %i\n", a);
int b = ++x;
// b = 2, da erst Inkrement, dann Rückgabe von x
printf("\n x: %i", x);
printf("\n b: %i\n", b);
int c = x--;
// c = 2
printf("\n x: %i", x);
printf("\n c: %i\n", c);
int d = --x;
// d = 0
printf("\n x: %i", x);
printf("\n d: %i\n", d);
x++;
// Inkrement nur als Anweisung; entspricht: x = x + 1
printf("\n d: %i\n", d);
--x;
printf("\n a: %i\n", x);
Speicherklassen (en storage classes
) beschreiben Geltungsbereich, Sichtbarkeit und Lebensdauer von Variablen.
xxxxxxxxxx
// Bsp aus: C von A bis Z; 9.8 Statische Variablen
void inkrement(void)
{
static int i = 1; // Speicherklassenangabe erhebt i zur globalen Variable
printf("Wert von i: %d\n",i);
i++;
}
inkrement();
inkrement(); // ohne static: 1
inkrement(); // ohne static: 1
Auf Funktionen sind Speicherklassen nur bedingt anwendbar, da diese ausschließlich global definiert werden.
en static and dynamic scoping
lexikalisch / statisch
→ der umgebende Programmtext bestimmt die Bindung
dynamisch
→ die Ausführungsschicht zur Laufzeit des Programms bestimmt die Bindung.
xxxxxxxxxx
x = BEDINGUNG ? DANN : SONST
xxxxxxxxxx
if(<cond>) { <statements> }
xxxxxxxxxx
if(<cond>) {
<statements>
}
else if(<cond>) {
<statements>
}
else {
<statements>
}
xxxxxxxxxx
switch(<expr:integer>) {
case <expr:integer>: <statements> break;
case <expr:integer>: <statements> break;
…
default: <statements>
}
xxxxxxxxxx
/*
for(<init:number>; <termination:bool>; <update>)
{
<statements>
}
*/
for(int i = 20; i >= 1; i--)
{
printf("%2.i: %3.i\n", i, i*i);
}
xxxxxxxxxx
while(<cond>) {
<statements>
}
xxxxxxxxxx
do {
<statements>
} while(<cond>);
xxxxxxxxxx
// oder 'while(1)'
int i = 0;
loop
{
printf("\n%i", i++);
}
Jede Schleife, deren Bedingung stets wahr bleibt, ist eine Endlosschleife. Manche Sprachen bieten für ewige Wiederholungen einen eigenen Schleifentypen. Unter Ausnutzen des bestehenden Verhaltens kann in C mittels Macro eine neue Syntax für Endlosschleifen eingeführt werden.
xxxxxxxxxx
void bsp()
{
float eingabe;
anfang: // Befehlsabschluss „;“ kann auch unmittelbar erfolgen
printf("\n Bitte eine Zahl größer 0 eingeben:\n> ");
scanf("%f", &eingabe);
if(eingabe <= 0)
{
printf("\n= Ein Fehler ist aufgetreten! Wiederholung erfolgt.\n");
goto anfang;
}
}
Sprünge über Funktionsgrenzen hinweg sind nicht möglich; dafür bestehen die Funktionen setjmp
und longjmp
.
xxxxxxxxxx
char *c1 = "Hallo Welt!";
int c2[] = {3, 1, 4, 1, 5, 9};
int c3len = 4;
double *c3 = calloc(c3len, sizeof(double));
c3[0] = 1.2;
c3[1] = 3.4;
c3[2] = 5.6;
c3[3] = 7.8;
foreach(char, p1, c1, strlen(c1))
printf("1. Schleife: %c\n", *p1);
foreach(int, p2, c2, nemelen(c2))
printf("2. Schleife: %d\n", *p2);
foreach(double, p3, c3, c3len)
printf("3. Schleife: %3.1lf\n", *p3);
Ein als Makro implementierte Mengenschleife, welche jedes Element einer Reihung durchläuft; nachteilig hierbei ist die Beschränktheit auf Felder sowie das Erfordernis der ausdrücklichen Längenangabe, weshalb eine gewöhnliche Schleife nicht minder zweckdienlich erscheint:
xxxxxxxxxx
for(int i = 0; i < strlen(c1); i++)
printf("1. Schleife: %c\n", c1[i]);
for(int i = 0; i < nemelen(c2); i++)
printf("1. Schleife: %i\n", c2[i]);
for(int i = 0; i < c3len; i++)
printf("1. Schleife: %3.1lf\n", c3[i]);
xxxxxxxxxx
/*
<return type> <func name>(<type> <param name>, …)
{
<statements>
}
// Funktionsprototyp:
RÜCKGABETYP FUNKTIONSNAME(TYP PARAMETER-1, TYP PARAMETER-n, …);
// Funktionsdefinition:
RÜCKGABETYP FUNKTIONSNAME(TYP PARAMTER-1, TYP PARAMTER-n, …)
{
…
return RÜCKGABEWERT;
}
*/
double pot(float *bas, long *exp); // Funktionssignatur
double pot(float *bas, long *exp)
// Übergabe von Zeigern statt Kopien von Werten
{
double pot = *bas;
// Zuweisen des Inhalts der referenzierten Speicherstelle
for(int i = 2; i <= *exp; i++)
pot *= *bas;
return pot;
}
Die Umsetzung des Prototyps kann an beliebiger Stelle erfolgen, um eine noch nicht definierte Funktion bereits zu verwenden.
Anweisungen nach return bleiben unausgeführt (Funktionsabbruch).
xxxxxxxxxx
float grundzahl = 2.65;
long hochzahl = 5;
printf("\n%lf\n", pot(&grundzahl, &hochzahl));
umgesetzt mit dem Nichttyp void
xxxxxxxxxx
unsigned long zufallszahl(void)
{
unsigned long x = (unsigned long) &x;
// Typumwandlung: Speicheraddresse zur Zahl
return x % 1000;
// nur die letzten drei Stellen ausgeben
}
printf("\n%lu\n", zufallszahl());
Mit dem Datentyp void
lassen sich Funktionen ohne Übergabe definieren, welche in anderen Sprachen wie Pascal auch Prozeduren
genannt werden:
xxxxxxxxxx
void addiere(int s1, int s2, double *summe)
{
*summe = s1 + s2; // Veränderung äußerer Variable über einen Zeiger
return; // wirkungslos
}
double s = 0;
addiere(23, 42, &s);
printf("\n%lf\n", s);
Rekursive Funktionen verbrauchen aufgrund ihres vielfachen Selbstaufrufes deutlich mehr Speicher, sofern nicht endständig rekursiv (en tail call
), weshalb imperative Lösungen wie Sprungbefehle und Schleifen zu bevorzugen sind. Dennoch kann in seltenen Fällen eine Rekursion die bessere Lösung sein, insbesondere bei stark verzweigten sowie tiefen Verschachtelungen.
xxxxxxxxxx
/*
FUNKTIONSNAME(PARAMETER)
{
…
if(PARAMETER <span>= ! < ></span>= …)
// Abbruchbedingung – Rekursionsanker, andernfalls endlos
{
return …;
}
else
{
return FUNKTIONSNAME(PARAMETER ± 1);
// Selbstaufruf mit Wertabänderung
}
}
*/
long double potenz(float b, unsigned int exp)
{
long double x = 1; // Rückgabe, falls b⁰
if(exp == 0) // Abbruchbedingung
return x;
else
return x = potenz(b, exp - 1) * b; // Selbstaufruf
}
printf("\n%Lf\n", potenz(1.5, 8));
C unterstützt weder Koroutinen noch vergleichbare Konstrukte; jedoch bestehen verschiedene Möglichkeiten, diese nachzustellen. Eine davon nutzt den Umstand aus, dass die einzelnen Fallunterscheidungen nicht auf derselben Ebene stehen müssen. Wird jedoch der Wert in einer statischen Variable vorgehalten, so kann im Unterschied zu echten Koroutinen stets immer nur eine Instanz ablaufen.
xxxxxxxxxx
int fib(void)
{
static int i1 = 0, i2 = 1, stand = 0;
int tmp;
switch(stand)
{
case 0:
while (1)
{
stand = 1;
return i2;
case 1: // gehört noch zu switch(state)
tmp = i2;
i2 += i1;
i1 = tmp;
}
}
}
for(unsigned i = 0; i < 9; ++i)
{
printf("%d\n", fib());
}
printf("\n%d\n", fib());
// Alternative Implementierung mit Sprunganweisungen:
int fib(void)
{
static int i1 = -1, i2 = 1, stand = 0;
int tmp;
switch(stand)
{
case 0: goto ZIEL1;
case 1: goto ZIEL2;
}
ZIEL1: /* Funktionsstart */
while(1)
{
tmp = i2;
i2 += i1;
i1 = tmp;
stand = 1; /* für den sofortigen Rücksprung nach ZIEL2 */
return i2;
ZIEL2:; /* unmitelbar fortfahren nach return */
}
}
Da keine Möglichkeiten zur Funktionsüberladung bestehen, lassen sich nur mittels Ellipse und den über die Bibliothek stdarg
bereitgestellten Makros variadische Funktionen verwirklichen.
xxxxxxxxxx
/*
DATENTYP FUNKTIONSNAME(DATENTYP BEZEICHNER, ...);
// mind ein bekanntes Argument
*/
int max(int count, ...) // Auslassungszeichen, auch Ellipse genannt
{
unsigned int curmax = 0;
va_list argumentlist;
va_start(argumentlist, count);
while(count--)
{
unsigned int arg = va_arg(argumentlist, unsigned int);
if(arg > curmax)
curmax = arg;
}
va_end(argumentlist);
return curmax;
}
printf("%i\n", max(5, 72, 777, 253, 193, 615));
Ein grundlegendes Problem bei diesem Mechanismus stellt die fehlende Information über Anzahl und Typ der übergebenen Argumente dar, sodass händisch Maßnahmen zu implementieren sind, damit die Argumente richtig gedeutet und als tatsächlich übergeben verarbeitet werden. Auch ist zu beachten, dass Übergabewerte bestimmter Datentypen einer automatischen Umwandlung unterliegen:
Funktionen, welche als inline
markiert sind, dürfen in mehreren Übersetzungseinheiten enthalten sein, ohne gegen die One Definition Rule
(ODR) zu verstoßen:
xxxxxxxxxx
inline int f() { return 42; }
int main()
{ return f(); }
xxxxxxxxxx
inline int f() { return 42; }
xxxxxxxxxx
> gcc file1.cpp file2.cpp
en integer
xxxxxxxxxx
// {signed; unsigned} {short; long; long long} int BEZEICHNER;
// 32 Bit:
int min = -2147483648;
int max = 2147483647;
unsigned int min = 0;
unsigned int max = 4294967295;
Alle Ganzahlen sind als signed
voreingestellt, sodass ein ausdrückliches Deklarieren als vorzeichenbehaftet überflüssig ist.
Folgende verkürzende Schreibungen sind erlaubt (Wertebereich bezogen auf 64 bit-Systeme):
Bitmind | Schreibweisen | % | Wertebereich |
---|---|---|---|
8 | unsigned char | c / i1 | [0, 255] |
8 | signed char / char | [–128; 127] | |
16 | unsigned short [int ] | hu | [0; 65.535] |
16 | [signed ] short [int ]2 | h | [–32.768; 32.767] |
16 / 32 | [unsigned ] int | u | [0; 232 – 1] |
16 / 32 | signed int , signed , int | i | [– 231; 231 – 1] |
32 | unsigned long [int ] | lu | [0; 264 – 1] |
32 | [signed ] long [int ]2 | li | [– 263; 263 – 1] |
64 | unsigned long long [int ] | ll | [0; 264 – 1] |
64 | [signed ] long long [int ] | lli | [– 263; 263 – 1] |
integers in octal | o / O | ||
unsigned integers in hexadecimal | x / X |
In der ersten Spalte sind die vom C-Standard festgeschriebenen Mindestgrößen wiedergegeben; der tatsächliche Wertebereich kann jedoch systemabhängig höher ausfallen, bspw 32 Bit statt 16 Bit für int
auf modernen Rechnern.
short
bzw long
sind denkbar.en floating-point number
xxxxxxxxxx
float c = 299792458; // 4 Byte; 6-stellige Genauigkeit
double e = 2.7182818284; // 8 Byte; 15-stellige —
long double pi = 3.14159265359; // 10 Byte; 19-stellige —
Bit | Typ | % | prec | Wertebereich |
---|---|---|---|---|
4 | float | f | 6 | 1.2E-38 3.4E+38 |
8 | double | lf | 15 | 2.3E-308 1.7E+308 |
8 / 10 | long double | Lf | 19 | 3.4E-4932 1.1E+4932 |
4 / 8 | Exponentialdarstellung | e / E |
%.4 |
4 Nachkommastellen |
%0 |
Vornullen |
%- |
linksbündig |
%+ |
stets Anzeige des Vorzeichens |
en character
Ein einzelnes Zeichen wird als Ganzzahl abgespeichert, welche je nach Compiler für eine Kodierung in ASCII oder Unicode steht, wobei speziell für Unicode entsprechende Alternativdatentypen eingeführt wurden.
xxxxxxxxxx
char a = 'a'; // ASCII oder Unicode
wchar_t b = L'b'; // Breitzeichen-Typ
char16_t c = u'c'; // UTF-16
char32_t d = U'd'; // UTF-32
Literal | Typ | % | Byte |
---|---|---|---|
'a' | char | c | 1 |
L'a' | wchar_t | lc | 2 |
Da C lange vor Unicode entstand und somit der Breitzeichentyp erst nachträglich eingeführt wurde, lassen sich die Routinen zum Verarbeiten von Zeichen bzw Zeichenketten vom Typ char
nicht mit dem Breitzeichentyp nutzen, sondern es muss alternativ zu <stdio.h>, <stdlib.h>, <string.h>, <time.h> sowie <ctype.h> auf die Gegenstücke in den Bibliotheken <wchar.h> und <wctype.h> zurückgegriffen werden. Zur Ausgabe von Unicode-Zeichen ist setlocale
voranzustellen:
xxxxxxxxxx
setlocale(LC_ALL, "");
printf("\n%lc", L'Ö');
en Boolean type
In C besteht kein eigenständiger Datentyp für Wahrheitswerte, vielmehr gilt jede Zahl ungleich 0 als wahrer Wert.
xxxxxxxxxx
float x = -3.5;
if(x)
{
printf("Wahrheitswert: %f\n", x);
}
else
{
printf("Falscher Prüfwert!");
}
Auch der mit dem C99-Standard eingeführte Datentyp bool
ist nur eine Umschreibung für 0 / 1.
xxxxxxxxxx
bool b = true; int x = b;
printf("\n%i\n", x);
en void type
Das Schlüsselwort void gilt nicht als echter Datentyp, sondern wird nur verwendet, um auszudrücken, dass eine Wertübergabe unerwünscht ist oder kein spezifischer Datentyp vorliegt (void-Zeiger).
xxxxxxxxxx
// Funktion ohne Rückgabewert:
void addiere(int s1, int s2, double *summe)
{
// Verändern einer außerhalb bestehenden Variable über einen Zeiger:
*summe = s1 + s2;
return; // wirkungslos
}
en pointer
Als Zeiger wird eine an einer Variablen gebundene Speicheradresse bezeichnet. Hinter dieser Adresse können entweder Daten (Variablen) oder auch ganze Anweisungen stehen. Durch Dereferenzierung des Zeigers ist es möglich, auf die Daten oder Funktionen zuzugreifen.
xxxxxxxxxx
DATENTYP *ZEIGERVARIABLE = &ZIEL; // Sternchen '*' gehört zum Datentyp!
// Weitere mögliche Schreibungen:
DATENTYP * ZEIGERVARIABLE = & ZIEL;
DATENTYP* ZEIGERVARIABLE =& ZIEL;
// Bsp aus: C – Programmieren von Anfang an; S 148
long x = 88;
long *zeiger;
Der Datentyp der Zeigervariable muss mit dem des Referenzobjekts übereinstimmen! Auch ist zu beachten, dass das Sternchen (*) sowohl zum Deklarieren eines Zeigers – als Typenbezeichner – als auch zum Zugreifen auf das dahinterliegende Objekt – als Dereferenzierer – dient.
xxxxxxxxxx
zeiger = &x; // Zuordnen einer Speicheradresse
*zeiger = 66; // *zeiger ≙ x
// Dereferenzierung: Neuzuweisung über Zeiger
printf("\nx beträgt %ld;", x);
printf("\nDie Adresse von x ist %p.", &x);
printf("\n\n„*zeiger“ referenziert den Wert %ld;", *zeiger);
printf("\nDie Zeigervariable „zeiger“ speichert die Adresse %p.", zeiger);
xxxxxxxxxx
/*
Komponentenzugriff – Dereferenzierung von Zeigern auf Strukturen:
ZEIGER -> ELEMENT // ≙ (*ZEIGER).ELEMENT
*/
struct person
{
char vname[MAX];
char nname[MAX];
};
void ausgabe(struct person *x)
{
printf("\nVorname: %s", x -> vname); // oder: (*x).vname
printf("Nachname: %s", x -> nname); // oder: (*x).nname
}
struct person *eingabe(void)
{
static struct person *p;
p = malloc(sizeof(struct person));
printf("Vorname: ");
fgets(p -> vname, MAX, stdin);
printf("Nachname: ");
fgets(p -> nname, MAX, stdin);
return p;
}
struct person *mitglied;
mitglied = eingabe();
ausgabe(mitglied);
Da der Punktoperator .
einen höheren Stellenwert gegenüber dem Dereferenzierer *
aufweist, muss der Strukturname eingeklammert werden. Als Vereinfachung dieser umständlichen Schreibweise wurde der sog Strukturoperator ->
geschaffen: (*NAME).ELEMENT ≙ NAME -> WERT
xxxxxxxxxx
typedef «type» «alias»;
xxxxxxxxxx
typedef unsigned short alter;
alter a = 30;
Durch typedef
werden keine neuen Datentypen erzeugt, sondern nur Synonyme (Aliase) für bestehende eingeführt.
en type casting
Die automatische Typumwandlung in C führt häufig zu Fehlern oder unerwünschten Ergebnissen:
xxxxxxxxxx
/*
(ZIELDATENTYP) AUSDRUCK
*/
float x = 2 / 3; // keine eigenständige Konvertierung von int nach float
printf("2 / 3 = %f\n", x);
x = 2. / 3; // mind ein Literal als Gleitkommazahl
printf("2 / 3 = %f", x);
int a = -1;
unsigned b = 1; // für eine richtige Auswertung: ebenfalls signed
if(a < b)
printf("\n\na < b = wahr");
else
printf("\n\na < b = falsch"); // falsche Aussage, a ist sehrwohl kleiner!
// AUSDRÜCKLICHE TYPUMWANDLUNG
float x = (float) 2 / 3; // explizite Lösung zum obigen Problem
printf("\n2 / 3 = %f\n", x);
printf("\nint nach char: %c\n", (char) 69);
printf("\nchar nach float: %f\n", (float) 'R');
printf("\nfloat nach int: %d\n", (int) 2.125);
en array
auch Aufstellung, Matrix / Matrize, (endliche) Reihe / Reihung, Tabelle, Tupel (eindimensionales Feld)
Der Inhalt eines Feldes wird über die Reihenfolge, mit Null beginnend, identifiziert. Bei verschachtelten Feldern entspricht jeder Index eines Elements einer Dimension (Tiefe).
xxxxxxxxxx
// DATENTYP FELDNAME[ELEMENTANZAHL];
FELDNAME[INDEX] = WERT;
// Bsp eines mehrdimensionalen Datenfeldes:
int Matrix[4][5] =
{
{10, 20, 30, 40, 50},
{15, 25, 35, 45, 55},
{20, 30, 40, 50, 60},
{25, 35, 45, 55, 65}
};
Ein Feldname in C ist lediglich ein Zeiger auf das erste Element, sodass dieser wie eine Speicheradresse gehandhabt wird:
xxxxxxxxxx
int element[8]= { 1, 2, 4, 8, 16, 32, 64, 128 };
int *zeiger = element; // oder »&element[0]«
printf("Der Zeiger verweist auf das erste Element mit dem Wert %d.\n", *zeiger);
printf("*(zeiger+1) = %d\n", *(zeiger+1)); // Erhöhen der Adresse um 4 Bytes
printf("*(zeiger+3) = %d\n", *(zeiger+3));
en string
In C werden Zeichenketten als Datenfeld umgesetzt, wobei eine feste Größe (Index) bei Initialisierungen nicht zwinge nd angegeben werden muss. Um das Ende einer Zeichenkette (vorzeitig) zu markieren, bedarf es einer binären Null \0
.
xxxxxxxxxx
// unmittelbar über die Kodierung:
int hallo[] = { 72, 97, 108, 108, 111, 33, 10, 0 };
// als Einzelzeichen:
char hallo[] = {'H', 'a', 'l', 'l', 'o', ' ', '\n', '\0'};
// oder vereinfacht:
char hallo[] = "Hallo Welt!\n";
printf("%s\n", hallo); // %s als Formatangabe für Zeichenketten
Unterschied zwischen *X
und X[]
:
char a[] = "Hallo!" | char *p = "Hallo!" | |
---|---|---|
Objektart | Feld | Zeigervariable |
Größe in Byte | sizeof(a) = 7 (Zeichenkette) | sizeof(p) = 8 (Zeigergröße) |
Adressierung | a und &a verhalten sich gleich! | &p liefert nur Speicheradresse |
Speicherort | Stapel | p selbst auf dem Stapel; Inhalt von p im Programmbereich → nur lesender Zugriff |
Überschreiben | a = "Tach!"; // invalide! a selbst ist eine Adresse! | p = "Tach!"; // valide! |
Zeigerarithmetik | a++; // invalide! | p++; // valide! |
a[0] = 'x'; // valide! | p[0] = 'x'; // invalide! |
en enumerated type
Der Aufzählungstyp enum stellt eine Liste von Bezeichnern für ganzzahlige Konstanten dar, denen fortlaufend – beginnend mit 0 oder einer selbst festgelegten Ganzzahl als Startpunkt – ein Wert um 1 erhöht automatisch zugewiesen wird.
xxxxxxxxxx
enum Wochentage
{
Montag = 1, // ohne Wertzuweisung beginnend mit 0
Dienstag,
Mittwoch,
Donnerstag,
Freitag,
Samstag,
Sonntag
};
printf("Mittwoch ist der %i. Wochentag.\n", Mittwoch);
Mit enum und typedef lässt sich C um einen neuen Datentyp für Wahrheitswerte erweitern:
xxxxxxxxxx
typedef enum {false, true} bool;
bool x = true;
if(x) printf("\nx ist wahr!\n\n");
Dieser ist jedoch nicht typsicher, sondern verhält sich weiterhin wie eine Ganzzahl!
en object composition
, structure
in C oder record
in Pascal
Ein Verbund besteht aus einer oder mehreren Komponenten beliebigen Typs, welche selbst wiederum weitere Komponenten enthalten können.
xxxxxxxxxx
struct phys_gr
{
float a, d; // Atommasse, Dichte
};
struct chem_element
{
char symbol[3];
struct phys_gr stoffdaten;
};
struct chem_element antimon = {"Sb", {121.760, 6.697}};
struct chem_element pse[118]; // erzeugt 119 Strukturen als Datenfeld
strcpy(pse[26].symbol, "Fe");
pse[26].stoffdaten.a = 55.845;
pse[26].stoffdaten.d = 7.874;
printf("\nAtommasse von %s: %.3f u", pse[26].symbol, pse[26].stoffdaten.a);
printf("\nDichte von %s: %.3f g/cm³", antimon.symbol, antimon.stoffdaten.d);
Ein Vgl von Verbünden, bspw in einer Bedingung, ist nur über die einzelnen Komponenten möglich.
Mit typedef
umgeschriebene Strukturen lassen sich wie gewöhnliche Typen zur Deklaration neuer Objekte verwenden:
xxxxxxxxxx
// typedef TYPENDEFINITION BEZEICHNER;
typedef struct
{
char vname[30];
char nname[30];
unsigned short alter;
} person;
person mitglied = {"Hans", "Wurst", 24};
printf("%s %s (%hu)", mitglied.vname, mitglied.nname, mitglied.alter);
en union type
Eine Variante – auch Vereinigung genannt – ist ein Verbund, bei welchem alle Komponenten an der gleichen Speicheradresse beginnen, sodass ihre Speicherbereiche ganz oder zumindest teilweise überlappt sind. Folglich richtet sich das Fassungsvermögen nach der größten Komponente.
xxxxxxxxxx
/*
union TYPBEZEICHNER
{
DATENTYP ELEMENTNAME;
DATENTYP ELEMENTNAME;
…
} VARIANTENNAME;
*/
union anschrift
{
unsigned short postfach;
struct
{
char str[20];
unsigned short nr;
} str_hausnr;
};
union anschrift x;
strcpy(x.str_hausnr.str, "Birkenweg");
x.str_hausnr.nr = 16;
x.postfach = 56864; // teilweises Überschreiben der Struktur
printf("%s %hu", x.str_hausnr.str, x.str_hausnr.nr);
printf("\n%hu", x.postfach);
Zugriff und Wertzuweisung von Unionen ist wie bei Strukturen.
Varianten sind aufgrund ihrer Fehleranfälligkeit sowie erschwerten Portierbarkeit zu meiden.
Makros in C sind lediglich reine Textersetzungen mittels Präprozessor, wodurch keine echte Datenverarbeitung stattfindet. Dementsprechend muss der ersetzende Teil stets geklammert werden, damit die Auswertung wie beabsichtigt erfolgt. Dieser Umstand und die fehlende Typprüfung schränken die Möglichkeiten stark ein, bedingen aber eine schnellere Ausführgeschwindigkeit im Vgl zu Funktionen.
xxxxxxxxxx
/*
#define BEZEICHNER(ÜBERGABE-1, ÜBERGABE-2, …) (AUSDRUCK)
*/
// ohne Klammern: zuerst Multiplikation
int rechnung = ADD(1, 3) * ADD(2, 4);
printf("%d\n", rechnung);
C besitzt nur ein rudimentäres Modulsystem, welches das Einfügen von Quelltexten aus anderen Dateien über den Präprozessor sowie das Kompilieren von ausgelagerten Quelltexten als eigenständige Übersetzungseinheiten gestattet, wobei der Linker die Kompilate zum fertigen Programm zusammenführt. Der Präprozessor dient hierbei vornehmlich zum Einbinden von sog Kopfdateien
(en header files
), welche lediglich Funktionsprototypen und Variablendeklarationen enthalten, damit der Übersetzer bereits vor dem Zusammenführen der Objektdateien die ausgelagerten Funktionen kennt und deren korrekte Anwendung überprüfen kann. Moderne Sprachen bedürfen keiner Kopfdateien, sondern erlauben den unmittelbaren Zugriff auf Module.
Um das Kompilieren zu vereinfachen, indem unter Anderem nur noch geänderte Dateien neu übersetzt werden, bestehen Erstellungswerkzeug wie Make
, CMake
oder Vergleichbares, welche alle Abhängigkeiten auflösen und den Bauvorgang zur fertigen ausführbaren Datei lenken.
xxxxxxxxxx
wchar_t kleinbuchstabe(wchar_t c);
int palin(wchar_t *c);
void test_palin(wchar_t *c);
xxxxxxxxxx
wchar_t kleinbuchstabe(wchar_t c)
// alternative zu to(w)upper für deutsche Schriftzeichen
{
unsigned max = 31;
wchar_t
*vorlage = malloc(max * sizeof(wchar_t)),
*zielvorlage = malloc(max * sizeof(wchar_t));
// Haldenspeicher anfordern
wcscpy(vorlage, L"AÄBCDEFGHIJKLMNOÖPQRSTUÜVWXYZẞ");
wcscpy(zielvorlage, L"aäbcdefghijklmnoöpqrstuüvwxyzß");
for(unsigned i = 0; i <= max; i++)
{
if(c == (vorlage[i]))
{
c = zielvorlage[i];
free(vorlage);
free(zielvorlage); // angeforderten Haldenspeicher freigeben
return c;
}
}
free(vorlage);
free(zielvorlage); // angeforderten Haldenspeicher freigeben
return c;
}
int palin(wchar_t *c)
{
static unsigned
i = 0,
erst = 0,
letzt = 0;
if(i == 0)
letzt = (unsigned) wcslen(c) - 1;
// Erfassen der Zeichenkettenlänge
wchar_t
c_erst = kleinbuchstabe(c[erst]),
c_letzt = kleinbuchstabe(c[letzt]);
// Umwandeln in Kleinbuchstaben
if(erst == letzt || erst > letzt)
{
i = 0, erst = 0, letzt = 0;
// zurücksetzen für nächste Prüfung
return 1;
// wahr, wenn Zeichenkette bis zur Mitte spiegelgleich
}
else if(c_erst != c_letzt)
{
i = 0, erst = 0, letzt = 0;
// zurücksetzen für nächste Prüfung
return 0;
}
else
{
i++;
erst++;
letzt--;
return palin(c);
// Selbstaufruf - Rekursive Abarbeitung
}
}
void test_palin(wchar_t *c)
{
if(palin(c))
{
printf("\n„%ls“ ist ein Palindrom!\n", c);
}
else
{
printf("\n„%ls“ ist KEIN Palindrom!\n", c);
}
}
xxxxxxxxxx
// main.c
// Einfügen eigener Header mit Prototypen
int main()
{
setlocale(LC_ALL, ""); // ermöglicht Unicode in printf
test_palin(L"Lol"); // aufrufen einer Modulfunktion
test_palin(L"HAHA");
test_palin(L"BÜrggrüb");
// weitere Palindrome: Pop, Reittier, Rentner, Staats, tot, …
return 0;
}
xxxxxxxxxx
main: main.o palin.o
gcc -o main main.o palin.o
main.o: main.c
gcc -c main.c
palin.o: palin.c
gcc -c palin.c
Jedes Programm besteht aus vier Bereichen, welche im tatsächlichen bzw virtuellen Arbeitsspeicher – oder auch im Festwertspeicher (zB Bios) – abgelegt sind:
stack)
heap) oder im mmap-Bereich
void *malloc(size_t size); | p = (double) malloc(sizeof(double)); | |
// char[] — char*
Typische Fehler
hängender Zeiger dangling pointer
Unter einer Datenstruktur ist eine abstrakte Beschreibung zu verstehen, wie Daten zu speichern und verwalten sind. Die konkrete Umsetzung mit wohldefiniertem Wertebereich wird hingegen als Datentyp bezeichnet.
C bietet nur Felder als vorgegebene Datenstruktur, dessen Elemente statisch zur Übersetzungszeit auf dem Stapelspeicher abgelegt werden. Das dynamisches Gegenstück bilden verkettete Listen, welche vom Programmierer selbst zu definieren sind.
auch Lineare Listen
xxxxxxxxxx
// Größer / Kleiner
A > B / A < B
// Größer- / Kleinergleich
A >= B / A <= B
// Gleichheit / Ungleichheit
A <span>= !</span>= B
// Logisches Und / Oder:
A <span>&& || !</span> B
// Logisches Nicht (unär):
!A
// Vorzeichen:
+ZAHL / -ZAHL
// Rechenoperatoren, einschließlich Modulo:
A + - * / % B …
// Kürzel bei Neuzuweisungen:
A [+ - * / %]= B; // ≙ A = A <span>+ - * / %</span> B;
// Erhöhung oder Verminderung um 1 – Inkrement und Dekrement:
Z++ // ≙ Z = Z + 1; als Postfix: Inkrementierung nach Gebrauch
++Z // als Präfix: sofortige Inkrementierung
Z-- // ≙ Z = Z - 1; Dekrementierung nach Gebrauch
--Z // sofortige Dekrementierung<!--
--></blockquote>
int i=0;
printf("\n%d\n", i++); // Erhöhung erst nach Gebrauch
printf("\n%d\n", i);
printf("\n%d\n", ++i); // sofortige Erhöhung
// Dereferenzierung:
*ZEIGER
// Komponentenzugriff – Dereferenzierung von Zeigern auf Strukturen:
ZEIGER -> ELEMENT // ≙ (*ZEIGER).ELEMENT
// Punktoperator bei Komponentenzugriff:
VERBUNDNAME.ELEMENT
// Adressoperator:
&ADRESSE
// Speichergröße – in Byte:
sizeof(<span>OBJEKT DATENTYP</span>)
Konstrukte
Verbund (Struktur) | Vereinigung (Variante) | Aufzählung
struct
| union
| enum
Typdefinition
typedef
Konstrollstrukturen
Sprungbefehle
goto
return
break
continue
Bedingung
if
else
Fallunterscheidung
switch
case
default
Schleifen
for
do
while
Datentypen
Nichtwert
void
Zahlen
char
shor
int
long
| signed
unsigned
| double
float
Speicherklassen
const
volatile
auto
extern
register
static
Speicherbehandlung
sizeof
malloc
calloc
realloc
free
xxxxxxxxxx
/*
// Strichpunkt als Anweisungsabchluss:
…;
// Schweifklammern um Anweisungsblöcke:
{
…
}
// Gleichheitszeichen als Zuweisungsoperator:
A = B;
// Rundklammern um Schnittstellen:
FUNKTIONSNAME(…);
// Komma zum Trennen von Ausdrücken innerhalb einer Anweisung:
A, B, …;
// Eckklammern um Kennzeichen (Indexe):
A[INDEX] = …
// Bedingungsoperator:
BEDINGUNG ? WAHRHEITSAUSDRUCK : ALTERNATIVAUSDRUCK
*/
int x = (1, 2, 3); // entspricht int x = 3;
printf("\n%i\n", x);
for(int i = 0, j = 1; i < 10; i++, j--) // mehrere Schleifenvariablen
{
// …
}
Die einfachste Lösung ist das Umbenennen von eigenen Funktionen, sofern möglich.
Falls mehrere benötigte Fremdbibliotheken gleichnamige Bezeichner exportieren, sind in einem neuen Modul (Übersetzungseinheit) die jeweiligen Funktionen einer Bibliothek unter einem neuen Namen öffentlich zu referenzieren.
Umbenennen von Symbolen der Objektdatei mittels des Programms objcopy
xxxxxxxxxx
objcopy --redefine-sym old=new file
Vergleiche
xxxxxxxxxx
int a = 2, b = 2, c = 2
if(a == b == c) {} // 'a == b' = 1
// Lösung
if(a == b && b == c) {}
Streng nach Definition gilt C als höhere Programmiersprache, da ein in Standard-C geschriebenes Programm nicht unmittelbar von einer Maschine ausgeführt werden kann, sondern bereits unabhängig von der drunter liegenden Architektur arbeitet und erst von einem Übersetzer in maschinenabhängige Befehle umgewandelt werden muss. Jedoch ist C im Vergleich zu den übrigen höheren Programmiersprachen stark an den Begebenheiten von Maschinen ausgerichtet und weist überdies keinerlei hochsprachliche Abstraktionen auf, welche über strukturierte sowie prozedurale Konstrukte hinausgehen. Zudem enthalten C-Programme häufig integrierten Assembler, und sind damit nicht mehr völlig plattformunabhängig. Da Chip-Hersteller sogar gezielt auf C neben Assembler zur Steuerung zurückgreifen, muss C aus praktischer Sicht eher als mittelhohe Programmiersprache angesehen werden.
External names are ones that are visible to other compilation units, like non-static functions and variables declared with the "extern" keyword. These names have to be exposed to linkers and loaders. In Ye Olden Days, some linkers and loaders could only handle very short names.
Internal names are ones that are not made visible outside of the module being compiled -- basically anything with "static" scope, or anything local to a function. C compilers have to handle these names, but not anything else
The C Standard sets the minimum number of significant characters that must be supported by all conforming implementations. Some implementations exceed these minimums, but taking advantage of that benefit can make your software hard to port to other implementations.
C11 Standard: same as C99