Notions de base sur le langage C : cours complet avec des exemples

1 - Types de données et déclarations
2 - Les structures
3 - Tableaux et chaînes de caractères
4 - Tests conditionnels

5 - Boucles d'itérations
6 - Fonctions
7 - Déclarations complexes  




1 - Types de données et déclarations



1.1 - Types :



void : type non défini

enum : entier 16 bits [-32768, 32767]

int : entier 16 bits [-32768, 32767]

short ou short int : entier 16 bits [-32768, 32767]

unsigned int : entier 16 bits [0, 65535]

long ou long int : entier 32 bits [-2147483648, 2147483647]

unsigned long : entier 32 bits [0, 4294967295]

float : flottant 32 bits [3.4E-38, 3.4E+38]

double : flottant double précision 64 bits [1.7E-308, 3.4E+308]

long double : flottant 80 bits [3.4E-4932, 1.1E+4932]

char : caractère 8 bits [-128, 127]

unsigned char : caractère 8 bits [0, 255]





On peut ajouter à cette liste l’opérateur d’indirection * pour définir des pointeurs.





Les pointeurs peuvent être :



near : si le pointeur doit se limiter à adresser une zone mémoire contiguë de 64Ko.

Un pointeur near a une taille de 16 bits, soit un mot, ou encore 2 octets.



far : si le pointeur peut adresser toute l’étendue de 1Mo (adresse réelle sur 20 bits).

Un pointeur far a une taille de 32 bits, soit un double mot, ou encore 4 octets.






1.2 - Enumérations : enum



enum jour {Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi, Dimanche};



Regroupe un ensemble de constantes, représentées par un entier. Le premier élément vaut 0.





Exemple faisant appel à l'énumération jour :





enum jour Day; /* Day est une énumération de type jour */



Day = Vendredi;

Day = 4; /* Ne marche pas en C++ qui est plus contraignant au niveau


des concordances de types. Ici on tente d'affecter un entier à une variable de type enum */

1.3 - Déclaration :





< type de la variable à déclarer > < nom de la variable >;




unsigned char Byte; /* Byte est un caractère non signé : 1 octet de 0 à FFh */

typedef unsigned char Byte; /* idem mais Byte est maintenant un nouveau type */
Byte octet;


2 - Les structures

struct [ étiquette ] { déclarations } [ variables ]


2.1 - Nous pouvons définir simplement l’étiquette :

struct personne
{
char nom[20];
char prenom[20];
char tel[15];
int age;
}

struct personne paul;



2.2 - Nous pouvons ne pas définir d’étiquette :

On est obligé de déclarer les variables nécessaires en fin de structure puisqu’il n’y a pas d’étiquette.

struct
{
char nom[20];
char prenom[20];
char tel[15];
int age;
} laurent, beatrice;


2.3 - Nous pouvons définir des variables en même temps que l’étiquette :

Il est alors possible de déclarer de nouvelles variables du type de la structure définie.

struct personne
{
char nom[20];
char prenom[20];
char tel[15];
int age;
} laurent, beatrice;

struct personne paul;



2.4 - Définir une Structure en tant que nouveau type : typedef

typedef struct personne
{
char nom[20];
char prenom[20];
char tel[15];
int age;
} Personne ;

Personne paul, laurent, beatrice;

Personne est alors un nouveau type. Il évite en faite d’avoir à préciser struct personne lors d'une déclaration.

typedef permet de créer cette nouvelle définition de type (typedefine).


Ou plus simplement :

typedef struct
{
char nom[20];
char prenom[20];
char tel[15];
int age;
} Personne ;


Nous pouvons aussi imaginer une structure contenant une structure :

typedef struct
{
char nom[20];
char prenom[20];
char tel[15];
int age;
struct bureau
{
int poste;
char tel[15];
char fax[15];
}
} Personne ;


2.5 - Accès par référence

Si on accède à cette structure par son nom, et donc par référence à ce nom :

Personne inconnu;

