• Aucun résultat trouvé

Cours1Tableaux,pointeurs,allocationdynamique Conceptiondestructuresdedonn´ees

N/A
N/A
Protected

Academic year: 2022

Partager "Cours1Tableaux,pointeurs,allocationdynamique Conceptiondestructuresdedonn´ees"

Copied!
36
0
0

Texte intégral

(1)

Conception de structures de donn´ ees

Cours 1

Tableaux, pointeurs, allocation dynamique

11 f´evrier 2013

(2)

Avant de commencer

page web du cours :

http://monge.univ-mlv.fr/~pivoteau/STRUCT/index.html B planning, dates d’Exam

B transparents de cours, sujets de TD

B sujets de TP, programmes, quelques corrections B bibliographie et lien utiles

7 s´eances de cours B lundi, 10h30 - 12h30 environ 14 s´eances de TD/TP

B TD en demi-groupes

B TP en quarts de groupe, `a rendre.

contrˆole des connaissances :

B contrˆole continu : TPs, partiel (mi-semestre) B exam fin de semestre

B projet (avril - juin)

(3)

But du cours

Ce n’est pas un cours de programmation !

... mˆeme si il y aura de la programmation quand mˆeme.

L’objectif est d’apprendre `aCONCEVOIR des structures de donn´ees et les algorithmes qui les manipulent.

... autrement dit : il faut apprendre `a r´efl´echir.

Chaque COURS sera en partie :

de l’algorithmique(des structures de donn´ees) ;

de l’implantation dans un langage de programmation (C) ; En TD : lesbases et les exemples fondamentaux.

En TP :implantation des structures vues en cours et en TD et utilisationpour r´esoudre diff´erents probl`emes.

B Tout ce qui est vu en TD (fini ou non) doit ˆetre su.

B Tous les TPs sont `a finir et `a rendre.

(4)

Rappels :

Tableaux et Matrices

(5)

Tableaux statiques

