Conception de structures de donn´ ees
Cours 1
Tableaux, pointeurs, allocation dynamique
11 f´evrier 2013
Struct 1/32
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)
Struct 2/32
Tableaux et Matrices Pointeurs
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 `a CONCEVOIRdes 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 : les bases et les exemples fondamentaux.
En TP : implantation des structures vues en cours et en TD et utilisation pour 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.
Tableaux et Matrices Pointeurs
Rappels :
Tableaux et Matrices
Tableaux statiques
B Un “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).
B La taille d’un tableau statique doit donc ˆetreconnue lors de la compilation (C Ansi) ; c’est :
soit une constante enti`ere : 4, 18, 150, ...
soit une constante symbolique : #define N 1000 B en 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[];
B Le type des ´el´ements du tableau doit ˆetre sp´ecifi´e lors de la d´eclaration :
type nomTab[taille];
Struct 5/32
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;
Struct 6/32
Tableaux et Matrices Pointeurs
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 ≥n provoquera 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 utilise lasentinelle ’\0’pour stopper le parcours.
for(i=0; mot[i]!=’\0’; i++) putchar(mot[i]);
Tableaux et Matrices Pointeurs
Tableaux et fonctions
Attention : TABLEAU = POINTEUR! !
la variable nom tableau contient l’adresse de la premi`ere case du tableau.
B Cons´equence : un tableau pass´e en param`etre d’une fonction peut ˆetre modifi´e! !
MAIS : une fonction ne peut pas renvoyer un tableau statique cr´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.
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;
}
Struct 9/32
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;
...
Struct 10/32
Tableaux et Matrices Pointeurs
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 ?
B Convention :
type nomMatrice[nb lignes][nb colonnes];
B matrice[i][j] → case sur la ie ligne, dans la je colonne .
Tableaux et Matrices Pointeurs
Pointeurs
Rappels
Vision abstraite :
B Un pointeur est une variable “pointant”
vers un emplacement en m´emoire.
En pratique :
B Un pointeur est une variable qui contient l’adresse m´emoire d’une variable.
B Donc : 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; ? ?
ou dynamiquement.
int *q = (int *) malloc( sizeof(int) );
*q = 4;
Struct 13/32
Rappels - suite
2 op´erateurs sp´ecifiques :
R´ef´erencement :&donne l’adresse d’une variable, (un pointeur sur cette variable) :
char c = ’a’;
char *p = &c; /* p est de type (char *) */
D´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 */
Struct 14/32
Tableaux et Matrices Pointeurs
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];
Tableaux et Matrices Pointeurs
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 *pi de la taille du type point´e*/
b = *t+1; /* b=t[0]+1 */
b = *(t+1); /* b=t[1] ... attention aux priorit´es !*/
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
Struct 17/32
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,
g´en´erique, polymorphe.
On utilisevoid * quand on ne connait pas (`a priori) le type point´e.
B Par exemple, le type de retour de malloc est void *.
! 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;
Struct 18/32
Tableaux et Matrices Pointeurs
Passage de param` etres par r´ ef´ erence
En C, les param`etres des fonctions sont pass´es par valeur.
C’est une COPIEde 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-d passer les param`etres par r´ef´erence par 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;
}
Tableaux et Matrices Pointeurs
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 `ap=tab et *pd´esigne tab[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 que p = tab.
L’op´ erateur sizeof()
L’op´erateur sizeof() : 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);
...
Struct 21/32
Allocation dynamique
#include <stdlib.h>On a vu qu’allouer de la m´emoire statique avait un int´erˆet limit´e.
Comment faire pour r´eserver de l’espace au moment de l’ex´ecution ? B Il faut allouer la m´emoiredynamiquement.
void *malloc (size t size);
• Alloue sizeoctets ;
• 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*/
Struct 22/32
Tableaux et Matrices Pointeurs
Allocation dynamique - suite
void *calloc (size t nb, size t size);
Alloue et remplit de 0 la m´emoire n´ecessaire pournb´el´ements desize 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 sizepremiers octets `a l’adressep. 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;
Tableaux et Matrices Pointeurs
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);
}
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);
}
Struct 25/32
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 fonction void free( void *p ) :
• lib`ere l’espace m´emoire point´e par p
• qui a ´et´e obtenu lors d’un appel `a malloc, calloc ourealloc
• Sinon, ou si il a d´ej`a ´et´e lib´er´e avec free(), le comportement est ind´etermin´e.
• Attention, freene met pas le pointeur p `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 */
Struct 27/32
Tableaux et Matrices Pointeurs
Copie de m´ emoire, de tableaux
#include <string.h>void *memcpy (void *dest, const void *src, size t n) Copien octets depuis la zone m´emoire src vers la zone m´emoire dest. Les deux zones ne doivent pas se chevaucher. Si c’est le cas, utiliser plutˆot memmove.
Ces deux fonctions renvoient un pointeur sur dest.
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));
Tableaux et Matrices Pointeurs
Copie de m´ emoire (suite)
Exemple d’utilisation de void * et memcpy pour ´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);
}
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 et constantes : 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 des char * et allou´ees dynamiquement (malloc(), ...).
char *s3 = (char *)malloc( 5*sizeof(char) ); if (s3==NULL) error("...");
s3[0] = ’t’; /* OK */
printf(s3) /* NON! */
Struct 30/32
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 par src (y compris le ’\0’final) dans la chaˆıne point´ee par dest. Les 2 chaˆınes ne doivent pas se chevaucher et destdoit ˆetre assez grande pour la copie. Renvoie un pointeur sur la chaˆıne dest.
char *strdup (const char *s)
Renvoie un pointeur sur une nouvelle chaˆıne de caract`eres qui est dupliqu´ee depuis s. 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.
Struct 31/32
Tableaux et Matrices Pointeurs
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 dest en ´ecrasant le
’\0’`a la fin de dest, 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 chaine s1 et chaines2et renvoie un entier : - n´egatif, si chaine s1 est inf´erieure `a chaine s2;
- nul, si chaine s1est ´egale `a chaine s2;
- positif, si chaine s1 est sup´erieur `a chaines2.
char * strchr(const char *s, int c)
Recherche le caract`ere c dans la chaine s et renvoie la position de la 1ere occurrence ou de la derni`ere occurrence (strrchr).
Variantes avec la longueur : strncpy, strncat, strncmp, ...