inconnu.nom /* accède au champ nom */

inconnu.tel /* accède au champ tel */

(inconnu.bureau).tel /* accède au champ telde la structure bureau */


2.6 - Accès par pointeur

Si on accède à cette structure par un pointeur sur cette structure :

Personne *ptr;

ptr->nom /* accède au champ nom */

ptr->tel /* accède au champ tel */

(ptr->bureau).tel /* accède au champ tel de la structure bureau */


Dans ce dernier cas, nous accédons à la structure Personne par un pointeur, mais bureau est bien une structure et non un pointeur sur une structure.

On accède donc logiquement au champ tel de la structure bureau par référence et non par pointeur.


Notons au passage que ptr->tel pourrait aussi s'écrire (*ptr).tel puisque *ptr correspond au nom de la structure.

2.7 - Utilité des structures : les listes chaînées

Les structures sont utiles dès qu'il est possible de créer un bloc regroupant des informations pertinentes, pour stocker ou organiser ces informations.

L'exemple le plus simple et le plus significatif est celui d'une gestion de liste chaînée. Une liste chaînée est une structure permettant de stocker des données dont la taille croit, décroît selon les besoins, comme un fichier contenant des fiches d'un répertoire téléphonique.

Au lieu de créer un tableau en mémoire dont la taille est plus ou moins à définir à sa déclaration et dont l'encombrement mémoire est figé, les listes chaînées apportent souplesse d'utilisation, rigueur de l'algorithme et occupation optimale de l'espace mémoire.

On travaille sur des listes à l'aide de pointeur. Tout ce que l'on connaît de la liste, c'est le pointeur qui va pointer son premier élément. Cet élément aura lui même un champ pointeur que l'on fera pointer sur le suivant et ainsi de suite, pour accéder jusqu'au dernier.

La seule contrainte de ces listes est que l'on ne peut accéder directement à un élément. On est obligé de repartir du pointeur de tête et de parcourir les éléments de la chaîne jusqu'à trouver l'élément recherché.


typedef struct
{
char nom[20];
char prenom[20];
char tel[15];
} Fiche;

Cette structure déjà rencontrée plus haut représente l'élément de base de notre liste chaînée. Cet élément maillon de la chaîne porte le nom de CELLULE. La cellule telle qu'elle est décrite ici renferme tous les champs d'informations utiles, mais ne permet pas d'accéder à la cellule suivante. Pour ce faire il faut lui ajouter un champs pointeur sur une structure de type Fiche

typedef struct
{
char nom[20];
char prenom[20];
char tel[15];
Fiche *suivant; /* le champ suivant est un pointeur sur une structure */
} Fiche;

Un problème se pose dans cette déclaration : on déclare suivant comme un pointeur sur la structure Fiche alors que cette structure est en cours de déclaration. Le compilateur n'en connaît pas encore l'existence et générera une erreur du genre "undefined symbol suivant" : le champ suivant ne peut être défini puisque le type Fiche n'est pas (encore) défini.


On procédera alors de la sorte :


typedef struct cellule
{
char nom[20];
char prenom[20];
char tel[15];
struct cellule *suivant;
} Fiche;


A l'instant ou le champ suivant est déclaré le compilateur connaît struct cellule. Rappelons que Fiche équivaut à struct cellule grâce à la présence du typedef. Mais si struct cellule est connu dans le corps de la structure, Fiche ne l'est pas.
Voici un exemple de programme commenté:


#include<stdio.h>
#include<alloc.h>
#include<string.h>


typedef struct cellule
{
char nom[20];
char prenom[20];
char tel[15];
struct cellule *suivant;
} Fiche;