BUn “objet”statique est un objet dont l’emplacement en m´emoire est r´eserv´e lors de la compilation(et pas `a l’ex´ecution).

BLa tailled’un tableau statique doit donc ˆetre connue lors de la compilation(C Ansi) ; c’est :

soit une constante enti`ere : 4, 18, 150, ...

soit une constante symbolique :#define N 1000 Ben C 99 : variable length arrays. la taille n’a plus besoin d’ˆetre connue `a la compilation !

Mais on ne peut toujours pas d´eclarer : int tab[];

BLe typedes ´el´ements du tableau doit ˆetre sp´ecifi´e lors de la d´eclaration:

type nomTab[taille];

(6)

D´ eclarer et initialiser un tableau (statique)

#define TAILLE 100 ...

int i;

int n=10;

int tab0[n]; /* !! n n’est pas constant (ok en C 99)*/

int tab1[TAILLE];

char tab2[20];

short tab3[]={0,1,2,3,4,5,6,7,8,9}; /* taille = 10 */

double tab4[]={12.2,3.0,8.1,4.777}; /* taille = 4 */

char mot[]="Toto"; /* {’T’,’o’,’t’,’o’,’\0’} */

for(i=0; i<=TAILLE-1; i++) tab1[i]=i*n;

for(i=0; i<20; i++) tab2[i]=’a’+i;

(7)

Parcourir un tableau

En C, tousles tableaux commencent `a l’indice 0.

Dans un tableau de longueur n, on peut acc´eder aux cases d’indice 0 `a n−1.

Une tentative d’acc`es `a une case d’indice≥nprovoquera un r´esultat al´eatoire et risque en particulier de provoquer une segmentation fault.

for(i=0;i<TAILLE;i++) printf("%d ", tab1[i]);

for(i=TAILLE-1;i>=0;i--) printf("%d ", tab1[i]);

Dans le cas d’une chaˆıne de caract`eres :

soit on connaˆıt sa taille et on peut proc´eder de mˆeme ; soit on utiliselasentinelle ’\0’pour stopper le parcours.

for(i=0; mot[i]!=’\0’; i++) putchar(mot[i]);

(8)

Tableaux et fonctions

Attention :TABLEAU = POINTEUR! !

la variable nom tableau contient l’adresse de la premi`ere case du tableau.

BCons´equence : un tableau pass´e en param`etre d’une fonction peut ˆetre modifi´e! !

MAIS : une fonction ne peut pas renvoyer un tableau statiquecr´e´e dans cette fonction.

Pourquoi ? Parce que l’espace r´eserv´e (allou´e) au tableau dans la fonction est d´etruit `a la sortie de la fonction.

Par contre, on peut passer en param`etre de la fonction un tableau d´ej`a d´eclar´e et le modifier.

(9)

Exemple

void afficheTab(int tab[], int lgTab){

int i;

for(i=0;i<lgTab;i++) printf("%d ",tab[i]);

printf("\n");

}

void modifTab(int tab[], int i, int elem){

tab[i]=elem;

}

int main (int argc, char *argv[]){

int tab[]={0,1,2,3,4,5,6,7,8,9}; /* taille = 10 */

afficheTab(tab,10);

modifTab(tab,5,-1);

afficheTab(tab,10);

return 0;

(10)

Exemple

void afficheTab(int tab[], int lgTab){

int i;

for(i=0;i<lgTab;i++) printf("%d ",tab[i]);

printf("\n");

}

void modifTab(int tab[], int i, int elem){

tab[i]=elem;

}

int main (int argc, char *argv[]){

int tab[]={0,1,2,3,4,5,6,7,8,9}; /* taille = 10 */

afficheTab(tab,10); /* 0 1 2 3 4 5 6 7 8 9 */

modifTab(tab,5,-1);

afficheTab(tab,10); /* 0 1 2 3 4 -1 6 7 8 9 */

return 0;

}

(11)

D´ eclarer et initialiser une matrice

Matrice = Tableau `a 2 dimensions.

Matrice = Tableau dont les cases sont des tableaux.

Bcomme pour un tableau classique, on indique sa taille lors de la d´eclaration (taille constante) :

int matrice[2][3];

Bcomme pour un tableau classique, on peut l’initialiser lors de la d´eclaration ...

int matrice[2][3] = { { 1 , 2 , 3 }, { 4 , 5 , 6 } };

B ... ou apr`es

matrice[0][0] = 1;

matrice[0][1] = 2;

matrice[0][2] = 3;

matrice[1][0] = 4;

...

(12)

Parcourir une matrice

int i,j;

int matrice[3][5];

for(i=0;i<3;i++) for(j=0;j<5;j++)

matrice[i][j]=i+j;

0 1 2 3 4 1 2 3 4 5 2 3 4 5 6

0 1 2 1 2 3 2 3 4 3 4 5 4 5 6

ou ?

BConvention :

type nomMatrice[nb lignes][nb colonnes];

Bmatrice[i][j]→ case sur la ie ligne, dans la je colonne .

(13)

Pointeurs

(14)

Rappels

Vision abstraite :

BUn pointeur est une variable“pointant”

vers un emplacement en m´emoire.

En pratique :

BUn pointeur est une variable qui contient l’adresse m´emoire d’une variable.

BDonc : pointeur = adresse m´emoire (entier).

L’espace m´emoire point´e peut ˆetre allou´e : de fa¸con statique(d´ej`a vu) :

int i = 3; et pourquoi pas : int *p;

int *p = &i; *p = 3; ? ?

oudynamiquement.

int *q = (int *) malloc( sizeof(int) );

*q = 4;

(15)

Rappels - suite

2 op´erateurs sp´ecifiques :

ef´erencement :&donne l’adresse d’une variable, (un pointeur sur cette variable) :

char c = ’a’;

char *p = &c; /* p est de type (char *) */

er´ef´erencement, indirection :*donne la valeur point´ee par un pointeur (la valeur `a l’adresse correspondante) :

printf("%c", *p); /* affiche a */

Exemples (Kernighan et Ritchie) : int x = 1, y = 2, z[10];

int *pi;

pi = &x;

y = *pi; /* y=*(&x) */

*pi = 0;

pi = &z[0]; /* pi=z */

(16)

1

x

2

pi y

z

1

x

1

pi y

z

0

x

1

pi y

z

0

x

1

pi y

z

1. pi = &x; 2. y = *pi;

3. *pi = 0; 4. pi = &z[0];

(17)

D’autres exemples

D´eclarations :

int i=6, b, *p, *pi, t[10]={0};

struct {int x, y;} *q; /* pointeur sur struct. anonyme */

int *t1[10]; /* tableau de pointeurs sur entier */

int (*t2)[10]; /* pointeur sur tableau d’entiers */

void *g; /* pointeur g´en´erique */

Affectations et arithm´etique : pi = &i;

p = t;

t = p; /* NON ! pas d’affectation de tableau. */

*p = 6; /* m´emoire non allou´ee ! */

p = i;

p = &t[3];

*pi += 1; /* incr´emente *pide la taille du type point´e*/

b = *t+1; /* b=t[0]+1*/

b = *(t+1); /* b=t[1]... attention aux priorit´es !*/

(18)

Rappel ? Priorit´ es des op´ erateurs

Cat´egorie d’op´erateurs Op´erateurs Assoc.

( ) [ ] . −> G→D

unaires − ++ −− ! ˜ ∗ & sizeof (type) D→G

mult., div., mod. * /% G→D

addition, soustraction + − G→D

binaires de d´ecalage << >> G→D

relationnels < <= > >= G→D

de comparaison == ! = G→D

et binaire & G→D

ou exclusif binaire ˆ G→D

ou binaire | G→D

et logique && G→D

ou logique || G→D

conditionnel ?: D→G

affectation = + = −= ∗= etc... D→G

virgule , G→D

(19)

void * et NULL

Il est possible de d´efinir un pointeur sur void, c’est-`a-dire sur quelque chose qui n’a pas de type pr´ed´efini. Un

pointeur de typevoid *est un pointeur : universel,

en´erique, polymorphe.

On utilise void *quand on ne connait pas (`a priori) le type point´e.

BPar exemple, le type de retour de mallocestvoid *.

! Pas d’indirection (*), ni d’arithm´etique sur un void *.

Un pointeur doit de pr´ef´erence ˆetre typ´e !

Il existe un pointeur qui ne pointe sur rien : NULL.

int *p = NULL;

(20)

Passage de param` etres par r´ ef´ erence

En C, les param`etres des fonctions sont pass´es parvaleur.

C’est uneCOPIE de la valeur de l’argument qui est manipul´ee dans la fonction : donc on ne peut pas modifier une variable pass´ee en param`etre d’une fonction.

Solution :Utiliser des pointeurs sur les variables `a modifier, c-`a-dpasser les param`etres par r´ef´erencepar exemple :

void swap(int a, int b){

int tmp = a;

a=b; =>

b=tmp;

}

int a=1,b=2;

swap(a,b); /* NON: a=1, b=2 */

swap2(&a,&b); /* OUI: a=2, b=1 */

void swap2(int *a, int *b){

int tmp = *a;

*a=*b;

*b=tmp;

}

(21)

Pointeurs et tableaux

... mais lorsque l’on passe un tableau en param`etre d’une fonction on peut le modifier ! Pourquoi ? Parce que le nom du tableau est en fait un pointeur vers la premi`ere case du tableau (l’adresse de d´epart du tableau).

Si on d´eclare un tableau statique et un pointeur ainsi : int tab[10];

int *p;

alors l’affectation : p=&tab[0] est ´equivalente `a p=tab et*p d´esignetab[0].

les crochets sont une simplification d’´ecriture : tab[i] ⇔ *(tab+i)

Il existe tout de mˆeme une diff´erence entre pointeur et tableau : un nom de tableau n’est pas une variable, on ne peut donc rien affecter `a un nom de tableau, contrairement

`

a un pointeur : tab = p alors quep = tab.

(22)

L’op´ erateur sizeof()

L’op´erateursizeof(): Renvoie la taille en octets de l’argument (soit un type, soit une variable ou une expression)

short A; char B[5][10];

sizeof A 2 sizeof B 50 sizeof 4.25 8

sizeof "abcd" 5 sizeof(float) 4 sizeof(double) 8 void fnc (int tab[]){

printf("%ld, %ld \n", sizeof(tab), sizeof(tab[0])); } int main (int argc, char *argv[]){

int t1[]={1,2,3,4,5};

int *t2= (int *)malloc(20*sizeof(int));

printf("%ld, %ld \n", sizeof(t1), sizeof(t1[0]));

printf("%ld, %ld \n", sizeof(t2), sizeof(t2[0]));

fnc(t1);

...

(23)

L’op´ erateur sizeof()

L’op´erateursizeof(): Renvoie la taille en octets de l’argument (soit un type, soit une variable ou une expression)

short A; char B[5][10];

sizeof A 2 sizeof B 50 sizeof 4.25 8

sizeof "abcd" 5 sizeof(float) 4 sizeof(double) 8 void fnc (int tab[]){

printf("%ld, %ld \n", sizeof(tab), sizeof(tab[0])); } int main (int argc, char *argv[]){

int t1[]={1,2,3,4,5};

int *t2= (int *)malloc(20*sizeof(int));

printf("%ld, %ld \n", sizeof(t1), sizeof(t1[0])); 20,4 printf("%ld, %ld \n", sizeof(t2), sizeof(t2[0])); 8,4 fnc(t1); 8,4

...

(24)

Allocation dynamique

#include <stdlib.h>

On a vu qu’allouer de la m´emoire statiqueavait un int´erˆet limit´e.

Comment faire pour r´eserver de l’espace au moment de l’ex´ecution ? BIl faut allouer la m´emoire dynamiquement.

void *malloc (size t size);

• Allouesize octets ;

• Renvoie un pointeur sur la m´emoire allou´ee ;

• La zone de m´emoire n’est pas initialis´ee.

long *a = (long *)malloc( sizeof(long) );

int n = 100;

long *tab = (long *) malloc(n * sizeof(long));

struct z {int f,g;}; struct z *p;

p=(struct z *)malloc(sizeof(struct z)); /* ptr surstruct z*/

(25)

Allocation dynamique - suite

void *calloc (size t nb, size t size);

Alloue et remplit de 0 la m´emoire n´ecessaire pour nb´el´ements de size octets ; renvoie un pointeur sur la zone allou´ee.

Attention, ce n’est pas la mˆeme signature quemalloc! int n = 100;

long *tab = (long *) calloc(n, sizeof(long)); /*nfois 0 */

void *realloc (void *p, size t size);

R´eduit (ou augmente) la taille du bloc de m´emoire point´e par p

`

a une taille de sizeoctets ; conserve les size premiers octets `a l’adresse p. Le reste de la nouvelle zone n’est pas initialis´e.

int * tab;

tab = (int *) calloc ( 2, sizeof(int) );

tab[0] = 33; tab[1] = 55;

tab = (int *)realloc(tab, 3 * sizeof(int) );

tab[2] = 77;

(26)

Allocation dynamique - suite

! Attention, il peut toujours se produire des errors lors de l’allocation dynamique de m´emoire : il faut TOUJOURS v´erifier que le pointeur retourn´e lors de l’allocation n’est pas NULL !

int *tab1, *tab2;

tab1 = (int *) malloc( 1000*sizeof(int) );

if( tab1 == NULL ){

fprintf(stderr,"allocation rat´ee !");

exit(EXIT_FAILURE);

}

if( ( tab2 = (int *)malloc(1000*sizeof(int)) ) == NULL ){ fprintf(stderr,"allocation rat´ee !");

exit(EXIT_FAILURE);

}

(27)

Fonction pour l’allocation dynamique

void alloue(int *p, int nb){

if( (p=(int *)malloc(nb*sizeof(int))) == NULL ) error("L’allocation a ´echou´e !");

}

void alloue2(int **p, int nb){

if( (*p=(int *)malloc(nb*sizeof(int))) == NULL ) error("L’allocation a ´echou´e !");

}

int *alloue3(int nb){

int *p;

if((p=(int *)malloc(nb*sizeof(int)))==NULL) error("alloc. rat´ee !");

return p;

}

int main(void){

int *tab=NULL, *tab2=NULL, *tab3=NULL;

alloue(tab,1000);

alloue2(&tab2,1000);

tab3=alloue3(1000);

(28)

Fonction pour l’allocation dynamique

void alloue(int *p, int nb){ /* version incorrecte */

if( (p=(int *)malloc(nb*sizeof(int))) == NULL ) error("L’allocation a ´echou´e !");

}

void alloue2(int **p, int nb){ /* fonction qui modifie le pointeur */

if( (*p=(int *)malloc(nb*sizeof(int))) == NULL ) error("L’allocation a ´echou´e !");

}

int *alloue3(int nb){ /* fonction qui renvoie le pointeur */

int *p;

if((p=(int *)malloc(nb*sizeof(int)))==NULL) error("alloc. rat´ee !");

return p;

}

int main(void){

int *tab=NULL, *tab2=NULL, *tab3=NULL;

alloue(tab,1000); /* il ne se passe rien! */

alloue2(&tab2,1000); /* l’allocation est effectu´ee */

tab3=alloue3(1000); /* l’allocation est effectu´ee */

}

(29)

Lib´ eration de la m´ emoire

La m´emoire n’´etant pas infinie, lorsqu’un emplacement m´emoire n’est plus utilis´e, il est important de lib´erer cet espace.

La fonctionvoid free( void *p ):

•lib`ere l’espace m´emoire point´e par p

•qui a ´et´e obtenu lors d’un appel `amalloc,callocou realloc

•Sinon, ou si il a d´ej`a ´et´e lib´er´e avecfree(), le comportement est ind´etermin´e.

•Attention, free ne met pas le pointeurp`a NULL.

int i, int *tab=(int *)calloc(1000, sizeof(int));

free(tab);

int *tab2;

alloue2(&tab2,1000);

for (i=0;i<1000;i++)

*(tab2+i)=i;

afficheTab(tab,1000); /* affiche (parfois) 0 1 2 3 ... 999 */

(30)

Lib´ eration de la m´ emoire

La m´emoire n’´etant pas infinie, lorsqu’un emplacement m´emoire n’est plus utilis´e, il est important de lib´erer cet espace.

La fonctionvoid free( void *p ):

•lib`ere l’espace m´emoire point´e par p

•qui a ´et´e obtenu lors d’un appel `amalloc,callocou realloc

•Sinon, ou si il a d´ej`a ´et´e lib´er´e avecfree(), le comportement est ind´etermin´e.

•Attention, free ne met pas le pointeurp`a NULL.

int i, int *tab=(int *)calloc(1000, sizeof(int));

free(tab); → tab = NULL;

int *tab2;

alloue2(&tab2,1000);

for (i=0;i<1000;i++)

*(tab2+i)=i;

afficheTab(tab,1000); /* error... */

(31)

Copie de m´ emoire, de tableaux

#include <string.h>

void *memcpy (void *dest, const void *src, size t n) Copienoctets depuis la zone m´emoire srcvers la zone m´emoire dest. Les deux zones ne doivent pas se chevaucher. Si c’est le cas, utiliser plutˆotmemmove.

Ces deux fonctions renvoient un pointeur surdest.

int lg=20;

int t1[]={1,2,3,4,5};

int *t2=(int *)malloc(lg*sizeof(int)); if (t2==NULL) error("...");

int *t3=(int *)malloc(lg*sizeof(int)); if (t3==NULL) error("...");

for(i=0;i<lg;i++) t2[i]=i;

memcpy(t3, t2, lg*sizeof(int));

memmove(t3+10, t3, 10*sizeof(int));

(32)

Copie de m´ emoire (suite)

Exemple d’utilisation devoid *etmemcpypour ´ecrire une fonction d’´echange universelle :

void swap3(void *a, void *b, size_t size){

if ( (void *tmp = malloc(size)) == NULL ) error(...);

memcpy(tmp,a,size);

memcpy(a,b,size);

memcpy(b,tmp,size);

free(tmp);

}

int main(void){

int k = 3, j = 4;

printf("%d, %d \n", k, j);

swap3(&k,&j,sizeof(int));

printf("%d, %d \n", k, j);

}

(33)

Copie de m´ emoire (suite)

Exemple d’utilisation devoid *etmemcpypour ´ecrire une fonction d’´echange universelle :

void swap3(void *a, void *b, size_t size){

if ( (void *tmp = malloc(size)) == NULL ) error(...);

memcpy(tmp,a,size);

memcpy(a,b,size);

memcpy(b,tmp,size);

free(tmp);

}

int main(void){

int k = 3, j = 4;

printf("%d, %d \n", k, j); → 3, 4 swap3(&k,&j,sizeof(int));

printf("%d, %d \n", k, j); → 4, 3 }

(34)

Le cas des chaˆınes de caract` eres

#include <string.h>

On a vu que les chaˆınes de caract`eres sont des tableaux, donc des pointeurs... avec quelques subtilit´es.

Attention, les chaˆınes de type char *initialis´ees avec des " "

sont statiques etconstantes : on ne peut pas les modifier.

char s1[] = "toto";

char *s2 = "titi";

s1[0] = ’x’; /* OK */

s2[0] = ’y’; /* NON! */

Elles peuvent ´egalement ˆetre d´eclar´ees comme deschar *et allou´ees dynamiquement (malloc(), ...).

char *s3 = (char *)malloc( 5*sizeof(char) ); if (s3==NULL) error("...");

s3[0] = ’t’; /* OK */

printf(s3) /* NON! */

(35)

Chaˆınes de caract` eres - suite

#include <string.h>

printf,scanf:<stdio.h>

size t strlen (const char *s);

Renvoie longueur de la chaˆıne de caract`eres s, sans compter le caract`ere nul’\0’final.

char *strcpy (char *dest, const char *src)

Copie la chaˆıne point´ee parsrc(y compris le ’\0’ final) dans la chaˆıne point´ee pardest. Les 2 chaˆınes ne doivent pas se chevaucher etdest doit ˆetre assez grande pour la copie. Renvoie un pointeur sur la chaˆınedest.

char *strdup (const char *s)

Renvoie un pointeur sur une nouvelle chaˆıne de caract`eres qui est dupliqu´ee depuiss. La m´emoire est obtenue par malloc(), et peut (doit) donc ˆetre lib´er´ee. Renvoie un pointeur sur la chaˆıne dupliqu´ee, ou NULL s’il n’y avait pas assez de m´emoire.

(36)

Chaˆınes de caract` eres - suite

#include <string.h>

char *strcat (char *dest, const char *src)

Ajoute la chaˆıne src`a la fin de la chaˆıne desten ´ecrasant le

’\0’ `a la fin dedest, puis en ajoutant un nouveau’\0’ final.

Les chaˆınes ne doivent pas se chevaucher, et destdoit ˆetre assez grande pour le r´esultat. Renvoie un pointeur sur dest.

int strcasecmp(const char *s1, const char *s2);

Compare les chaˆınes chaines1et chaines2et renvoie un entier : - n´egatif, si chaine s1est inf´erieure `a chaines2;

- nul, si chaines1 est ´egale `a chaine s2; - positif, si chaine s1est sup´erieur `a chaines2.

char * strchr(const char *s, int c)

Recherche le caract`erecdans la chaine set renvoie la position de la 1ere occurrence ou de la derni`ere occurrence (strrchr).

Variantes avec la longueur : strncpy,strncat,strncmp, ...

Références

Documents relatifs

[r]

Dès lundi matin nou s nous m ettons li ce travail et cela nous demandera plus de deux semaines: ce fut une situation mathématique très riche.. les débats à ce

- Si on n’a plus besoin d’un bloc de mémoire réservé dynamiquement par malloc, alors on peut le libérer à l’aide de la fonction

Exprimer en heures et minutes les temps des quatre concurrents dont il est question dans cette course..

Calculer les deux valeurs possible de x max puis déduire la valeur de x max à conserver et indiquer le réactif limitant dans cette réaction... Calculer la masse de carbone

Source : Article 7 de l’Arrêté ministériel du 20/11/2017 relatif au suivi en service des équipements sous pression et des récipients à pression simple.

[r]

[r]