vendredi 7 octobre : comparaison du pascal et du C
vendredi 14 octobre 2005 : ++ += virgule ? : for
vendredi 21 octobre 2005 : priorité des opérateurs
vendredi 28 octobre 2005 : pointeurs et passage d'arguments
vendredi 4 novembre 2005 : pointeurs et tableaux
vendredi 18 novembre 2005 : structures, pointeurs, malloc et listes chaînées
vendredi 25 novembre 2005 : liste chaînée : création et parcours
vendredi 2 décembre 2005 : manipulations élémentaires de listes chaînées
vendredi 9 décembre 2005 : insertion triée, fusion et trifusion
vendredi 16 décembre 2005 : pointeur sur un pointeur
vendredi 13 janvier 2006 : contrôle
vendredi 20 janvier 2006 : corrigé du contrôle
suivant sommaire
vendredi 7 octobre : comparaison du pascal et du C
Le langage C a été inventé en s'inspirant du pascal.
Il lui ressemble donc beaucoup.
Nous allons donc voir toutes les différences entre ces deux langages.
compacité: BEGIN END IF THEN ELSE WHILE et DO
Les inventeurs du C ont voulu rendre les programmes les plus courts possible.
C'est pourquoi, ils ont essayé de mettre le moins possible de mots réservés.
Par exemple BEGIN et END sont devenus { et } tandis que
THEN et DO ont été remplacés par ).
begin {
a:=3; a=3;
c:=5 c=5;
end }
if a=3 then b:=4 if(a==3) b=4;
else c:=5 else c=5;
while i>0 do i=i-2 while(i>0) i-=2;
Dans un IF en pascal la condition se trouve entre IF et THEN, et elle n'est donc pas nécessairement entourée de parenthèse.
C'est le THEN qui indique la fin de la condition.
En C, il n'y a pas de then, mais la condition doit obligatoirement être mise entre parenthèse.
Tout ce passe donc comme si le IF et le THEN du pascal étaient remplacés par if( et ) en C.
De même, on peut considérer que le WHILE et le DO du pascal sont remplacés par while( et ) en C.
point-virgule et instruction vide
Le point-virgule a un statut très différent en pascal et en C.
En pascal une instruction ne se termine jamais par un point-virgule, les points-virgules se trouvent entre les instructions
qui sont mises les unes derrière les autres dans un bloc BEGIN ... END (ou un REPEAT ... UNTIL).
Il n'y a donc pas besoin de point-virgule avant le END, et il n'y a jamais de point-virgule avant un ELSE. (Ce point-virgule séparerait
le IF qui précède de l'instruction suivante qui commencerait par ELSE, or aucune instruction ne commence par ELSE).
En C ce n'est pas du tout la même chose.
Toutes les instructions se terminent par un point-virgule (ou une accolade fermante).
On met un point-virgule derrière une expression pour en faire une instruction.
A l'intérieur d'un bloc {...} toutes les instructions sont mises bout à bout sans rajouter de séparateur.
Par exemple begin a:=3;c:=5 end contient deux instructions «a:=3» et «c:=5» séparées par un «;» tandis que
{a=3;c=5;} contient deux instructions «a=3;» et «c=5;» sans rien entre elles.
Ces deux instructions composées se ressemblent donc beaucoup.
On peut confondre facilement ces deux syntaxes, d'autant plus qu'en pascal on a le droit d'écrire
begin a:=3;c:=5;end qui contient trois instructions «a:=3» et «c:=5» et «»(l'instruction vide) séparées par deux «;».
L'instruction vide existe aussi en C et se note «;».
Par exemple
begin;;a:=3;;;c:=5;;;end contient neuf instructions : 2 vides, «a:=3», 2 vides, «c:=5» et 3 vides séparées par huit «;»
tandis que
{;;a=3;;c=5;;;} contient huit instructions : 2 vides, «a=3;», 2 vides, «c=5;» et 2 vides sans rien entre elles.
Tout cela peut paraître un peu bizarre, et l'instruction vide semble inutile.
En fait elle sert dans le cas d'un if contenant un else mais sans rien à faire dans le cas du then :
if a=3 then if(a==3) ;
else c:=5 else c=5;
En pascal il n'y a rien entre THEN et ELSE, tandis qu'en C il y a un point-virgule entre ) et else.
On peut aussi mettre une instruction vide comme corps d'une boucle while :
while readkey=' ' do; while(getchar()==' ') ;
a:=a+1 a++;
En pascal le point-virgule sépare le while de l'affectation qui suit.
Il ne fait pas partie du while.
L'instruction répétée est l'instruction vide entre do et «;».
En C le point-virgule est l'instruction vide qui est répétée. Il fait donc partie du while.
déclarations de variables
En pascal le type est mis après la variable à déclarer.
En C le type est mis avant.
var i:integer; int i;
ii:word; unsigned int ii;
j,k:longint; long int j,k;
x,y:single; float x,y;
z:double; double z;
u:extended; long double u;
En pascal les déclarations sont mises avant le corps de la procédure.
En C elles sont mises au début de n'importe quel bloc.
opérateurs +, *, /, etc.
2+3 2+3 5
4-1 4-1 3
4*5 4*5 20
23 div 5 23/5 4
21 mod 8 21%8 5
7.0/2.0 7.0/2.0 3.0
6 and 5 6&5 4
6 or 5 6|5 7
6 xor 5 6^5 3
false or true = true 0||32 1
false and true = false 0&&32 0
-(2+3) -(2+3) -5
not true = false !4 0
not false = true !0 1
not 5 ~5 -6
21 shr 2 21>>2 5
7 shl 3 7<<3 56
4=3 4==3 0
4<>3 4!=3 1
4<3 4<3 0
4>3 4>3 1
4<=3 4<=3 0
4>=3 4>=3 1
En pascal il existe un type booléen (BOOLEAN), différent des types numériques.
En C il n'en existe pas. Les opérateurs logiques && (et logique), || (ou logique) et ! (non logique) ont un résultat entier qui
peut être 0 (pour faux) ou 1 (pour vrai).
Par contre leurs opérandes peuvent être des nombres quelconques, entiers ou réels. Un nombre nul est considéré comme faux.
Un nombre non nul est considéré comme vrai.
Par exemple (-3.0 && 0) se comprend comme (vrai et faux) qui est égal à faux qui est représenté par 0.
suivant précédent sommaire
vendredi 14 octobre 2005 : ++ += virgule ? : for
opérateurs ++ et --
Les instructions i++; et ++i; sont équivalentes à i=i+1;
Par contre les expressions i++ et ++i ont le même effet, d'augmenter i de 1,
mais elles ont des valeurs différentes : la valeur de i++ est l'ancienne valeur de i, tandis que la valeur de ++i
est la nouvelle valeur de i.
Par exemple
{i=3; a=i++;} est équivalent à {i=4; a=3;}, mais
{i=3; a=++i;} est équivalent à {i=4; a=4;}.
opérateurs d'affectation
a+=3 est équivalent à a=a+3.
b/=2 est équivalent à b=b/2.
etc.
Les opérateurs d'affectation sont = += -= *= /= %= <<= >>= |= &= ^=.
En pascal a:=3 est une instruction.
En C l'instruction équivalente est a=3; (avec un point-virgule).
Par contre a=3 (sans point-virgule) n'est pas une instruction, c'est une expression qui a pour valeur 3 (et dont l'évaluation a pour effet
de mettre 3 dans a).
Par exemple c=(a=3)+(b=4);
est équivalent à
a=3;
b=4;
c=a+b;
Les opérateurs binaires sont en général
«associatifs à gauche» par exemple 4-5-6 est égal à (4-5)-6 et vaut -7, alors que 4-(5-6) vaut 5.
Les opérateurs d'affectation sont eux «associatifs à droite».
Par exemple a=b=c=3; est équivalent à a=(b=(c=3)); c'est-à-dire à {c=3; b=c; a=b;} ou encore à {c=3; b=3; a=3;}.
De même s+=t*=x/++i; est équivalent à {i=i+1; t=t*(x/i); s=s+t;}
ordre d'évaluation des opérandes
Quand on exécute a=f(i,3)+g(i); on ne sait pas si la fonction f(i,3) est évaluée avant ou après la fonction g(i).
Selon la norme ansi, ce choix est laissé à la discrétion du compilateur.
Tous les opérateurs binaires ont ce même comportement, sauf && || et ,(virgule).
Les opérateurs ou et et logiques évaluent d'abord leur opérande gauche.
Après cela ils n'évaluent leur opérande droit que si sa valeur est nécessaire pour déterminer le résultat de l'opération.
Par exemple
{i=0; j=3; k=i++||j++;} est équivalent à {i=1; j=4; k=1;} mais
{i=0; j=3; k=++i||j++;} est équivalent à {i=1; j=3; k=1;}.
Puisque ++i est non nul, j++ n'est pas évalué, et donc j garde la valeur 3.
opérateur virgule
L'opérateur virgule évalue d'abord son opérande gauche, puis son opérande droit.
Le résultat de l'opération est l'opérande droit.
Par exemple 23,45 vaut 45.
a=(expr1,expr2); est équivalent à {expr1; a=expr2;}
expr1,expr2; est équivalent à {expr1; expr2;}
opérateur conditionnel ternaire ? :
a=expr1 ? expr2 : expr3; est équivalent à
if(expr1) a=expr2;
else a=expr3;
Pour évaluer l'expression (expr1 ? expr2 : expr3) on évalue d'abord expr1.
Puis on évalue une seule des deux expressions expr2 ou expr3.
Par exemple 2?3:4 vaut 3.
0?3:5 vaut 5.
i=3, j=6, k=2?i++:j++; est équivalent à i=4, j=6, k=3;
instruction do...while
repeat do
a:=a-10; { a-=10;
i:=i+1 i++;
until a<0 } while(a>=0);
instruction for
for(expr1;expr2;expr3) instr
est totalement équivalent à
{ expr1;
while(expr2)
{ instr
expr3;
}
}
Par exemple l'instruction du pascal for i:=1 to 10 do j:=j+i
peut se traduire en C par for(i=1;i<=10;i++) j+=i;
Les trois expressions expr1, expr2 et expr3 peuvent bien sûr contenir des virgules mais pas de point-virgule.
Donc il doit y avoir exactement deux points-virgules entre les parenthèses du for.
Chacune des trois expressions expr1, expr2 et expr3 peut être vide.
Si expr2 est vide, sa valeur est considérée comme toujours vraie, et la boucle se fait donc indéfiniment.
Par exemple
for(;;) printf("coucou\n");
écrit une infinité de lignes contenant toutes coucou.
TD
bonjour
Lancer le programme : démarrer / programme / dev c++
Ecrire le programme
int main()
{ printf("bonjour\n");
getchar();
}
Le sauver sous le nom c:\dev-cpp\dupont\bonjour.c.
Le compiler puis l'exécuter.
Dans le menu : outils / options du compilateur / commande
remplacer «gcc.exe» par «gcc.exe -Wall»
Recompiler et corriger le programme pour supprimer les avertissements (warnings) :
Ajouter #include<stdio.h> en première ligne.
Ajouter return 0; en avant dernière ligne.
3+4=7
Ecrire le programme a3b4.c
#include
int main()
{ int a,b;
a=3;
b=4;
printf("a=%d b=%d\n",a,b);
getchar();
return 0;
}
Le compiler puis l'exécuter.
Le modifier pour qu'il écrive
3+4=7
3*4=12
Remplacer a=3 par printf("Tapez un nombre "); scanf("%d",&a);
Idem pour b.
Mettre deux appels à getchar();
1 4 9 ... 81 100
Ecrire un programme qui affiche les carrés des entiers de 1 à 10 (avec une boucle for).
équation du second degré
Compléter le programme :
#include
#include
int main()
{ double a,b,c,d,x1,x2;
printf("Coefficient de X*X : ");
scanf("%lf",&a);
suivant précédent sommaire
vendredi 21 octobre 2005 : priorité des opérateurs
L'opérateur de multiplication * est prioritaire sur l'opérateur d'addition + :
L'expression 1*3+2 est équivalente à (1*3)+2, elle vaut 5.
L'expression 1+3*2 est équivalente à 1+(3*2), elle vaut 7.
D'une manière générale, les expressions qui contiennent plus de trois niveaux de parenthèses,
sont assez difficiles à lire quand les parenthèses ne se ferment pas toutes au même endroit.
C'est pourquoi il faut connaître la priorité des opérateurs,
cela permet d'éviter de mettre des parenthèses inutiles.
Bien sûr si on a des doutes sur la priorité des opérateurs, il vaut mieux mettre des
parenthèses inutiles que de prendre le risque d'écrire une expression fausse.
Mais dès que possible, il vaut mieux essayer d'enlever ces parenthèses inutiles.
Un programme qui contient trop de parenthèses, montre la prudence de celui qui l'a écrit,
mais aussi son ignorance de la syntaxe du langage.
associativité à droite ou à gauche
La plupart des opérateurs binaires sont associatifs à gauche, cela signifie que lorsque deux opérateurs
binaires de même priorité se suivent, celui de gauche est prioritaire.
Par exemple 4-5-6 est équivalent à (4-5)-6 qui vaut -7 et non pas à 4-(5-6) qui vaut 5.
En C tous les opérateurs binaires sont associatifs à gauche, sauf les opérateurs d'affectation
qui sont associatifs à droite.
Par exemple
a=b=3 est équivalent à a=(b=3) c'est-à-dire à b=3,a=b ou encore à b=3,a=3
a=b=c=2 est équivalent à a=(b=(c=2)) c'est-à-dire à c=2,b=c,a=b ou encore à c=2,b=2,a=2
a+=b*=x+=1 est équivalent à a+=(b*=(x+=1)) c'est-à-dire à x+=1,b*=x,a+=b
opérateur ? :
? et : jouent le rôle de parenthèses.
On peut donc mettre n'importe quelle expression entre eux.
Le ?expr: se comporte syntaxiquement comme un opérateur binaire qui est associatif à droite (comme les opérateurs d'affectation).
Autrement dit expr1?expr2:expr3?expr4:expr5 est équivalent à expr1?(expr2):(expr3?(expr4):expr5).
Par exemple 1?2:3?4:5 vaut 2 alors que (1?2:3)?4:5 vaut 4.
Associativité de la virgule
L'opérateur virgule est associatif :
En effet les deux expressions (expr1,expr2),expr3 et expr1,(expr2,expr3) sont équivalentes car elles ont toutes deux
pour effet d'évaluer dans l'ordre
expr1 puis expr2 puis expr3 et ont pour valeur la valeur de expr3.
On peut donc considérer que la virgule est un opérateur associatif à droite ou à gauche.
priorité des opérateurs en pascal
1) opérateurs unaires postfixes : [indice] (liste_d_arguments) .nom_de_champ ^
2) opérateurs unaires préfixes : @ - not
3) opérateurs multiplicatifs : * mod / div and shl shr
4) opérateurs additifs : + - or xor
9,5) opérateurs de comparaison : < > <= >= = <>
priorité des opérateurs en C
En C on retrouve plus ou moins ces cinq niveaux de priorité dans le même ordre, mais il y en plein d'autres.
1) opérateurs unaires postfixes : [indice] (liste_d_arguments) .nom_de_champ ->nom_de_champ ++ --
2) opérateurs unaires préfixes : & - ~ ! + * (type)
3) opérateurs multiplicatifs : * % /
4) opérateurs additifs : + -
5) opérateurs de décalage : >> <<
6) opérateur et bit à bit : &
7) opérateur ou exclusif bit à bit : ^
8) opérateur ou bit à bit : |
9) opérateurs de relation : < > <= >=
10) opérateurs de comparaison : == !=
11) opérateur et logique : &&
12) opérateur ou logique : ||
13) opérateur conditionnel : ?...:
14) opérateurs d'affectation : = += -= *= /= %= &= ^= |= >>= <<=
15) opérateur virgule : ,
Puisque ? et : jouent le rôle de parenthèses, on peut donc mettre entre eux une affectation ou une virgule.
exemple
a=5,b=a<3?b=5,c=6:j=7,k=8; est équivalent à
(a=5) ,
(b=((a<3)?((b=5),(c=6)):(j=7)))
, (k=8);
ou encore à
a=5;
if(a<3) b=(b=5,c=6);
else b=(j=7);
k=8;
ou encore à
a=5;
if(a<3) {b=5;c=6;b=c;}
else {j=7;b=j;}
k=8;
Puisque a vaut 5, la condition a<3 est fausse.
Donc en fait l'instruction précédente est équivalente à
{a=5; b=j=7; k=8;}
De même en mettant 2 dans a au lieu de 5 on voit que l'instruction
a=2,b=a<3?b=5,c=6:j=7,k=8; est équivalente à
{a=2; b=c=6; k=8;}
TD : tableaux et fonctions
tableaux
Traduire en C le crible d'Erathostène :
pour j=2,3, ... n faire
t[j]:=1
fait
pour j=2,3, ... √n faire
si t[j]=1 alors // si j est premier
pour k=j*j, j*j+j, j*j+2j, ... faire
t[k]:=0
fait
finsi
fait
pour j=2,3, ... n faire
si t[j]=1 alors
afficher j
finsi
fait
Si k est composé, il est le produit de son plus petit diviseur premier j, par un facteur supérieur ou égal à j.
Donc si pour chacun des nombres premiers déjà trouvés, on élimine les nombres produits de ce nombre premier par
un entier plus grand, on est sûr d'éliminer tous les nombres composés, et il ne reste que les nombres premiers.
#include
int main()
{ int n=100000, t[n+1], j,k;
for(j=2;j <=n;j++) t[j]=1;
for(j=2;j*j<=n;j++) if(t[j]) for(k=j*j;k<=n;k+=j) t[k]=0;
for(j=2;j <=n;j++) if(t[j]) printf("%d ",j);
getchar();
getchar();
return 0;
}
fonctions
Compléter le programme :
#include
void aff(int n,int t[n])
{...}
int somme(int n,int t[n])
{...
return s;
}
int main()
{ int n=5, t[]={3,6,9,2,1};
aff(n,t);
printf("La somme vaut %d\n",somme(n,t));
getchar();
getchar();
return 0;
}
suivant précédent sommaire
vendredi 28 octobre 2005 : pointeurs et passage d'arguments
cours : pointeurs
adresse d'une variable
On peut considéder la mémoire de l'ordinateur comme un très grand tableau, dans lequel on réserve des petits morceaux
pour y mettre les variables utilisées par un programme.
L'indice dans ce grand tableau est l'«adresse» de la variable qui s'y trouve.
Par exemple si on déclare
int a,b,c[3];
float d,e[3];
struct {int x,double y[2];} f,g[3];
Les variables a, b, c, d, e, f et g peuvent être rangées consécutivement à partir de l'adresse 1000 :
!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_,_,_,_,_!
1000 1004 1008 1012 1016 1020 1024 1028 1032 1036 1040 1048
a b c[0] c[1] c[2] d e[0] e[1] e[2] f.x f.y[0] f.y[1]
c e f.x f.y
f
!_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_!_,_,_,_,_,_,_,_!_,_,_,_,_,_,_,_!
1056 1060 1068 1076 1080 1088 1096 1100 1108
g[0].x g[0].y[0] g[0].y[1] g[1].x g[1].y[0] g[1].y[1] g[2].x g[2].y[0] g[2].y[1]
g[0].x g[0].y g[1].x g[1].y g[2].x g[2].y
g[0] g[1] g[2]
g
L'expression &a ,l'adresse de a vaut 1000.
De même &c = &c[0] = 1008.
&g=&g[0]=&g[0].x=1056.
pointeur
Un pointeur est une variable qui prend pour valeur l'adresse d'une autre variable.
int *p;
p=&a; // p=1000
*p=3; // *1000=3 i.e. a=3
p=&b; // p=1004
*p=4; // *1004=4 i.e. b=4
p=a; // p=3 est interdit car 3 est un entier ce n'est pas l'adresse d'un entier
*p=&a // *1004=1000 i.e. b=1000 est interdit car *p (c'est-à-dire b) est un entier et &a n'est pas un entier, c'est l'adresse d'un entier
Les opérateurs unaires préfixes * (indirection) et & (adresse) sont l'inverse l'un de l'autre.
& transforme une variable en son adresse, * appliqué à l'adresse d'une variable donne cette variable.
Par exemple &a vaut 1000 et *1000 est a.
Autrement dit *&a est la variable a tandis que &*p est la valeur de p.
Dans les lignes précédentes il y a une différence entre «est» et «vaut» :
&a est une expression qui a pour valeur l'adresse de a. On ne peut pas changer l'adresse de a.
Cette expression peut être le membre droit d'une affectation, mais pas le membre gauche.
J'appelle cela une valeur (On appelle cela aussi une rvalue).
*p est la variable pointée par p, c'est la variable dont l'adresse est rangée dans p.
C'est bien une variable, on peut la mettre comme membre gauche d'une affectation (On appelle cela aussi une lvalue).
*&a et a sont deux expressions qui représentent la même variable.
p et &*p sont deux expressions qui ont la même valeur, mais p est une variable alors que &*p n'est pas une variable.
syntaxe des déclarations de pointeur
La déclaration int *p,a,*q;
signifie que les trois expressions *p, a et *q représentent des entiers.
Donc a est un entier et p et q sont des pointeurs sur des entiers.
TD : passage d'argments
Ecrire des procédures qui :
échange deux entiers
échange deux entiers s'ils ne sont pas dans l'ordre croissant
permute trois entiers pour les mettre dans l'ordre croissant
calcule la surface et le périmètre d'un cercle, à partir du rayon
#include
#include
void ech(int *aa,int *bb)
{ int c;
c=*aa, *aa=*bb, *bb=c;
}
void tri2(int *aa,int *bb)
{ if(*aa>*bb) ech(aa,bb);
}
void tri3(int *aa,int *bb,int *cc)
{ tri2(aa,bb);
tri2(bb,cc);
tri2(aa,bb);
}
void cercle(double rayon,double *perimetre,double *surface)
{ *perimetre=2*rayon*M_PI;
*surface=rayon*rayon*M_PI;
}
int main()
{ double r,c,s;
int i,j,k;
printf("Donnez trois nombres : ");
scanf("%d%d%d",&i,&j,&k);
printf("%d %d %d\n",i,j,k);
tri3(&i,&j,&k);
printf("%d %d %d\n",i,j,k);
printf("Quel est le rayon du cercle ? ");
scanf("%lf",&r);
cercle(r,&c,&s);
printf("La circonférence est %20.15lf\n",c);
printf("La surface est %20.15lf\n",s);
getchar();
getchar();
return 0;
}
suivant précédent sommaire
vendredi 4 novembre 2005 : pointeurs et tableaux
cours
passage d'arguments
Avec les procédures du programme précédent, on éxécute instruction par instruction le bout de programme :
a=4, b=3;
tri2(&a,&b);
!_,_,_,4!_,_,_,3!
2000 2004
a b
On exécute tri2(2000,2004);
!_,_,_,4!_,_,_,3! !_,_2000!_,_2004!
2000 2004 2020 2024
a b aa bb
tri2(&a,&b)
*aa>*bb donne *2000>*2004 c'est-à-dire 4>3 qui est vrai.
Donc on exécute ech(aa,bb);
!_,_,_,4!_,_,_,3! !_,_2000!_,_2004! !_,_2000!_,_2004!_,_,_,_!
2000 2004 2020 2024 2040 2044 2048
a b aa bb aa bb c
tri2(&a,&b) ech(aa,bb)
c=*aa; donne c=*2000 c'est-à-dire c=a donc c=4
*aa=*bb; donne *2000=*2004 c'est-à-dire a=b donc a=3
*bb=c; donne *2004=c c'est-à-dire b=4
!_,_,_,3!_,_,_,4! !_,_2000!_,_2004! !_,_2000!_,_2004!_,_,_,4!
2000 2004 2020 2024 2040 2044 2048
a b aa bb aa bb c
tri2(&a,&b) ech(aa,bb)
tableaux et pointeurs
En C l'opérateur [] s'applique à un tableau ou à un pointeur, mais une référence à un tableau à une dimension
se comporte comme un pointeur sur son premier élément :
Si t est un tableau à une dimension, les deux expressions t et &t[0] sont équivalentes. Donc de même *t et t[0] sont équivalents.
tableau passé en argument
Dans une liste d'arguments de fonction, les trois déclarations int *t, int t[] et int t[4] sont donc équivalentes.
De plus les tableaux sont toujours passés par référence en C, ils ne sont jamais recopiés lors d'un appel de fonction.
opérations sur les pointeurs
t+3 est équivalent à &t[3].
Donc de même *(t+3) est équivalent à t[3]
On peut soustraire deux pointeurs pour obtenir un entier.
(t+5)-(t+3) vaut 2
TD
Compléter les procédures :
int maxt(int n,int t[n])
int posmax(int n,int t[n])
void tri(int n,int t[n])
void fusion(int n,int m,int t[n],int u[m], int v[n+m])
maxt cherche le plus grand des éléments d'un tableau et posmax cherche sa position.
tri permute les éléments d'un tableau en les mettant dans l'ordre croissant,
en échangeant le plus grand élément avec le dernier et en recommençant en considérant qu'il y a un élément de moins dans le tableau.
fusion fusionne les deux tableaux triés t et u en un tableau trié v, en prenant le plus petit des deux premiers éléments de t et u,
en l'enlevant de ce tableau pour le mettre dans v et en recommençant.
#include
int maxt(int n,int t[n])
{ int m=t[0], i;
for(i=1;im) m=t[i];
return m;
}
int posmax(int n,int t[n])
{ int i,j=0;
for(i=1;it[j]) j=i;
return j;
}
void tri(int n,int t[n])
{ for(;n;n--)
{ int j=posmax(n,t), x=t[j];
t[j]=t[n-1];
t[n-1]=x;
}
}
void fusion(int n,int m,int t[n],int u[m], int v[n+m])
{ int i=0,j=0;
while(i1)
{ int m=n/2, *u=t+m, v[n];
trifusion(m ,t);
trifusion(n-m,u);
fusion(m,n-m,t,u,v);
for(m=0;m
suivant précédent sommaire
vendredi 18 novembre 2005 : structures, pointeurs, malloc et listes chaînées
déclaration de structure
pascal vs C, première version sans déclaration de type
var a,b:record x,y:integer end; struct {int x,y;} a,b;
déclare deux structures identiques a et b qui ont chacune deux champs de type entier appelés x et y.
On a donc quatre variables entières a.x, a.y, b.x, b.y.
pascal vs C, deuxième version avec déclaration de type
pascal C de Kernighan et Ritchie C ansi
type xy=record x,y:integer end; struct xy {int x,y;}; typedef struct {int x,y;} xy;
var a,b:xy; struct xy a,b; xy a,b;
En pascal le type est "xy" en C de K&R c'est "struct xy".
Le typedef du C ansi permet de se rapprocher du pascal.
syntaxe de la déclaration struct
Une déclaration de structure en C comporte dans l'ordre :
le mot "struct"
un nom de structure
une liste de déclarations de champs entre accolade
une liste de variables séparées par des virgules
un point-virgule.
Par exemple
struct xy{int x,y;} a,b;
Chacune des trois zones en couleur est facultative.
Par exemple la ligne précédente est équivalente à
struct xy{int x,y;};
struct xy a,b;
Elle aurait pu aussi s'écrire
struct {int x,y;} a,b;
si on n'avait pas voulu définir le type "struct xy".
On peut même écrire :
struct truc;
qui signifie que truc est le nom d'une structure, dont on précisera plus tard le contenu.
pointeurs sur des structures
En C comme en pascal, on a le droit de déclarer des pointeurs sur une structure dont on ne connaît pas encore la composition.
Cela ne pose pas de problème au compilateur, puisque les pointeurs ont tous quatre octets. Leur taille ne dépend pas
de la taille de la structure pointée.
Par exemple :
type ptruc=^truc; typedef struct truc *ptruc;
truc=record a:integer; b:double; c:ptruc end; struct truc{int a;double b;ptruc c;};
var p,q:ptruc; ptruc p,q;
En C K&R (donc sans typedef) on aurait écrit :
struct truc{int a;double b;struct truc *c;};
struct truc *p,*q;
allocation de mémoire
malloc et free vs allocation automatique dans un bloc
{ int t[n]; { int *t; t=malloc(n*sizeof(*t));
... ...
free(t);
} }
sont équivalents.
rappel : différence entre déclaration et passage en argument de pointeurs et de tableaux
Dans les déclarations en début de bloc, int t[n]; n'est pas équivalent à int *t; .
La première déclaration réserve la place pour n entiers à chaque fois que l'on rentre dans ce bloc et libère cette place quand on sort
de ce bloc. La deuxième réserve la place pour un pointeur (4 octets) dans lequel on devra mettre l'adresse d'un tableau
(par exemple un tableau créé par malloc, qu'il faudra détruire par free quand on n'en aura plus besoin).
Par contre dans une liste d'argument de fonction, ,int t[n], est équivalent à ,int t[], ou a ,int *t, . Aucun tableau n'est créé.
L'adresse du tableau passé en argument est rangée dans le pointeur t.
listes chaînées
Une liste chaînée est une suite de chaînons contenant chacun un élément de la liste et un pointeur sur le chaînon suivant.
Le dernier chaînon contient le pointeur nul.
Autrement dit, une liste chaînée est représentée par un pointeur sur un chaînon, qui est
soit le pointeur nul, si la liste est vide,
soit un pointeur sur le premier chaînon qui contient le premier élément de la liste et un pointeur représentant le reste de la liste.
Le programme suivant crée la liste (4,5,2) et appelle une procédure qui affiche cette liste.
typedef struct chainon *liste; // liste= pointeur sur (struct chainon)
struct chainon {int val; liste suite;};
void aff(liste a) // version récursive
{ if(a) printf(" %d",a->val), aff(a->suite); // Pour afficher une liste non vide, on affiche son premier élément, puis le reste.
else printf("\n"); // Pour afficher une liste vide, on passe simplement à la ligne
}
void aff2(liste a) // version iérative
{ for(;a;a=a->suite;) printf(" %d",a->val); // Tant que la liste est non vide, on affiche son premier élément, et on passe à la suite.
printf("\n"); // A la fin de la liste on passe à la ligne
}
int main()
{ liste p,q,r;
p=malloc(sizeof(*p));
q=malloc(sizeof(*q));
r=malloc(sizeof(*r));
p->val=4; p->suite=q;
q->val=5; q->suite=r;
r->val=2; r->suite=0;
aff(p);
aff2(p);
return 0;
}
Dans le programme précédent on aurait pu ne pas mettre la définition du type liste typedef struct chainon *liste;
et remplacer partout liste par struct chainon *.
Par exemple, au lieu de void aff(liste a) on peut écrire void aff(struct chainon *a).
Au lieu de liste p,q,r; on peut écrire struct chainon *p,*q,*r;.
suivant précédent sommaire
vendredi 25 novembre 2005 : liste chaînée : création et parcours
Dans le programme principal précédent, on fait des choses assez similiaires avec p, q et r :
p=malloc(sizeof(*p)); p->val=4; p->suite=q; peut être remplacé par l'appel de fonction
p=nouv(4,q);.
Si on ajoute la fonction
liste nouv(int val,liste suite)
{ liste a;
a=malloc(sizeof(*a));
a->val=val;
a->suite=suite;
return a;
}
le programme principal devient
int main()
{ liste p,q,r;
r=nouv(2,0);
q=nouv(5,r);
p=nouv(4,q);
aff(p);
aff2(p);
return 0;
}
On peut éliminer les variables q et r de ce programme de deux manières :
int main()
{ liste p;
p=nouv(4,nouv(5,nouv(2,0))));
aff(p);
aff2(p);
return 0;
}
int main()
{ liste p=0;
p=nouv(2,p);
p=nouv(5,p);
p=nouv(4,p);
aff(p);
aff2(p);
return 0;
}
La première manière est plus compacte est montre clairement qu'on fabrique la liste (4 5 2).
La deuxième suggère de mettre p=nouv(...,p); dans une boucle :
int main()
{ liste p=0;
int t[]={2,5,4}, i;
for(i=0;i<3;i++) p=nouv(t[i],p);
aff(p);
aff2(p);
return 0;
}
TD
Dans la version récursive de l'affichage d'une liste chaînée mettre l'affichage du premier élément après
l'appel récursif qui affiche le reste de la liste. On obtient ainsi une procédure qui affiche la liste à l'envers.
Ecrire une fonction itérative qui calcule la somme des éléments d'une liste chaînée.
Elle ressemblera à la procédure itérative d'affichage, dans laquelle on remplace l'écriture de
a->val par s+=a->val;.
Ecrire une fonction récursive qui calcule la somme des éléments d'une liste chaînée.
Cette somme est nulle si la liste est vide.
Sinon c'est la somme du premier élément et de la somme du reste de la liste.
Ecrire une fonction récursive qui fabrique une copie d'une liste chaînée avec de nouveaux chaînons.
La copie d'une liste vide est la liste vide.
La copie d'une liste non vide est un nouveau chaînon qui contient la même valeur que le premier chaînon de l'originale et
la copie de la suite de ce premier chaînon.
Ecrire une fonction itérative qui fait une copie à l'envers d'une liste chaînée.
Elle ressemblera à la procédure itérative d'affichage, dans laquelle on remplace l'écriture de
a->val par p=nouv(a->val,p);.
#include
#include
typedef struct chainon *liste; // liste= pointeur sur (struct chainon)
struct chainon {int val; liste suite;};
void aff(liste a) // version récursive
{ if(a) printf(" %d",a->val), aff(a->suite); // Pour afficher une liste non vide, on affiche son premier élément, puis le reste.
else printf("\n"); // Pour afficher une liste vide, on passe simplement à la ligne
}
void aff2(liste a) // version iérative
{ for(;a;a=a->suite) printf(" %d",a->val); // Tant que la liste est non vide, on affiche son premier élément, et on passe à la suite.
printf("\n"); // A la fin de la liste on passe à la ligne
}
liste nouv(int val,liste suite)
{ liste a;
a=malloc(sizeof(*a));
a->val=val;
a->suite=suite;
return a;
}
void ffa(liste a)
{ if(a) ffa(a->suite), printf(" %d",a->val);
}
int sommeit(liste a)
{ int s=0;
for(;a;a=a->suite) s+=a->val;
return s;
}
int sommere(liste a)
{ if(a) return a->val+sommere(a->suite);
else return 0;
}
liste copie(liste a)
{ if(a) return nouv(a->val,copie(a->suite));
else return 0;
}
liste eipoc(liste a)
{ liste p=0;
for(;a;a=a->suite) p=nouv(a->val,p);
return p;
}
int main()
{ liste p=0,q,r;
int t[]={2,5,4}, i;
for(i=0;i<3;i++) p=nouv(t[i],p);
aff(p);
aff2(p);
ffa(p);
printf("\n somme : %d = %d\n",sommeit(p),sommere(p));
q=copie(p);
r=eipoc(p);
aff(q);
aff(r);
return 0;
}
suivant précédent sommaire
vendredi 2 décembre 2007 : manipulations élémentaires de listes chaînées
parcours
for(p=l;p;p=p->suite) ...p->val...
L'opération faite sur chaque élément de la liste peut être par exemple un affichage à l'écran : printf(" %d",a->val);
ou le calcul de la somme : s+=a->val;
ou le comptage des chaînons nb++;
ou une modification de la valeur rangée dans la liste comme a->val++;
ajout d'un élément en tête de liste
p=nouv(23,p); ajoute un chaînon contenant 23 en tête de la liste p.
suppression d'un élément en tête de liste
q=p;
p=q->suite;
free(q);
enlève le premier chaînon de la liste p, mais q pointe sur ce chaînon, ce qui permet de libérer la place qu'il utilisait.
free(q); ne marche que si ce chaînon avait été créé par un appel à malloc, éventuellement fait par un appel à nouv.
combinaisons de ces manipulations élémentaires
parcours et insertion en tête de liste : copie à l'envers
l2=0;
for(p=l1;p;p=p->suite) l2=nouv(p->val,l2);
Si par exemple la liste l1 contient (3 5 4), alors la boucle sera faite trois fois et p->val vaudra 3 puis 5 puis 4.
La liste l2 contiendra successivement () puis (3) puis (5 3) et enfin (4 5 3).
La liste l1 n'est pas modifiée, elle contient toujours (3 5 4).
La liste l2 est une copie de l1 à l'envers avec de nouveau chaînons.
détachement en tête de liste et insertion en tête de liste : retournement d'une liste avec les mêmes chaînons
l2=0;
for(;l1;l1=l1->suite) p=l1, l1=p->suite, // on détache le premier chaînon p de l1
p->suite=l2, l2=p; // on l'ajoute en tête de l2
Les valeurs successives de l1 et l2 sont par exemple :
(4 7 8 2) ()
(7 8 2) (4)
(8 2) (7 4)
(2) (8 7 4)
() (2 8 7 4)
On détache un par un les chaînons en tête de l1, pour les mettre en tête de l2.
A la fin l2 contient les chaînons de l1, chaînés à l'envers.
copie d'une liste chaînée
Il y a deux façons de fabriquer une copie d'une liste chaînée :
De manière itérative avec deux boucles : on fabrique une liste à l'envers, que l'on retourne ensuite.
De manière récursive : La copie d'une liste non vide est un nouveau chaînon contenant la même valeur que le premier chaînon
de liste originale et un pointeur représentant la copie de la suite du premier chaînon.
TD
Ecrire une fonction pour chacune des opérations vues en cours.
void ajoute(int v,liste *p) { *p=nouv(v,*p); }
int enleve(liste *l)
{ liste p=*l; // adresse du premier chaînon
int v=p->val; // valeur du premier chainon
*l=p->suite; // la liste commence maintenant au deuxième chainon
free(p); // on libère la place du premier chaînon
return v; // valeur qui était rangée dans le premier chaînon
}
liste eipoc(liste a)
{ liste p=0;
for(;a;a=a->suite) p=nouv(a->val,p);
return p;
}
liste retourne(liste l1)
{ liste l2=0, p;
for(;l1;l1=l1->suite) p=l1, l1=p->suite, // on détache le premier chaînon p de l1
p->suite=l2, l2=p; // on l'ajoute en tête de l2
return l2;
}
liste copieit(liste l) { return retourne(eipoc(l)); }
liste copie(liste a)
{ return a ? nouv(a->val,copie(a->suite)) : 0;
}
suivant précédent sommaire
vendredi 9 décembre 2005 : insertion triée, fusion et trifusion
copie d'une liste avec une seule boucle qui insère en fin de liste
liste copie2(liste a)
{ if(!a) return a; // liste vide
{ liste p=nouv(a->val,0), d=p; // premier et dernier chainons de la copie
for(;a=a->suite,a;) d=d->suite=nouv(a->val,0); // insertion d'un nouveau chaînon au bout
return p;
}
}
On peut aussi partir de la fonction récursive :
liste copie(liste a)
{ liste b;
if(a) b=malloc(sizeof(*b)), *b=*a, b->suite=copie(b->suite);
return b;
}
la transformer en une procédure récursive :
void copier(liste *aa)
{ liste b;
if(*aa) b=malloc(sizeof(*b)), *b=**aa, *aa=b, copier(&b->suite);
}
puis la dérécursiver :
void copier(liste *aa)
{ liste b;
while(*aa) b=malloc(sizeof(*b)), *b=**aa, *aa=b, aa=&b->suite;
}
et enfin la transformer en une fonction itérative :
liste copie(liste a)
{ liste b,*aa;
for(aa=&a;*aa;aa=&b->suite) b=malloc(sizeof(*b)), *b=**aa, *aa=b;
return a;
}
insertion dans une liste triée
On pourra tester la procédure d'insertion dans une liste triée avec les instructions suivantes
l=0;
for(j=0;j<10;j++) l=instri(j*3%10,l),aff(l);
qui insèrent dans une liste initialement vide les nombres 0 3 6 9 2 5 8 1 4 7.
A la fin liste doit être (0 1 2 3 4 5 6 7 8 9).
fonction récursive
Si la liste est vide ou que l'élément à insérer est plus petit que le premier élément, alors on insère l'élément en tête de liste,
sinon on l'insère dans la suite;
liste instrirec(int v,liste a)
{ if(a && a->valsuite=instrirec(v,a->suite);
else a=nouv(v,a);
return a;
}
procédure récursive
On passe maintenant l'adresse de la variable de type liste :
void instrirecp(int v,liste *aa)
{ if(*aa && (*aa)->valsuite);
else *aa=nouv(v,*aa);
}
procédure itérative
On peut dérécursiver la procédure précédente :
void instriitep(int v,liste *aa)
{ while(*aa && (*aa)->valsuite;
*aa=nouv(v,*aa);
}
fonction itérative
On peut tranformer la procédure précédente en une fonction :
liste instriite(int v,liste a) { instriitep(v,&a); return a; }
liste instriite(int v,liste a)
{ liste *aa=&a;
while(*aa && (*aa)->valsuite;
*aa=nouv(v,*aa);
return a;
}
fusion de deux listes triées
On veut écrire une procédure qui part de deux listes chaînées triées dans l'ordre croissant et les fusionne en une liste triée,
en gardant les mêmes chainons.
Il ne faut donc pas appeler malloc ni nouv.
Si une des deux listes est vide on rend l'autre.
Sinon on peut comparer le premier élément de chacune des deux listes.
Si par exemple le premier élément de a est plus petit que celui de b, alors ce sera le premier élément du résultat de la fusion, et il sera suivi
du résultat de la fusion de b et de la suite des éléments de a.
version récursive :
liste fusion(liste a,liste b)
{ if(!a) return b;
if(!b) return a;
if(b->val>a->val) { a->suite=fusion(a->suite,b); return a; }
else { b->suite=fusion(b->suite,a); return b; }
}
version itérative :
liste fusion(liste a,liste b)
{ liste c, *cc=&c;
while(a && b) if(b->val>a->val) *cc=a, cc=&a->suite, a=*cc;
else *cc=b, cc=&b->suite, b=*cc;
*cc= a ? a : b;
return c;
}
tri fusion
Pour trier une liste chaînée, on la sépare en deux parties de mêmes longueurs.
Si ces deux parties sont non vides, on les trie (récursivement) et on les fusionne.
p=(); q=()
tant que a n'est pas vide faire
on enlève le premier chaînon de a et on l'ajoute en tête de q.
on échange p et q
fait
si q est vide alors
// p a au plus un élément et est donc trié
on rend p
sinon
on trie p
on trie q
on fusionne ces deux listes triées
on rend le résultat de la fusion
finsi
liste trifusion(liste a)
{ liste p=0, q=0, r;
while(a) r=a, a=r->suite, r->suite=q, q=p, p=r;
return q ? fusion(trifusion(p),trifusion(q)) : p;
}
q p a
() () (7 4 8 3)
() (7) (4 8 3)
(7) (4) (8 3)
(4) (8 7) (3)
(8 7) (3 4) ()
suivant précédent sommaire
vendredi 16 décembre 2005 : pointeur sur un pointeur
cours
Exécution pas à pas de la procédure de fusion itérative précédente :
liste fusion(liste a,liste b)
{ liste c, *p=&c;
while(a && b) if(b->val>a->val) *p=a, p=&a->suite, a=*p;
else *p=b, p=&b->suite, b=*p;
*p= a ? a : b;
return c;
}
appliquée au deux listes a=(4 6) et b=(5 7).
La boucle while est faite trois fois.
!_,_,_,_! !_&M4_,_! !_,_,_,4!_&M6_,_! !_,_,_,6!_,_,_,0! // a=(4 6) c=*p
c a M4 M6
!_&c,_,_! !_&M5_,_! !_,_,_,5!_&M7_,_! !_,_,_,7!_,_,_,0! // b=(5 7)
p b M5 M7
Première itération*p=a, p=&a->suite, a=*p;
c=&M4, p=&M4.suite, a=&M6;
!_,&M4,_! !_&M6_,_! !_,_,_,4!_&M6_,_! !_,_,_,6!_,_,_,0! // a=(6) c=(4 ...) p=&M4
c a M4 M6
!&M4.sui! !_&M5_,_! !_,_,_,5!_&M7_,_! !_,_,_,7!_,_,_,0! // b=(5 7)
p b M5 M7
Deuxième itération *p=b, p=&b->suite, b=*p;
M4.suite=&M5, p=&M5.suite, b=&M7;
!_,&M4,_! !_&M6_,_! !_,_,_,4!_&M5_,_! !_,_,_,6!_,_,_,0! // a=(6) c=(4 5 ...) p=&M5
c a M4 M6
!&M5.sui! !_&M7_,_! !_,_,_,5!_&M7_,_! !_,_,_,7!_,_,_,0! // b=(7)
p b M5 M7
Troisième itération *p=a, p=&a->suite, a=*p;
M5.suite=&M6, p=&M6.suite, a=0;
!_,&M4,_! !_,_,_,0! !_,_,_,4!_&M5_,_! !_,_,_,6!_,_,_,0! // a=() c=(4 5 6 ...) p=&M6
c a M4 M6
!&M6.sui! !_&M7_,_! !_,_,_,5!_&M6_,_! !_,_,_,7!_,_,_,0! // b=(7)
p b M5 M7
*p= a ? a : b;
M6.suite= &M7;
!_,&M4,_! !_,_,_,0! !_,_,_,4!_&M5_,_! !_,_,_,6!_&M7_,_! // c=(4 5 6 7)
c a M4 M6
!&M6.sui! !_&M7_,_! !_,_,_,5!_&M6_,_! !_,_,_,7!_,_,_,0!
p b M5 M7
return c;
return &M4; // (4 5 6 7)
TD
Compléter les fonctions suivantes :void separe(liste a,int seuil,liste *p,liste *q)
// *p={x dans A | x< seuil}
// *q={x dans A | x>=seuil}
void separen(liste a,int n liste t[n])
// t[j]={x∈ | x mod n=j}
liste raboute(int n,liste t[n])
On peut faire deux versions de ces fonctions.
Dans la première on construit les listes itérativement dans le même sens en utilisant des pointeurs sur des listes.
void separe1(liste a,int seuil,liste *p,liste *q)
// *p={x dans A | x< seuil}
// *q={x dans A | x>=seuil}
{ for(;a;a=a->suite) if(a->valsuite;
else *q=b, q=&b->suite;
*p=*q=0;
}
void separen1(liste a,int n liste t[n])
// t[j]={x∈ | x mod n=j}
{ liste *p[n];
int j;
for(j=0;jsuite) j=a->val%n, *p[j]=a, p[j]=&a->suite;
for(j=0;jsuite) ;
*p=0;
return c;
}
On peut aussi fabriquer des listes retournées en détachant les chaînons en tête d'une liste pour les insérer en tête d'une autre liste.
Dans cette version on n'a pas vraiment besoin de pointeur sur des listes.
void separe2(liste a,int seuil,liste *p,liste *q)
// *p={x dans A | x< seuil}
// *q={x dans A | x>=seuil}
{ *p=*q=0;
for(;a;a=a->suite)
{ liste r=a; a=r->suite;
if(r->valsuite=*p, *p=r;
else r->suite=*q, *q=r;
}
}
void separen2(liste a,int n liste t[n])
// t[j]={x∈ | x mod n=j}
{ int j;
for(j=0;jsuite) {liste r=a; a=r->suite, r->suite=t[j=r->val%n], t[j]=r;}
}
liste raboute2(int n,liste t[n])
{ liste c=0,p,r;
int i;
for(i=0;isuite, r->suite=c, c=r;
return c;
}
suivant précédent sommaire
vendredi 13 janvier 2006 : contrôle
Qu'écrit le programme suivant quand on l'exécute ?
#include
void pr(int a,int b)
{ if(b>0) pr(a+3,b-1),pr(a+2,b-2),pr(a+4,b-1);
else printf("%4d",a);
}
void pr2(int a,int b)
{ printf("%4d",a);
while(b) pr2(a++,--b);
}
#define a 5
#define b 6
int main()
{ int i,j,k,l,*p=&i,*q=&j,*r;
printf("%4d %4d %4d %4d\n",a&b,a|b,a^b,(a,b));
printf("%4d %4d %4d %4d\n",-a,~a,!a,!!a);
printf("%4d %4d %4d %4d\n",i=a,j=b,k=a+b,l=a-b);
printf("%4d %4d %4d %4d\n",++i*j--,k+=l--,a<<2,a>>2);
printf("%4d %4d %4d %4d\n",++i*j--,k+=l--,a<<2,a>>2);
r=p,p=q,q=r;
i=a,j=b;
printf("%4d %4d %4d %4d\n",i,j,*p,*q);
j+=i+=*q, j+=i+=*q;
printf("%4d %4d %4d %4d\n",i,j,*p,*q);
pr (a,3); printf("\n");
pr2(a,3); printf("\n");
return 0;
}
Compléter les deux procéduresliste select(liste a,int masque);
liste tri(liste a);
qui permutent les chaînons d'une liste chaînée.
select(a,masque) met en tête tous les chaînons dont la clé v vérifie v\&masque==0.
Tous les autres chaînons suivent.
Les chaînons gardent l'ordre de la liste initiale s'il le peuvent.
tri() trie son argument dans l'ordre croissant des clés en utilisant select.
précédent sommaire
vendredi 20 janvier 2006 : corrigé du contrôle
4 7 3 6
-5 -6 0 1
5 6 11 -1
36 10 20 1
35 8 20 1
5 6 6 5
20 36 36 20
14 13 15 10 15 14 16 10 9 11 15 14 16 11 16 15 17
5 5 5 5 6 6 6 7
a=5 101
b=6 110
a&b=4 100
a|b=7 111
a^b=3 011
a<<2=20 10100
a>>2=1 1
(a,b)=(5,6)=6, -a=-5, ~a=~5=-5-1=-6, !a=!5=0, !!a=!0=1.
i=a, j=b, k=a+b, l=a-b valent 5, 6, 11 et -1 et rangent ces quatre valeurs dans i, j, k et l.
++i fait passer i de 5 à 6 et vaut la nouvelle valeur de i : 6.
j-- fait passer j de 6 à 5 et vaut l'ancienne valeur de j : 6.
++i*j-- vaut donc 6*6=36.
l-- fait passer l de -1 à -2 et vaut l'ancienne valeur de j : -1.
k+=-1 fait passer k de 11 à 10 et vaut 10.
++i fait passer i de 6 à 7 et vaut la nouvelle valeur de i : 7.
j-- fait passer j de 5 à 4 et vaut l'ancienne valeur de j : 5.
++i*j-- vaut donc 7*5=35.
l-- fait passer l de -2 à -3 et vaut l'ancienne valeur de j : -2.
k+=-2 fait passer k de 10 à 8 et vaut 8.
Avant r=p,p=q,q=r; les variables valent i( 5 ), j( 6 ), p( &i ), q( &j ).
r=p,p=q,q=r; échange les valeurs de p et q. Les variables valent i( 5 ), j( 6 ), p( &j ), q( &i ).
j+=i+=*q est équivalent à j+=i+=i c'est-à-dire à i=i+i, j=j+i.
La première exécution change i en 5+5=10 et j en 6+10=16.
La deuxième exécution change i en 10+10=20 et j en 16+20=36.
L'appel de procédure pr (a,3); écrit 17 nombres.
L'appel de procédure pr2(a,3); écrit 8 nombres.
#include
#include
typedef struct chainon *liste;
struct chainon{int val; liste suite;};
void aff(liste a)
{ for(;a;a=a->suite) printf("%d ",a->val);
printf("\n");
}
liste selectm(liste a,int masque)
{ liste p=0, q=0, r;
while(a) if(a->val&masque) r=a, a=r->suite, r->suite=p, p=r;
else r=a, a=r->suite, r->suite=q, q=r;
while(p) r=p, p=r->suite, r->suite=a, a=r;
while(q) r=q, q=r->suite, r->suite=a, a=r;
return a;
}
liste tri(liste a)
{ int masque;
for(masque=1;masque;masque<<=1) a=selectm(a,masque);
return a;
}
liste nouv(int val,liste suite)
{ liste a=malloc(sizeof(*a));
a->val=val;
a->suite=suite;
return a;
}
int main()
{ liste a=0;
int i;
for(i=0;i<10;i++) a=nouv(i*3%10,a);
aff(a);
a=tri(a);
aff(a);
return 0;
}