void main(void)
{
Fiche *Tete; /* tete de la liste chaînée */
Fiche *Cell1; /* Pointeur sur la première cellule */
Fiche *Cell2; /* Pointeur sur la seconde cellule */

/* Attention : nous avons déclaré des pointeurs sur des structures. Seuls ces pointeurs sont crées. Les structures qu'ils pointent ne le sont pas encore. Nous devons allouer de la mémoire pour que ces structures aient une existence en mémoire */

Cell1 = (Fiche*)malloc(sizeof(Fiche));
Cell2 = (Fiche*)malloc(sizeof(Fiche));

/* malloc alloue un bloc de mémoire de taille spécifiée, ici sizeof(Fiche) c'est à dire la taille en octets de la structure fiche, et retourne un pointeur sur la zone mémoire allouée. Le pointeur retourné étant générique (void*) , puisque l'on peut allouer de la mémoire pour n'importe quel type de donnée, il faut prendre l'habitude de faire une conversion de type, appelée CASTING. */

/* Ainsi (Fiche*) converti le pointeur générique retourné par la fonction malloc en un pointeur sur le type de donnée Fiche. Cell1 et Cell2 sont donc des pointeurs sur des structures ayant une existence en mémoire. */

strcpy(Cell1->nom,"PAPIN");
strcpy(Cell1->prenom,"Jean-Pierre");
strcpy(Cell1->tel,"0102030405");

strcpy(Cell2->nom,"PROST");
strcpy(Cell2->prenom,"Alain");
strcpy(Cell2->tel,"00324123456789");

/* Les champs d'informations utiles sont remplis. On va maintenant effectuer le chaînage entre la cellule 1 et la cellule 2 */

Cell1->suivant=Cell2;

/* La cellule 2 est la dernière de la liste. Elle ne pointe sur aucune cellule suivante. Le champ suivant de la cellule 2 doit donc contenir NULL, valeur prédéfinie pour les pointeurs nuls. On se servira de cette valeur dans les algorithmes pour tester l'arrivée en fin de liste chaînée. */

/* On n'est pas sensé connaître les adresse de chaque cellule. On n'est même pas sensé connaître le nombre de cellules. On connaît seulement le pointeur sur la première cellule appelé Tete */

Tete=Cell1;


/* Accédons aux cellules : */

printf("Fiche1 :%s\n",tete->prenom);
printf("Fiche1 :%s\n",tete->tel);
printf("Fiche2 :%s\n",tete->suivant->prenom);
printf("Fiche2 :%s\n",tete->suivant->tel);


/* il reste à libérer la mémoire précédemment allouée par malloc */

free(Cell1);
free(Cell2);

/* fin du programme */
}


2.8 - Schéma de fonctionnement
  
Tete Cell1 Cell2

Champs : N P T Suiv N P T Suiv
NULL



Ce programme n'a aucune utilité pratique dans la mesure ou les deux fiches ont été créées, remplies et parcourues à la main. En réalité on passera par des fonctions, si possible récursives (fonctions s'appelant elles même) puisque la récursivité est particulièrement adaptée à ce genre de travaux dont les actions sont principalement :


INSERTION D'UNE NOUVELLE CELLULE DANS LA CHAINE
Assure l'allocation mémoire, le remplissage des champs et le chaînage dans la liste.

- Insérer en début de chaîne
- Insérer en fin de chaîne
- Insérer dans la chaîne selon un critère donné ( ordre croissant, alphabétique... )


SUPPRESSION D'UNE CELLULE DANS LA CHAINE
Recherche de la cellule à supprimer, modifie le chaînage entre la cellule qui précède et la cellule qui suit la cellule supprimée et enfin libère l'espace mémoire alloué à la cellule que l'on supprime.

- Suppression dans la chaîne d'une chaîne donnée, selon un critère sur un champ.


OPERATIONS SUR LA CHAINE

- Lister le contenu des cellules.
- Compter le nombre de cellules (nombre de fiches);
... etc

  
2.9 - Remarques

Pour simplifier l'insertion en fin de chaîne, et ne pas avoir à la parcourir depuis le début on pourra gérer un pointeur sur le dernier élément appelé pointeur de Queue.

