Einführung in C - Teil 1
Martin Sulzmann
Inhalt
- Motivation
- Hello World
- "Work flow": Editieren/compilieren/ausführen
- Argumentübergabe
- IO printf/scanf
- Einfache Datentypen und Ausdrücke
- Kontrollstrukturen
- Bedingte Anweisung (Kurzschlussauswertung)
- Schleifen (break und continue)
- Komplexe Datenstrukturen
Motivation
Wieso C/C++ (nicht?)
C am Beispiel (quicksort)
#include "stdio.h"
/*
Quicksort.
Idea: Split array (some totally ordered collection of values) into
those that are lesser and greater than a certain element
(commonly referred to as the pivot element).
Quicksort the lesser and greater array.
Concat results.
Example:
[3,2,5,1,4]
pick first element 3 as our pivat element.
lesser = [2,1]
greater = [5,4]
quicksort [2,1] => [1,2]
quicksort [5,4] => [4,5]
Then,
quicksort [3,2,5,1,4]
= (quicksort [2,1]) ++ [3] ++ (quicksort [5,4])
= [1,2] ++ [3] ++ [4,5]
= [1,2,3,4,5]
*/
void display(int x[], int low, int high) {
printf("\n x[] low = %d high =%d \n", low, high);
for(; low <= high; low++) {
printf("%d ", x[low]);
}
}
void swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
}
void quicksort(int x[],int left, int right){
int i, j, pivot, temp;
if(left < right){
pivot=left;
i=left;
j=right;
/*
1. Partition array
(a)
From left and right compare against pivot element.
Adjust i (upper left frontier) and j (lower right frontier).
Swap if frontiers haven't met yet.
(b)
Swap so that pivot element is placed in the middle.
2. Recursively quicksort
(a)
All elements lesser (or equal) than pivot element.
(b)
All elements greater than pivot element.
*/
// (1a)
while(i<j){
while(x[i]<=x[pivot] && i<right) // (I1)
i++;
while(x[j]>x[pivot]) // (I2)
j--;
if(i<j){
swap(&x[i], &x[j]);
}
}
// (1b)
swap(&x[pivot], &x[j]);
// (2a)
quicksort(x,left,j-1);
// (2b)
quicksort(x,j+1,right);
}
}
/*
Further comments.
(I1) The side condition "i<right" ensures that we stay
within valid array bounds.
We might reach the case that i = right.
Access to x[right] is valid, so we can safely evaluate
x[i] <= x[pivot] and then abort the while loop because
condition "i<right" no longer holds.
(I2)
*/
int main() {
int x[] = {3,4,1,2,5};
int l, r, l2, r2;
display(x, 0, 4);
quicksort(x, 0, 4);
display(x, 0, 4);
return 1;
}
Haskell im Vergleich
quicksort [] = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
where
lesser = filter (< p) xs
greater = filter (>= p) xs
Kurz und knackig!
Zusammengefasst
C/C++
Haskell
Wieso dann C/C++ und nicht Haskell (oder ...)?
C ist einfach mit einer überschaubaren Anzahl von elementaren Features.
C ist die DNA 'aller' Programmiersprachen. Z.B. Haskell wird in eine 'sichere' Untermenge von C kompiliert.
C++ erweitert C mit einer Vielzahl von Features. Z.B. Klassen a la Java. Higher-order functions/lambdas a la Haskell.
Einfach wichtig verschiedene Programmiersprachen und deren Features zu kennen.
Hello World in C
#include <stdio.h> // standard i/o lib
// Wird vom Betriebssystem gestartet
int main() {
printf("Hallo Welt! \n"); // vgl. java println()
return 0; // Rueckgabewert Fehlercode/Status
}
Was ist anders im Vergleich zu Java?
include
zum Einbinden von Bibliotheksfunktionen
Jedes C Programm (dies gilt auch für C++) muss eine main
Funktion haben.
Die main
Funktion wird immer am Anfang aufgerufen.
In C gibt es nur Funkionen, keine Methoden und Klassen!
C++ ist eine Erweiterung von C und unterstützt Methoden und Klassen.
Zuerst betrachten wir aber nur C.
Programm Files, Work flow (edit, compile, execute), Versionskontrolle
Programm Files
Programme werden auf verschiedene Files aufgeteilt. In C/C++ gelten folgende Konventionen.
Beachte:
- Dies sind nur Konventionen. Entwicklungswerkzeuge (IDEs) verlangen in der Regel, dass die Konvention eingehalten wird.
Entwicklungswerkzeuge (IDEs)
Entwicklungswerkzeuge (Integrated Development Environment = IDEs) helfen bei der Programmentwicklung. Eine kleine Auswahl.
Visual Studio, Eclipse, ...
Speziell für den Mac gibt es xcode.
Usw.
In der Regel unterstützt eine IDE mehrere Programmiersprachen. Sie können also ihre Lieblings IDE benutzen.
Online C/C++ Compiler
Es gibt eine Reihe von online IDEs. Das reicht oft aus weil wir meistens nur ein File bearbeiten.
Editor und Kommandzeile
Für die old schoolers.
- Program editieren (z.B. emacs, jeder andere Texteditor tut es auch)
- Compilieren und ausführen:
Auf dem mac via Terminal (Kommandozeile).
> gcc helloWorld.c
> ./a.out
Hallo Welt!
Alternative
> gcc -o helloWorld helloWorld.c
> ./helloWorld
Hallo Welt!
Hier die Variante unter Windows via der Kommandozeile.
$ gcc.exe helloWord.c
$ ./a.exe
Hallo Welt!
Alternativ
$ gcc.exe -o helloWord.exe helloWord.c
$ ./helloWorld.exe
Hallo Welt!
Versionskontrolle
Ziel:
Github und Git sind wohl der populärste Host und Tool zur Versionskontrolle.
main Argumentübergabe
In C/C++ kann man dem Executable (kompilierten Programm) auch Argumente (z.B. beim Start von der Kommandozeile) mitgeben. An dem Beispiel unten ist diese Feature kurz erklärt.
#include <stdio.h>
// argc Anzahl der Aufrufargumente bei Programmstart
// argv Array der Argumente
int main(int argc, char* argv[]) {
int i = 0;
while(argc >= 0) {
printf("Argument %d = %s \n", i+1, argv[i]);
i++;
argc--;
}
return 0;
}
Beispiel
$ ./mainArgs.exe hi 5
Argument 1 = ./mainArgs.exe
Argument 2 = hi
Argument 3 = 5
Argument 4 = (null)
IO printf
#include <stdio.h>
int main() {
printf ("Characters: %c %c \n", 'a', 65);
printf ("Decimals: %d %ld\n", 1977, 650000L);
printf ("float: %f \n", 3.1416);
printf ("%s \n", "A string");
}
liefert
Characters: a A
Decimals: 1977 650000
float: 3.141600
A string
Wie geht das?
Betrachte printf ("Characters: %c %c \n", 'a', 65);
.
"Characters: %c %c " ist der Format(ierungs) String.
'a' und 65 sind weitere Argumente.
%c ist ein Platzhalter und sagt, an dieser Stelle wird ein Character erwartet.
Worher kommt das Character? Einfach, siehe weitere Argumente. 65 ist einfach der Ascii-Code von A.
Was alles schief gehen kann
#include <stdio.h>
int main() {
printf ("%c %d %s %f \n", 'a', 3.0, 100, "A string");
}
%c passt auf 'a'
%d passt nicht auf 3.0. Was passiert? Undefiniertes Verhalten. Der Wert 3.0 (float) wird als int interpretiert. Geht schief da Datenverlust.
Was ist mit %s und 100? 100 ist eine Zahl kein String!
Ebenso schief geht %f und "A string".
Wenn wir "schief" sagen, heisst das nicht, dass ein Fehlverhalten immer beobachtbar ist. Z.B. beobachtbar als Absturz/Ausnahme. Es kann gut sein, das Programm wird ausgewertet, aber die Ausgaben sind willkürlich.
IO printf Zusammenfassung
printf ("Decimals: %d %ld\n", 1977, 650000L);
- Erster Parameter: eine Zeichenkette (Literal mit Format-Steuertext)
- Weitere Parameter: Variablen mit Typen gemäß Format-Steuerung
- Platzhalter im Format-Steuertext:
%d Dezimalzahl
%f Kommazahl (float oder double)
- Sonderzeichen im Format-Steuertext:
\n new line, Zeilenvorschub
\t Tabulator
http://www.cplusplus.com/reference/clibrary/cstdio/printf/
IO scanf
#include <stdio.h>
int main () {
char str [80];
int i;
printf ("Geben Sie Ihren Namen ein: ");
scanf ("%s",str); // Referenzuebergabe via Pointer
printf ("Alter: ");
scanf ("%d",&i); // & Operator liefert Pointer auf i
printf ("%s ist %d Jahre alt.\n",str,i);
printf ("Geben Sie ein hexadezimale Zahl ein: ");
scanf ("%x",&i); // Referenzuebergabe via Pointer
printf ("You have entered %#x (%d).\n",i,i);
return 0;
}
Was fällt auf?
scanf behandelt Eingabe, also Rückgabewert? Aber scanf liefert keinen Wert zurück?
Beachte: Der Rückgabewert (z.B. Name als String, Alter als int) wird via einem Referenzparamter übergeben!
Betrachte: scanf ("%d",&i);
Auf der Konsole wird ein String erwartet.
Dieser String wird als int Wert interpretiert.
Der int
Wert soll in der Variablen i
gespeichert werden.
Dazu muss i
ein Referenzparamter sein. In C geschieht dies durch &i
.
Auf technischer Ebene geschieht folgendes.
&i
berechnet die Speicheradresse von i
.
Die Speicheradresse wird an scanf übergeben.
scanf kennt die Speicheradresse und speichert dort den int Wert.
Was ist mit scanf ("%s",str);
? str
ist eine Stringvariable. Strings werden als Zeiger/Pointer dargestellt. Deshalb ist &
nicht notwendig. Mehr zu Zeiger/Pointer kommt noch.
Weitere Info
http://www.cplusplus.com/reference/clibrary/cstdio/scanf/
Einfache Datentypen: Char, Int und Float
#include <stdio.h>
int main () {
// Ganzzahlig
char c; // 1 Byte
int i; // 2-4 Byte je nach Plattform
// Gleitkomma
float f; // 4 Byte
double d; // 8 Byte
// Typoperatoren: short, long, unsigned
short int i16; // Immer 2 Byte
long int i32; // Immer 4 Byte
unsigned long int u32; // Wertebereich 0 - 4294967295
return 0;
}
Typedef
#include <stdio.h>
typedef int km_hour; // typedef alterTyp neuerType;
typedef short int int16;
int main () {
int i=3; // m/second
km_hour j=4;
i = j + i; // Unlogisch km/h und m/secs zu addieren
int16 k=3;
short int l;
l = 2 + k; // typedefs sind nur Abkuerzungen !
return 0;
}
http://en.wikipedia.org/wiki/Typedef
Konstanten
#include <stdio.h>
int main () {
int i;
const int k = 3;
const int j; // zufaellige Konstante
i = 1;
i = 2;
// j = 3; error read-only variable
// k = 3; error read-only variable
printf("i=%d, j=%d, k=%d \n",i,j,k);
return 0;
}
C hat kein Boolean
#include <stdio.h>
int main () {
// C hat kein Boolean aber Integerausdruecke
// haben eine Boolesche Interpretation
int b=1;
if(b != 0)
printf("true");
else
printf("false");
b=0;
if(b) // Integerausdruck ungleich 0 ist true
printf("true");
else // Integerausdruck gleich 0 ist false
printf("false");
return 0;
}
Globale und lokale Variablen
#include <stdio.h>
int i; // globale Variablen
char c;
float f;
int main () {
char i = 'a'; // lokale Variablen im scope
int c = 3; // der main Funktion
float d = 1.1;
f = d + 1.9;
printf("i=%c, c=%d, f=%f \n",i,c,f);
return 0;
}
Arithmetische Ausdrücke
3 + 5 Addition
3 - 5 Subtraktion
3 * 5 Multiplikation
3 / 5 Division
3 % 5 Modulofunktion
-3 Negation
- Operatoren auch mit Zuweisung kombinierbar
+=, -=, *=, /=
3 * 5 - 3
Automatische Typkonvertierungen
#include <stdio.h>
int main () {
// Bei Zuweisung konvertiere vom
// einfacheren zum komplizierteren Typ:
// char -> short int -> long int -> float -> double
float x = 2;
// Umgekehrt kann zu Datenverlust fuehren
int i = -2.3;
printf("-2.3 und %d \n", i);
// -2.3 und -2
return 0;
}
Konvertierung in (Teil)ausdrücken
#include <stdio.h>
int main () {
// Konvertierung in Ausdruecken. Vorsicht!
float f1 = 18 / 10 * 1.5;
float f2 = 18.0 / 10.0 * 1.5;
printf("f1=%f f2=%f \n",f1,f2);
// f1=1.500000 f2=2.700000
// Jeder Teilausdruck wird fuer sich betrachtet, nur so
// praezise wie noetig aufgrund der beteiligten Typen.
// 18 / 10 --> 1
return 0;
}
Explizite Typkonvertierung (Cast)
#include <stdio.h>
int main () {
float x = 340.2;
int i = (int)x; // erzwinge Typkonvertierung
printf("i=%d \n",i);
char c1 = (char)i;
char c2 = (int)x; // implizite automatische Konvertierung
printf("c1=%c c2=%c \n",c1,c2);
float f1 = (float)(18) / (float)(10) * 1.5;
float f2 = 18.0 / 10.0 * 1.5;
printf("f1=%f f2=%f \n",f1,f2);
return 0;
}
Logische Ausdrücke
&& AND if (i>=0 && i<6) { }
|| OR if (i<0 || i>=6) { }
! NOT if (!i) { }
Vergleichsoperatoren sind Boolesche Werte:
<, >, ==, !=, <=, >=
Bit-Operationen
& bitweises AND
| bitweises OR
^ bitweise XOR (exklusiv oder)
~ bitweise Komplement (Negation)
<<n bitweise links um n Positionen schieben
(0 nachschieben)
>>n bitweise rechts um n Positionen schieben
(0 nachschieben)
Bit-Operationen Beispiele
x |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
1 |
y |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
x&y |
0 |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
x|y |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
x |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
1 |
x<<2 |
1 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
Bit-Operationen Anwendungen
#include <stdio.h>
int main () {
unsigned short int x = 8; // 16bit Hardwareregister
int i = 3; // Bitposition
// Pruefen, ob Bit i gesetzt ist:
if (x & (1<<i)) {printf("bit %d in x gesetzt\n",i);};
// Setzen von Bit i ohne Veraenderung der uebrigen Bits:
x = x |(1<<i);
// Ruecksetzen von Bit i ohne Veraenderung:
x = x & ~(1<<i);
// Negieren ("kippen", "toggeln") von Bit i
// ohne Veraenderung:
x = x ^(1<<i);
return 0;
}
Bedingte Anweisungen
#include <stdio.h>
int main () {
if (i<10) { // If Anweisung
printf("i kleiner 10! \n");
} else {
printf("i groesser 9! \n");
}
switch (i) { // Fallunterscheidung
case 1: printf("Fall 1\n");
break;
case 5: printf("Fall 5\n");
break;
default: printf("restliche Faelle\n");
}
return 0;
}
Kurzschlussauswertung ("short-circuit evaluation")
Betrachte folgendes Programmfragment.
if(n !=0 && m / n > 0) { ... }
Die Reihenfolge der Operanden ist wichtig!
Falls n !=0' falsch, wird die folgenden Operanden nicht mehr ausgewertet. Sprich, ein bedingter Ausdruck mit einer Sequenz von mit
&&` verketteten Operanden entspricht einer Programmsequenz von verschachtelten bedingten Ausdrücken.
Obiges Programmfragment entspricht.
if(n !=0) {
if(m / n > 0) { ... }
}
Eine ähnliche "Kurzschlussausertungs Regel" gilt für eine mit ||
verkettete Sequenz von Operanden. In diesem Fall bricht die Auswertung ab (wird kurzgeschlossen), sobald einer der Operanden wahr ist.
Bedingter Ausdruck
cond ? thenExp : elseExp
#include <stdio.h>
int main () {
int a=1,b=2,z;
if (a>b)
z=a;
else z=b;
z = (a > b) ? a : b; // z = max(a, b)
printf("a=%d, b=%d, z=%d \n",a,b,z);
return 0;
}
Schleifen
#include <stdio.h>
int main () {
int i;
for (i=0; i<5; i++) { // For Schleife
printf("Hallo Welt for %d \n",i);
}
i = 0;
while (i<5) { // While Schleife
printf("Hallo Welt while %d \n",i);
i++;
}
i = 0;
do { // Do-While Schleife
printf("Hallo Welt do-while %d \n",i);
i++;
} while (i<5);
return 0;
}
break
und continue
break
: Abbruch und Ausstieg aus der Schleife.
continue
: Abbruch und Fortführung der Schleife.
Betrachte folgendes Beispiel.
#include <stdio.h>
int main() {
int i = 0;
while (1) {
if(i >= 40) {
break;
}
i++;
if(i >= 30) {
continue;
}
printf("\n i = %d", i);
}
printf("\n Ende: i = %d", i);
return 0;
}
Enumerationen
Mengen von endlichen Elementen
enum Wochentag {
Mo=0,
Di=1,
Mi=2,
Do=3,
Fr=4,
Sa=5,
So=6
};
enum Wochentag wt = Mo;
- Abbildung von Bezeichner auf Integer Werte
- Abbildung optional, Compiler verwendet aufsteigende Nummerierung
Wochentag
beschreibt eine Menge von Bezeichnern
wt
enthält eines der Elemente in der Menge
Enumerationen Beispiel
int main () {
enum Wochentag wt = Mo;
switch(wt) {
case Mo: printf("Montag \n");
break;
default: printf("andere Tage \n");
};
}
Enumerationen in C versus C++
enum Wochentag wt = 2;
int i = Do;
- Schwache Typisierung
- Obiges Programm wird ohne Warnung akzeptiert
- Starke Typisierung in C++
enum Wochentag w2 = 5; // ok in C
enum Wochentag w2 = Wochentag(5); // ok in C++
Wochentag wt2 = Wochentag(4); // ok in C++
- Vergleich enums in C und C++
http://www.cprogramming.com/tutorial/c-vs-c++.html
Strukturen (Komplexe Datatypen)
Einfache Datatypen können zu komplexen Datentypen zusammengefasst werden. Zur Beschreibung von komplexen Datentypen gibt es Strukturen ("structs") in C.
Hier ist ein einfaches Beispiel.
#include <stdio.h>
struct position {
int x;
float y;
}; // Semikolon verlangt, anders wie bei Java
int main () {
struct position p = {1,3.0}; // Deklaration mit Initialisierung.
struct position q; // Erst Deklaration
q.y = 3.0; // dann Zuweisung
q.x = 1; // nacheinander.
printf("position (%d,fd) \n",p.x,p.y); // Zugriff.
return 0;
}
Mit Hilfe dem struct
Keyword wird ein (komplexer) Datentyp/Struktur angelegt.
Jede neue deklarierte Struktur hat einen Namen. Hier struct position
.
Die Struktur hat zwei Element. Einen int
und einen float
Wert.
Jedes Element einer Struktur wird mit Hilfe eines eindeutigen Namens identifiziert.
Hier x
für int
und y
für float
.
Aus der Java Perspektive ist eine Struktur nichts anderes wie eine öffentliche Klasse (ohne Konstruktoren und Methoden).
In obigen Beispiel ist p
eine Variable vom Typ struct position
. Wir können den Java Slang verwenden und sagen: "p ist ein Objekt der Struktur position".
Notation struct position p = {1,3.0};
legt Objekt p
an. Auf Grundlage des textuellen Layouts der Definition von position
wird p.x
mit 1
und p.y
mit 3.0
initialisiert.
Der Zugriff auf ein Element eines Objekts ist wie in Java Mit Hilfe der "Dot Notation". Z.B. p.x
greift auf das int Element zu.
Beachte: In C/C++ müssen Strukturen immer mit einem Semikolon abgeschlossen werden.
Arrays
- Sequenz von Elementen vom gleichen Typ
- Array mit 4 Elementen
+===================================================+
| elem1 | elem2 | elem3 | elem4 |
+===================================================+
int iArr[4]
- Array mit 4
int
Elementen
- Zugriff auf Elemente via
iArr[index]
- Erstes Element
iArr[0]
- Letztes Element
iArr[3]
Initialisierung von Arrays
int b[10];
int i;
for(i=0; i<10; i++) b[i] = i;
int a[] = {0,1,2,3,4,5,6,7,8,9};
char s1[] = {'c','o','d','e','\0'};
char s2[] = "code";
- Initialisierung jedes Elements einzeln
- Initialisierung gesamtes Array bei Deklaration (Länge inferriert)
- String = Array von Zeichen
- Das Ende des Strings wir gekennzeichnet durch das Nullterminator Zeichen
Array Zugriff ohne Bereichsüberprüfung
#include <stdio.h>
int main () {
int a[10] = {0,1,2,3,4,5,6,7,8,9}; // Initialisierung
int i;
for(i=0; i<11; i++) {
printf("a[%d]=%d ",i,a[i]);
// Keine Bereichsueberpruefung!
}
printf("\n");
return 0;
}
- Kein (Laufzeit)Check ob in
a[i]
der Index i
innerhalb der Arraygrenzen liegt
Arrays und Strukturen
#include <stdio.h>
struct position {
int x;
float y;
};
int main () {
struct position ps[2] = { {2,4.0}, {1,3.0} };
int i;
for(i=0; i<2; i++) {
printf("ps[%d].x=%d ",i,ps[i].x);
}
return 0;
}
Mehrdimensionale Arrays
#include <stdio.h>
int main () {
int a[3][2] = { {0,1},{2,3},{4,5}}; // Initialisierung
// Oder {0,1,2,3,4,5}
// sprich hintereinander im Speicher
int i,j;
for(i=0; i<3; i++) {
for(j=0; j<2; j++)
printf("a[%d,%d]=%d ",i,j,a[i][j]);
printf("\n");
}
return 0;
}
- a[Reihe][Spalte]
int b[3][4][5]
...
Typbezeichner ("Type Qualifier")
signed, unsigned, long, short, long long, const,
volatile, static, auto, extern, register
Speicherklassen: static
, auto
, extern
, register
Konstante: const
Vorzeichen: signed
, unsigned
Genaugigkeit: long
, short
, long long
volatile
verhindert Optimierung (z.B. Speicherung Variable in Register)
Varianten
union u {
int x;
float y;
};
- Alle Komponenten benutzen die gleiche Speicherzelle.
- Typische Anwendung:
- Wenn Speicher knapp ist (zuerst benutze int danach float)
- Verschieden Interpretation von Bitmustern (Typkonvertierungen)
Zusammenfassung: C Teil 1
- main Programm
- IO (printf, scanf)
- Einfache Datentypen (char, int, float, double), const und typedef
- Arithmetische/Logische Ausdrücke und Bitoperationen
- Überlauf und Genauigkeit
- Typkonvertierungen (automatisch, cast)
- Kontrollstrukturen (Bedingte Anweisungen und Schleifen, bedingter Ausdruck)
- Komplexe Datenstrukturen