Fonctions
Les fonctions permettent d’encapsuler du code dans une region précise du programme pour y référer ensuite par leur nom.
Un appel de fonction consiste à l’écrire et à lui passer les paramètres voulus
entre parenthèses. Cette systaxe est proche de la notation mathématique d’un
appel de fonction a = f(x)
, de la même manière la description du d’une fonction
emprunte à
notation d’un ensemble en extension
🌍⤴
où chaque élément serait séparé par des points-virgules.
La comparaison s’arrête ici car en C toutefois les instructions sont ordonnées
et peuvent être dupliquées ce qui les rapprocherait davantage des
uplets
🌍⤴
.
Les fonctions avec un nombre d’arguments inférieur ou égale à 4 sont optimisées de sorte que les paramètres soient passés par les registres du processeur. Lorsqu’une fonction est suffisamment petite elle peut être intégrée dans le code qui l’appellé (inlinée).
Les fonctions longues et non inlinables génèrent un cout elevé en terme d’exécution de l’ordre de plusieurs magnitudes, à cause du saut, de la mise en mémoire du lieu d’appel, etc.
Une suite récursive d’appels de fonction expose à un stack overflow si l’algorithme n’est pas logarithmique.
Par héritage avec le C de K&R, une fonction ne déclarant rien entre ses parenthèses prend un nombre inspécifié d’arguments, aujourd’hui on recommande d’écrire une telle fonction de la sorte
int function(...) {}
Les trois points, ou ellipse, indiquent que cette fonction accepte un nombre indéterminé de paramètres , jusqu’a une limite définie par l’implémentation.
Dans la fonction main l’instruction return n’est pas nécessaire, une fonction
main lorsqu’elle se termine retourne la valeur qui correspond à une exécution
normale du programme (généralement celle-ci est zero : 0
). Une valeur
différente de zero correspond à une erreur, la valeur exacte donne
des précisions sur l’erreur en question. Chaque systeme possède ses propres
valeurs d’erreur, mais une valeur zero comme garante de succès est généralement
normal et protable, mais par portabilité maximale il est préférable de na pas
la mettre du tout.
Les deux fonctions main qui suivent sont identiques.
int main(void) {
return 0;
}
int main(void) {
}
En C la fonction main() peut s’appeller elle-même, cela est interdit en C++.
todo: bien expliquer la recursion terminale, pourquoi le tri fusion ne peut se faire avec
Un tableau lorsqu’il est évalué est converti en pointeur de type de ce qu’il contient, ainsi passer un tableau en parametre de fonction consiste à passer un pointeur sur celui-ci.
en parler dans la section tableau
La fonction ne connait rien du tableau originel hormis son type et son adresse, l’opérateur sizeof
sur le pointeur renverra la taille d’un pointeur générique et non la taille du tableau originel, la fonction ne peut donc pas connaitre la taille du tableau et il faut soit la passer comme paramètre supplémentaire ou bien mettre un avaleur sentinelle dans celui-ci, comme c’est le cas des chaines de caractère.
unsigned len1(char*tab)
int main(void) {
int tab[] = {'t', 'o', 't', 'o', '\0'};
len(tab);
len(&tab); // identique
}
danger de retourner un ppinteur sur une variable interne
Contrairement aux tableaux, les structures sont passées aux fonctions en tant que valeurs copiées et non comme pointeurs, modifier une structure dans une fonction est donc inutile et il est indispensable de la passer comme pointeur.
struct toto {
int a;
char b[5];
struct toto c;
}
void fun(struct toto * toto) {
(*toto).a = 2;
toto->a = 2; // identique
(*(*toto).b) = 'b';
toto->b[0] = 'b' // identique
(*(*(*toto).c).b) = 'd';²
toto->c->b[0] = 'd'; // identique
}
int main(void) {
struct toto toto ={1, "toto", {2, "tata"}};
fun(&toto);
printf("%d\n", toto.a); // 2
printf("%s\n", toto.b); // boto
printf("%s", toto.c.b); // doto
}
todo alleger exemple ci dessus
recursion terminale
passage de fonctions en appro
todo : parler de variables globales en appro
parler vatiables statiqies en appro
La fonction main()
est appellée par le système d’exploitation et sa signature pourrait être ce qu’on veut, la norme spécifie deux signatures qui assurent une compatibilité maximale avec la pluspart des systèmes
int main(void) {}
int main(int argc, char*argv[]) {}
La première forme ignore les paramètres fournis a la fonction, la deuxieme permet au programme d’accéder aux paramètres d’appel au programme.
Imaginons que le programme toto.exe
soit dans le dossier /home
et que nous l’executions ainsi /home/toto.exe titi tata
alors argc
(arguments count) vaudra 3
: ce qui correspond au nombre d’arguments fournis au programme plus un, argv
(arguments values) est un tableau de pointeurs vers les elemenrs de la commande d’appel au programme : ici argv[0]
vaut /home/toto
, argv[1]
vaut titi
, argv[2]
pointe sur tata
et argv[3]
est un pointeur nul.
Il y a deux facons d’afficher les arguments de notre programme
int main(int argc, char*argv[]) {
printf("%d", argc); // 3
for(int i = 0; i < arg; i++) {
prinf("%s", argv[i]);
}
char **ptr = argv;
while(*ptr) {
printf("%s", **ptr);
(*ptr)++;
}
}