Il est fréquent de trouver des listes doublement chaînées avec un deuxième pointeur dans la structure, ce dernier pointant la cellule précédente. Il est ainsi possible de remonter la chaîne dans l'autre sens depuis le pointeur de Queue, ou plus simplement pour connaître l'adresse de la cellule précédent la cellule courante.
 
Tete Queue

Cell1 Cell2
Prec N P T Suiv Prec N P T Suiv
NULL NULL


Enfin, nous aurions pu déclarer la structure d'une autre manière :


Pour contourner le problème du nom de la structure inconnu lors de sa description, et qui nous gênait lors de la déclaration du pointeur sur la structure on peut procéder en deux temps :


typedef struct cellule *Cellptr; /* Cellptr : pointeur sur la structure Fiche */

typedef struct cellule
{
char nom[20];
char prenom[20];
char tel[15];
Cellptr suivant;
} Fiche;



Il faut faire attention. Dans ce cas, le champ suivant doit être de type Cellptren non Fiche*, sinon il y aura conflit de type :


Cellptr Tete;
Cellptr Cell1;
Cellptr Cell2;

Cell1=(Cellptr)malloc(sizeof(Fiche));
Cell2=(Cellptr)malloc(sizeof(Fiche));

Cell1->suivant = Cell2; /* affectation correcte : types identiques */


3 - Tableaux et chaînes de caractères


3.1 - Tableaux

En langage C le type tableau n’est pas prédéfini. On l’obtient par l’emploi des crochets [ ].

int table[10]; /* table est un tableau de 10 entiers */

Les indices de ce tableau iront alors de 0 à 9, c’est à dire que table[10] n’est pas défini.

Il est possible d’initialiser le tableau :

int table[10] = { 0,1,2,3,4,5,6,7,8,9};

Il est important de savoir que pour le compilateur C, le nom du tableau correspond en fait à un pointeur sur une suite d’éléments dont la taille dépend du type, ici 16 bits pour int.

On peut donc accéder aux élément du tableau par :

int *ptr; /* ptr est un pointeur sur un entier */
int valeur; /* un entier */

ptr = table; /* ptr pointe maintenant sur le tableau */

valeur = (*ptr); /* contient la valeur 0 */
ptr++; /* le pointeur augmente automatiquement de 2 octets
puisque c’est un pointeur sur un entier */
valeur = (*ptr); /* contient la valeur 1 */

ptr += 2; /* augmente le pointeur de 4 octets ( taille de 2 entiers int ) */

valeur = (*ptr); /* contient la valeur 3 */



3.2 - Tableaux à plusieurs dimensions

float matix[3][4]; /* matrix est une matrice de 3 lignes et 4 colonnes de flottants */

Les éléments d’une matrice sont rangés en mémoire linéairement, ligne par ligne. Pour organiser son contenu en mémoire, il est donc indispensable de connaître le nombre de colonnes pour savoir où commence la ligne suivante.

En mémoire :

| 11 , 12 , 13 , 14 | 21 , 22 , 23 , 24 | 31 , 32 , 33 , 34 |

