Le langage C Centrale Réseaux Mardi 15 décembre 2009
Sommaire
C quoi? Le C est un langage de programmation créé en 1974, pour écrire le système d exploitation UNIX. Depuis,il est devenu (avec ses variantes), le langage le plus utilisé. Le C a l avantage d être : léger concis bas niveau portable haut niveau Le C est un langage compilé, ce qui lui permet d être très performant.
Compiler un programme Plus facile à utiliser sur un UNIX (Linux, MacOSX) : On écrit son programme avec son éditeur préféré La compilation se fait en ligne de commande avec gcc 1 : gcc monfichier.c -o monprog Sous Windows, il faut installer un IDE comme Visual Studio ou Code : :Blocks. 1. GNU C Compiler/ GNU Compiler Collection
Au programme :
Notre premier programme en C 1 #include <stdio.h> 2 int main(int argc, char** argv) { 3 printf("hello, World!\\n"); 4 return 0; 5 } Les instructions se terminent par un point-virgule Les blocs d instructions sont encadrés par des accolades Les commentaires sont encadrés par /* et */ Les commentaires courts commencent par //
Notre premier programme en C 1 #include <stdio.h> 2 int main(int argc, char** argv) { 3 /* Voici un commentaire sur plusieurs lignes 4 qui se continue ici : notre prermier programme en C */ 5 printf("hello, World!\n"); // On affiche du texte 6 return 0; 7 } Compilez et exécutez le programme hello.c ; c est trop ouf, hein!
Les variables Les variables en C sont typées et il faut les déclarer au compilateur. Il existe plusieurs types de base : Caractère : char Nombre entier : int (integer) Nombre décimal : float (floating-point) Nombre décimal en double précision : double 1 /* Déclarations */ 2 char c; 3 int i; 4 float f; 5 /* Affectation */ 6 c= c ; 7 i=42; 8 f=3.4159; La déclaration permet au programme de réserver de la pour y stocker la variable.
Les types On peut varier les types grâce à des mots-clefs : signed/unsigned, short/long et double type signification octets Plage de valeurs (signed)char caractère 1-128 à 127 unsigned char caractère non signé 1 0 à 255 short int entier court 2-32 768 à 32 767 unsigned short int entier court non signé 2 0 à 65 535 int entier 4-2 147 48 3648 à 2 147 483 647 unsigned int entier non signé 4 0 à 4 294 967 295 long int entier long 4-2 147 48 3648 à 2 147 483 645 unsigned long int entier long non signé 4 0 à 4 294 967 295 float nombre à virgule flottante 4 3.4 10 38 à 3.4 10 38 double nombre en double précision 8 3.4 10 138 à 3.4 10 138 long long int entier long étendu 8-9 223 372 036 854 775 807 à 9 223 372 036 854 775 807 unsigned long long int entier long étendu 8 0 à 18 446 744 073 709 551 615 long double nombre en double 10 3.4 10 4932 3.4 10 4932 précision étendu Généralement, signed est implicite : un int est en fait un signed int. Généralement, un int fait 4 octets ; parfois (sur certaines architectures) seulement 2. On peut omettre int et écrire directement short, long ou long long
Utilisons 1 /* On peut déclarer plusieurs variables d un coup */ 2 int i, j, k, n; 3 /* Déclaration + Affectation = Initialisation*/ 4 char c= c ; 5 float f, g=0.001,h; 6 /*Les opérations arithmétiques */ 7 i = 3 ; j = 4 ; 8 k = (i + (j * (i - n))) ;// attention aux priorités 9 k = i / j ; // = 0 (division entière) 0 /*Les affectations combinées */ 1 k += i ; // équivaut à k = k+i 2 k *= j ; // équivaut à k = k*i 3 k++; // équivaut à k+=1;
Précautions à prendre Une variable non initialisée a une valeur aléatoire! 1 int i = 3, j = 4 ; 2 float f = 0.42, g = 1.2 ; 3 g = i + f ;// conversion implicite 4 g = (float) i / (float) j ; // conversion explicite 5 /* on parle aussi de cast ou de transtypage */ Attention à la priorité des opérateurs et à leur ordre d évaluation. Parfois le résultat n est pas défini (exemple : i++ = i++;)
Utiliser printf Printf permet d afficher du texte à l écran. 1 #include <stdio.h> // nécéssaire pour utiliser printf 2 int main (int argc, char** argv ) { 3 int i = 0, j = 3; 4 printf("j vaut %d et i vaut %d\n",j,i) 5 } On utilise %* pour afficher une variable %d pour afficher sous la forme d un entier %f pour addicher sous la forme d un flottant %c pour afficher sous la forme d un caractère
Lire et écrire On peut récupérer un caractère de l entrée avec getchar() 1 #include <stdio.h> 2 /* ce programme affiche à l écran ce qu on frappe au clavier */ 3 int main (int argc, char** argv ) { 4 char c; 5 c = getchar(); 6 while (c!= EOF) { 7 printf("%c",c); 8 c = getchar(); 9 } 0 }
Conditions et valeur d une expression Les instructions sont évaluées, c est à dire qu elles ont une valeur. 1 int a,b; 2 a=(b=3); Toute valeur nulle est fausse. Les autres valeurs sont vraies. Pour spécifier des conditions on utilise : Les opérateurs de comparaison sont ==,!=, <,>,<=,<= Les opérateurs logiques : &&,,! On peut combiner valeurs de retour et opérateurs logiques : 1 int a=1,b=3; 2 a = (((b == 3) (a!= 1)) &&!(( b = 42) > 12)); 3 /* a vaut maintenant 0 et b 42 */
Les instructions if / else : accolades (obligatoire) et indentation (c est plus lisible) Les structure de choix : if, if/else ou if/else if/.../else. 1 if (i==3 && j < 3) { 2 // instructions 3 } else if (isnormal(k)) { 4 // instructions 5 } else { 6 // instructions 7 }
La boucle for 1 int i ; 2 for (i = 0 ; i < 100 ; i++) { 3 // instructions 4 } Les 3 instructions sont arbitraires : La première est effectuée une seule fois, La 2eme est effectuée au début de chaque itération. Si sa valeur est nulle la boucle s arrête La 3eme est effectuée au début de chaque itération.
La boucle while Les boucles while et for sont interchangeables (en théorie). Le mot-clef break permet de quitter la boucle. 1 int i ; 2 while (i!= 0) { 3 // instructions 4 if (erreur) { 5 break; 6 } 7 } 1 #include <stdio.h> 2 int main(int argc, char** argv) { 3 char c; 4 while ((c=getchar())!=eof) { 5 printf("%c",c); 6 } 7 }
Fonctions et variables 1 #include <stdio.h> 2 int f(int x) { 3 int b=42; 4 return x+42; 5 } 6 7 int main (int argc char** argv) { 8 int a=3; 9 int b; 0 b=f(a); 1 printf("%d",b) 2 } Les variables sont locales à un bloc et n existent pas à l extérieur.
Prototypes et fonctions Comme, les fonctions sont déclarées avec le type de leur valeur de retour et leurs arguments (prototype) Le type void est utilisé pour les fonctions sans paramètre ou qui ne retournent rien. 1 int fonction1(int param1, float param2); 2 float fonction2(double, unsigned char); 3 void fonction3 (int); 4 char fonction4(void); On ne peut pas déclarer une fonction dans une autre fonction
Les fonctions : quelques détails Les fonctions peuvent être récursives 1 int factorielle(int n) { 2 if (n == 1 n == 0) { 3 return 1; 4 } else { 5 return n * factorielle(n-1); 6 } 7 } Les arguments peuvent subir une conversion implicite Une erreur de compilation se produit si un cast est requis 1 double carre(double l){ 2 return l*l 3 } 4 //... plus tard, dans le main... 5 int a = 3; 6 carre(a); // a est converti en double
Un tableau est une suite de variables de même type. 1 int a[10]; /* crée un tableau de 10 entiers 2 on y accède par a[0], a[1]... a [9] */ 3 a[4] = 42 ; 4 int b[4] = {12, 3, 4, 17} ; // initialise le tableau Une chaîne est en fait un tableau de char! Mais attention au \0 final! 1 char s[5]; // crée un tableau de 5 char 2 s = "plop"; 3 // s contient p, l, o, p, \0 4 s = "meh"; 5 // s contient m, e, h, \0, une valeur inconnue
Au programme :
Qu est ce que la La est un grand tableau numéroté. Chaque case fait 1 octet.... 2453 2454 2455 2456 2457... Déclarer une variable lui réserve une place en 1 unsigned int a; // les blocs 2454 à 2457 sont réservés pour a 2 a = 1027 ; Maintenant la ressemble à ça : 00000000 00000000 00000010 0000011... 2454 2455 2456 2457...
Qu est ce qu un pointeur? L opérateur & donne l adresse d une variable 1 printf("%d",&a) ; // affiche l adresse de a Un pointeur est une variable qui contient l adresse d une autre variable. 1 p = &a ; // p pointe sur a L opérateur inverse est * (déréférencement) *p peut être utilisé à la place de a n importe où 1 a = 3 ; p = &a ; 2 *p += 40 ;// a vaut maintenant 42 3 b = f(*p) ;// 42 est passé en argument de f
Un peu plus près des * Les pointeurs doivent être déclarés en fonction du type de la variable sur laquelle ils pointent 1 int a ; double b ; unsigned char c ; 2 int * pa; // un pointeur sur int 3 double * pb = &b; //initialisation 4 unsigned char * pc = NULL ; // pointeur nul Pointeur non initialisé = DANGER! 1 //attention aux déclarations multiples 2 int *p,*q ;// p et q sont des int* ;
Exemple : le passage par référence Ce que je veux : une fonction swap(a,b) qui échange a et b. 1 void swap(int * pa,int * pb) { 2 int c = *pa; 3 *pa = *pb; 4 *pb = c; 5 } 6 //... plus tard... 7 int a = 12, b = 42 ; 8 swap(&a,&b); 9 /* maintenant a vaut 42 et b 12 */ Meilleures performances quand on passe de grosses donées
Les pointeurs et les tableaux Les pointeurs et les tableaux sont très liés! Un exemple : 1 short[10] a ; 2 short * p = &(a[0]) ; 3 int i; 4 for (i = 0 ; i<10 ; i++) { 5 a[i] = 3*i ; 6 printf("%d",*(p+i)); 7 // *(p+i) et a[i] désignent le même élément 8 } Dans la : a[0] a[0] a[1] a[1] a[2] a[2]... 2453 2454 2455 2456 2457... p (p+1) (p+2)
Application : les chaînes de caractères Une chaîne de caractère peut être représentée par un char* Un exemple : 1 char* str = "Une chaine de caracteres"; 2 void stringcopy(char * src, char * dest); 3 while( (*dest = *src)!= \0 ) { 4 dest++; 5 src++; 6 }
Les pointeurs génériques On utilise le type void* pour représenter un pointeur générique. Il est compté comme un pointeur sur un objet de 1 octet pour l arithmétique Il faut le caster pour s en servir Son usage : à limiter
Attention aux pointeurs! On ne peut pas lire ou écrire n importe où dans la! Dans le meilleur des cas : segmentation fault Dans le pire des cas : données corrompues Attention aux casts foireux.
Les structures Une structure est un rassemblement de plusieurs variables Les variables peuvent être de type différents 1 // déclaration de la structure 2 struct Pays { 3 char* nom; 4 long nbhabitants; 5 double superficie 6 } 7 // déclaration d une instance de la structure 8 struct Pays monpays; 9 // initialisation; 0 monpays = {"France",60000000,675417} 1 //accès à une valeur 2 printf(monpays.nom); 3 printf(" : un pays de %d habitants",monpays.nbhabitants);
Les pointeurs de structure 1 void naissance(struct Pays * unpays){ 2 // attention aux parentheses 3 (*unppays).nbhabitants++; 4 } On peut écrire a->b au lieu de (*a).b On peut faire un typedef pour clarifier les choses 1 // on définit un nouveau type Pays 2 typedef struct pays Pays; 3 //accès à une valeur 4 Pays monpays = {"Italie",60000000,300000}; 5 6 void naissance(pays * unpays){ 7 pays->nbhabitants++; 8 } 9 // on peut aussi faire un tableau de structures 0 pays Monde[194]; 1 //... et utiliser l arithmétique des pointeurs 2 pays * p = Monde[0]; 3 for(i=0 ; i <194 ; i++) { 4 printf(p->nom); 5 p++; 6 }
Les unions Une union est une variable qui peut prendre des valeurs de types différents Bien sûr pas en même temps! La syntaxe ressemble à celle des structures 1 // on définit l union Nombre et un alias de type 2 typedef union { 3 int vali; 4 float valf; 5 } Nombre ; 6 7 nombre ajouter(nombre a, Nombre b, char type) { 8 nombre result; 9 if (type == i ) { 0 result.vali = a.vali + b.vali ; 1 return result ; 2 } else if ( type == f ) { 3 result.valf = a.valf + b + valf; 4 return result; 5 } else { 6 printf("erreur!\n"); 7 return 0 8 } 9 }
Un exemple On veut gérer une base de données de personnes Une première approche : 1 typedef struct Personne { //... 2 } Personne ; 3 Personne mabase[10000]; 4 int taille = 0; 5 //... 6 void add(personne[] base, Personne * unepersonne,int * taille) { 7 if (*taille < 10000) { 8 base[*taille] = *unepersonne; 9 *taille++; 0 } 1 } Ce n est pas très pratique... On consomme beaucoup de pour rien
Malloc entre en jeu : l allocation dynamique On demande autant de qu on en a besoin avec On n oublie pas de la libérer quand on a plus besoin avec free 1 #include <stdlib.h> 2 typedef struct Personne { //... 3 Personne * next ; // on va créer une liste chaînée 4 } Personne ; 5 Personne** maliste = NULL ; 6 7 int add(personne** liste, Personne unepersonne); 8 Personne *p = (Personne *) (sizeof(personne)); 9 // on demande de la pour une personne 0 if (p == NULL) { // Il faut toujours faire ça 1 printf("ooops!"); 2 exit(0) ; 3 } else { 4 p->next = *liste ; 5 *liste = p ; 6 }
Les Les fonctions ne sont pas des variables, mais on peut quand même créer des pointeurs de fonction. Attention à la priorité entre * et () ; 1 void tri1(int* uneliste) { 2 //... 3 } 4 5 void tri2(int* uneliste) { 6 //... 7 } 8 9 void triliste(int* uneliste, void ( *fonction () ) ) { 0 (*fonction)(uneliste) 1 } 2 //... dans le main 3 if( typetri == 1){ 4 triliste(maliste, &tri1) 5 } else { 6 triliste(maliste, &tri2) 7 }
Au programme :
La portée La portée (scope) d une variable est l endroit du code où elles sont définies. Par défaut, sont locales à leur bloc. 1 int f(int a,int b) { 2 int c = a * b; 3 // la portée de c va jusqu à la fin de la fonction 4 } 5 6 int main (int argc, char** argv) { 7 double c ; // ce n est pas le même c! 8 int i ; 9 for (i = 0 ; i<=99 ; i++) { 0 char t; 1 // dans ce bloc on peut utiliser c, i et t 2 } 3 // ici, t est hors de portée. 4 } Les variables sont dites automatiques, elles sont détruites quand elles sont hors de portée Attention aux variables en dynamique!
Les variables globales On peut déclarer une variable à l extérieur de tout bloc, elle sera visible de tous. 1 int nbappel = 0; 2 int fonction1(int a) { 3 nbappel++; 4 //... 5 } 6 int fonction2(int a) { 7 nbappel++; 8 //... 9 } 0 int main(int argc, char ** argv) { 1 int i; 2 for (i = 0 ; i<1000 ; < i++) { 3 fonction1(i); 4 fonction2(i); 5 if (nbappel > 666) { 6 break; 7 } 8 } 9 }
Constantes On peut déclarer une variable constante à l aide du mot clef const. Cela permet d éviter certaines erreurs (mais on peut s en passer) 1 const int a=3; 2 //... 3 a = 42 ; // ERREUR à la compilation Une fonction peut déclarer attendre des arguments constants. Dans ce cas, on est sûr que la fonction ne modifie pas l objet 1 void fonction1(const char* chaine1, char* chaine2) { 2 // cette fonction ne modifie pas chaine1 3 } Bien sûr celà n est utile que lorsqu on passe des pointeurs!
Pointeurs et constantes Attention à la syntaxe! 1 const char * p; 2 // p est un pointeur sur un (const char) 3 char c ; p = &c ; // OK 4 *p = a ; // ERREUR : *p ne peut être modifié 1 char * const p ; 2 // p est un pointeur constant sur un char 3 char c ; p = &c ; // ERREUR : p ne peut être modifié 4 *p = a ; // OK 1 const char * const p ; 2 // p est un pointeur constant sur un const char 3 char c ; p = &c ; // ERREUR 4 *p = a ; // ERREUR
Les directives de préprocesseur Le préprocesseur agit avant le compilateur Ses commandes commencent par un # Nous en avons rencontré une : #include #include Inclut un fichier en entier #define Effectue un remplacement #if/#endif Compilation conditionelle #pragma Directive spécifique au compilateur (c est mal)
#define et les macros Exemple de constante : 1 #define PI 3.14159 Exemple de macro : 1 #define ABS(a) (a>0? a : -a ) Attention aux parenthèses! 1 #define CARRE(a) a*a 2 //... 3 CARRE(a+1); 4 // equivaut à a+1*a+1 = 2a + 1!
#if : compilation conditionelle 1 #include stdio.h 2 int main(int argc, char ** argv) { 3 #if WIN32 4 printf("hello, Windows World\n"); 5 #else 6 printf("hello, World\n"); 7 } 8 #endif
#include et les headers On regroupe souvent les déclarations de fonctions dans un fichier séparé appelé header 1 // fichier main.h 2 #ifndef MAIN_H // ceci permet de n inclure qu une seule fois le fichier 3 typedef struct Cercle { 4 double rayon; 5 int centre[2]; 6 } Cercle; 7 double aire(cercle *); 8 #define PI 3,14159 9 #endif 1 // fichier main.c 2 #include "main.h" 3 int main(int argc, char** argv) { 4 Cercle C; 5 double a = aire(&c); 6 } 7 8 double aire(cercle * uncercle) { 9 return PI * (uncercle->rayon) * (uncercle->rayon) ; 0 }
, plusieurs fichiers Quand un programme devient trop grand, on le sépare en plusieurs fichiers. Un fichier contient la fonction main, et d autres des fonctions annexes Du coup, on groupe les déclarations dans un ou plusieurs headers L édition des liens permet de créer un programme fonctionnel
Un schéma pour mieux comprendre
Les bibliothèques On utilise souvent des bibliothèques. Elles sont statiques (liées à la compilation) ou dynamiques (liées juste avant l exécution) Exemple : la lib standard (dont fait partie printf)
Un autre schéma
Les variables statiques Le mot clef static a plusieurs emplois. Une variable statique survit à la fin de son bloc Un fichier agit comme un bloc pour la portée (pas d accès en dehors du fichier) Les variables statiques sont initialisées à 0 par défaut Exemple : Variable globale statique : 1 static int a = 3; 2 static const char ch = c ; 3 int main (int argc, char** argv) { 4 // on peut utiliser a et ch partout dans le fichier 5 // mais pas ailleurs 6 } Exemple : Variable de fonction statique 1 int mafonction(int param1, double param2) { 2 static int nbappels; 3 // faire des trucs 4 nbappels++; 5 printf("j ai été appelée %d fois \n",nbappels); 6 }
Les variables externes extern sert pour globales On utilise extern quand la variable est définie dans un autre fichier Dans la pratique on utilise surtout un header pour les variables globales
La bibliothèque stantard J ai la flemme de faire cette slide stdio.h, string.f, stdlib.h, limits.h,...
Les trucs dont je n ai pas parlé Les branchements avec switch Les énumérations Les mots-clef volatile et register Plein d autres trucs que j oublie
C est fini... ou pas À vous de jouer! Lire le K&R Visiter le site du zéro Lire du code Coder en C!