int matix[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; /* initialisation */
int matix[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; /* plus lisible */

float matrix [ ] [4]; /* représente une matrice de n lignes de 4 éléments flottants */


De même :

int matix[3][4][2] ; /* matrix est une matrice de 3x4x2 d’entiers */

En mémoire :

| 111 , 112 | 121 , 122 | 131 , 132 | 141 , 142 || 211 , 212 | 221 , 222 | 231 , 232 | 241 , 242 || 311 , 312 | 321 , 322 | 331 , 332 | 341 , 342 |

3.3 - Chaînes de caractères

Les chaînes de caractères sont en fait des tableaux de caractères, et comme pour un tableau, le nom de la chaîne représente en fait un pointeur sur cette chaîne de caractères.

char nom[10]; /* nom est une chaîne de 10 caractères */

que l’on peut initialiser :

char nom[10] = "Jean";

J e a n \0




0 1 2 3 4 5 6 7 8 9


\0 est l'octet de fin de chaîne. Il correspond à la valeur 0 (non pas le caractère '0', mais la valeur 0 décimale ou hexadécimale).On nomme ces chaînes des chaînes ASCIIZ (Z pour Zéro). Il faut donc toujours prévoir un caractère pour cet octet. Exemple : pour une chaîne de 8 caractères maximum, il faut compter 9 caractères soit : char nom[9], qui seront numérotés de 0 à 8.

A partir du tableau ci-dessus exécutons le code suivant:

char *ptr; /* pointeur sur un caractère (longueur : 1 octet) */

nom[4] = 'n'; /* caractère n dans la case numéro 4 */

ptr = nom; /* ptr pointe désormais sur la chaîne, comme nom */

ptr += 5; /* ptr pointe sur la case numéro 5 */

*ptr = 'e'; /* caractère e dans la case numéro 5 */

ptr++; /* ptr pointe sur la case numéro 6 */

*ptr = 0; /* place la valeur zéro dans la case numéro 6 */


La chaîne contient alors :

J e a n n e \0


0 1 2 3 4 5 6 7 8 9




3.4 - Exemple de déclarations traitant de chaînes :

On peut déclarer un tableau de 5 chaînes de 10 caractères :

char table_string[5][10];

C'est en fait un tableau de deux dimensions.


Ou bien :

char *table_string[5];

Dans ce cas, c'est un tableau de 5 pointeurs sur des chaînes. L'espace mémoire n'est pas réservé dans ce cas. Il faudra effectuer une allocation dynamique de mémoire pour réserver 10 octets pour chaque chaîne pointée.

4 - Tests conditionnels

Les tests conditionnels et les boucles d'itérations on recours à l'évaluation d'une condition pour déterminer quelle action effectuer en conséquence. Cette évaluation retourne un entier.

L'évaluation d'une condition retourne toujours Vrai ou Faux. L'ordinateur ne connaissant pas Vrai ou Faux, le compilateur procède ainsi :

Si le résultat de l'évaluation est différent de 0 alors la condition vaut VRAI. En général, ce résultat vaudra 1 s'il est évalué par le compilateur. Mais si vous essayez if(-5), la condition vaudra VRAI.

Si le résultat de l'évaluation est égal à 0 alors la condition vaut FAUX.


4.1 - Opérateurs d'égalité :

L'opérateur d'égalité est == en non = qui est l'opérateur d'affectation.


4.2 - Opérateur de différence :

L'opérateur de différence est !=


4.3 - Autres opérateurs :

Une condition peut être composée de plusieurs conditions reliées par des opérateurs logiques "OU", "ET" ou encore "NON".

OU logique : || exemple : A OU B A || B
ET logique : && exemple : A ET B A && B
NON logique : ! exemple : NON A !A

Ne pas oublier que les parenthèses peuvent intervenir pour jouer sur les priorités.

Exemple : !(A||(B&&C))

Une condition doit toujours être mise entre parenthèses.


4.4 - Comparaisons

Une condition peut porter sur un test entre valeurs numérique, entre caractères et dans ce cas la comparaison porte sur leur valeur ASCII, mais pas directement entre chaînes de caractères. Il faut utiliser dans la condition la valeur retournée par la fonction strcmp par exemple.


4.5 - Test "si" :

Ce test fait appel aux mots réservés if et else :

if(condition)
{
/* exécuté si condition vraie */
}
else
{
/* exécuté si condition fausse */
}

Les accolades sont inutiles si il n'y a qu'une instruction a exécuter :

if(condition)
instruction1; /* exécuté si condition vraie */
else
instruction2; /* exécuté si condition fausse */
Si rien n'est à faire en cas d'évaluation à faux de la condition, else peut être omis :

if(condition)
{
/* exécuté si condition vraie */
}


Plusieurs tests peuvent être cascadés :

if(condition1)
{
/* exécuté si condition1 vraie */
}
else if(condition2)
{
/* exécuté si condition1 fausse et condition2 vraie */
}
else
{
/* exécuté si condition1 fausse et condition2 fausse */
}



4.6 - Test "selon" :

Ce test fait appel aux mots réservés switch et case :

switch(condition)
{
case const1 : instructions;
break;
case const2 : instructions;
break;
case const3 : instructions;
break;
default : instructions;
break;
}

const1, connst2... sont des constantes de type entier. Il ne peut s'agir de variables puisque le but est de définir une action selon des cas préétablis. Il faut garder à l'esprit qu'une condition est en fait un entier retourné lors de l'évaluation. La condition peut donc être un entier int ou long, une énumération enum, un caractère et dans ce cas sa valeur ASCII est employée, mais en aucun cas un flottant qui par nature n'est pas dénombrable.

Switch sert à évaluer la condition d'une table de décision.

Case énumère les différents cas possibles.

Break sert à sortir d'une branche d'instructions. Sans le break, les instructions des case suivants sont aussi exécutés.

Default permet d'exécuter des instructions dans le cas ou aucune entrée n'a été trouvée dans la table, c'est à dire dans le cas ou le résultat de l'évaluation de la condition ne correspond à aucune constante dans les case.
 

5 - Boucles d'itérations


5.1 - Boucle "for" : le nombre d’itérations est connu

for(i=0;i<=10;i++) /* ( valeur de départ ; condition de continuation ; incrémentation ) */
{
instructions
}



5.2 - Boucle "while" : le nombre d’itérations dépend de l’évaluation de la condition

while(condition de continuation)
{
instructions
}


5.3 - Boucle "do - while" : l’évaluation a lieu après l’exécution du bloc d’instructions

do
{
instructions
}
while(condition de continuation);

6 - Fonctions


<Type retourné> Nom de la Fonction (<types d'arguments>)
{
Corps de la fonction
}


6.1 - Exemples simples :


Fonction n'acceptant pas d'argument et ne retournant rien :

void Fonction (void)
{
}



Fonction retournant un entier, somme de deux entiers passés en arguments :

int Somme (int a, int b)
{
}


Fonction acceptant deux pointeurs sur des entiers longs et retournant un pointeur sur un flottant :

float* Somme (long *ptr1, long *ptr2)
{
}


6.2 - Fonctions et tableaux

void Fonction (char table[], int longueur)
{
}

Il n'est pas nécessaire de spécifier la taille du tableau, ce qui rend la fonction plus généraliste. Il faut cependant passer la dimension du tableau pour pouvoir le traiter. Sans le savoir, le tableau est passé à la fonction par adresse, car que le nom du tableau est en fait un pointeur sur ce tableau. Il nous est donc possible de modifier le contenu du tableau.


void Fonction (int matrice[3][4])
{
}

est correct.

void Fonction (int matrice[][4], int ligne)
{
}

est correct. On peut spécifier le nombre de lignes en le passant en argument.

void Fonction (int matrice[3][])
{
}

est incorrect. On peut omettre la première dimension du tableau, mais pas les suivantes, car comme nous l'avons vu, elles entrent dans le calcul de la position des éléments en mémoire.



6.3 - Fonctions et structures :

Contrairement aux tableaux, le nom d'une structure n'est pas un pointeur. Il représente simplement une étiquette regroupant un certain nombre de champs.

Si on passe une structure par argument, la fonction pourra lire le contenu de ses champs mais ne pourra les modifier puisque l'on n'en connaît pas l'adresse. La fonction Afficher opère par passage par argument. Elle référence les champs de la structure par l'opérateur '.' préfixant le nom de champ.

Pour qu'une fonction puisse modifier une structure, il faut lui passer cette structure par adresse. En passant l'adresse de la structure, on peut accéder aux champs pour les lire ou les modifier. La fonction Enregistrer procède ainsi.

Void Enregistrer (Personne *fiche);

L'argument fiche est donc un pointeur sur la structure Personne. Comme il ne s'agit plus d'une étiquette mais d'un pointeur, l'accès par référence avec le '.' n'est plus valable. On accède aux champs d'une structure à partir de son adresse par la flèche '->'. On écrira donc :

fiche->nom pour accéder au nom et pas fiche.nom.

On pourrait toutefois écrire (*fiche).nom puisque fiche est un pointeur sur la structure.

Une chose importante est à remarquer dans le programme qui suit : L'affectation, le test et toute opération sur les chaînes de caractères ne peut se faire que par l'emploi de fonction manipulant les chaînes.

Noter enfin que fiche étant un pointeur sur la structure, fiche++ va incrémenter le pointeur de la taille de la structure. L'emploi de sizeof n'est pas nécessaire. Si plusieurs éléments de type structure sont en mémoire (tableau de structure), on peut facilement passer d'un élément à l'autre.

#include<stdio.h>
#include<string.h>

typedef struct {
char nom[20];
char prenom[20];
char tel[15];
int age;
} Personne ;

void Afficher (Personne fiche)
{
printf("Nom : %s\n", fiche.nom);
printf("Prénom : %s\n", fiche.prenom);
printf("Téléphone : %s\n", fiche.tel);
printf("Age : %d\n", fiche.age);
}

void Enregistrer (Personne *fiche)
{
strcpy(fiche->nom,"PROST");
strcpy(fiche->prenom,"Alain");
strcpy(fiche->tel,"00324123456789");
fiche->age=45;
}

void main (void)
{
Personne pers;

strcpy(pers.nom,"PAPIN");
strcpy(pers.prenom,"Jean-Pierre");
strcpy(pers.tel,"0102030405");
pers.age=32;

Afficher(pers);
Enregistrer(&pers);
Afficher(pers);
}

7 - Déclarations complexes

On peut avoir à déclarer des fonctions assez complexes. Il faut alors faire systématiquement appel à la règle de priorité suivante :


7.1 - Priorités

Priorités par ordre décroissant :





( ) Opérateur de Fonction ou parenthèses de priorité
[ ] Opérateur de Tableau
* Opérateur d'indirection (pointeur)


Il faut tout d'abord rechercher le nom de la déclaration et reconnaître les blocs prioritaires en partant de ce nom.

7.2 - Par exemple :


*(t[])();

t[] : t est un tableau de ...
* XXX() : XXX est une fonction retournant un pointeur.

En remplaçant XXX par t[], mis entre parenthèses pour respecter la priorité :

*(t[])(); est un tableau de fonctions retournant un pointeur.


(*t[])();

t[] : t est en tableau de...
*t[] : t est un tableau de pointeurs.
XXX() : XXX est une fonction.

En remplaçant XXX par *t[], mis entre parenthèses pour respecter la priorité :

(*t[])(); est un tableau de pointeurs sur une fonction.


7.3 - Applications :

func est une fonction n'acceptant rien et retournant un pointeur générique (non défini) :

void *func(void);


funcptr est un pointeur sur une fonction n'acceptant par d'argument et ne retournant rien :

void(*funcptr)(void);


func est une fonction qui retourne un entier et qui accepte un tableau de 64 pointeurs sur des fonctions n'acceptant rien et ne retournant rien :

int func(void(*[64])(void));


func est une fonction acceptant un entier et retournant un pointeur sur une fonction acceptant un tableau de 4 pointeurs sur des chaînes de caractères et retournant un flottant :

float(*func(int))(char* [4]);


T est un tableau de 3 pointeurs sur des fonctions acceptant un entier et retournant un pointeur sur un tableau de 5 entiers longs :

long(*(*T[3])(int))[5];


func est une fonction n'acceptant rien et retournant un pointeur sur un tableau de 10 pointeurs sur des fonctions acceptant un entier et retournant un entier :

int(*(*func(void))[10])(int);

Article plus récent Article plus ancien

Leave a Reply

Telechargement