Ce bon vieux Hello world

Et oui, ici aussi vous n’y échapperez pas, notre premier programme C vas nous permettre d’afficher ce fameux Hello world dans la console.

Avant de commencer à coder, réfléchissons un peu. Pour rappel le C est une suite de fonction dont une obligatoire la fonction main( ), c’est elle qui sera exécutée en premier d’où son appellation point d’entrée. Nous allons devoir trouver le moyen d’afficher du texte dans la console.

En programmation C, il est aussi impératif de réfléchir à quel genre de “chose” doit renvoyer la fonction, comme par exemple un entier, une chaîne de caractères, un caractère, un booléen, … autrement dit, quel type la fonction va renvoyer.

Le point d’entrée main( )

Lorsqu’un programme commence à s’exécuter il appellera toujours main( )en premier. C’est une fonction qui permet de diriger les appels aux autres fonctions du programme.

Voyons ce qu’il se passe pour un programme qui n’utilise pas la fonction main().

Malgré tout quelques contraintes s’appliquent à main( ) :
On considère le code suivant associé à son résultat à la compilation :

#include <stdio.h>
 
int somme(int a, int b){
	return a+b;
}
...  /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/Scrt1.o : dans la fonction « _start » :
(.text+0x1b) : référence indéfinie vers « main »
collect2: error: ld returned 1 exit status

L’erreur spécifie directement que la fonction main est indéfinie, en gros qu’il manque la fonction main.

Elle ne peut pas être définit comme static.
En fait, le mot clé static possède deux utilités différentes selon le contexte :

  • Placé dans un fichier en global, il permet de limiter la visibilité au fichier courant.
  • Placé dans une fonction, appliquée à une variable elle lui donne une durée de vie statique.

Ainsi, si on définit la fonction maincomme étant static alors cette dernière sera invisible lors de l’édition des liens, le système sera donc incapable de la retrouver pour démarrer le programme.

  • Elle ne peut pas être appelée

Toute fonction en langage C possède le même squelette :

typeRetour nomFonction( arguments ) {
   instruction 1;
   instruction 2;
   ...;
   return ...;
}

Étudions ce squelette :

  • typeRetour représente le type de la donnée que doit renvoyer la fonction. C’est à dire si la fonction doit retourner un entier, un caractère, un tableau, …
  • nomFonction représente le nom que vous donnez à la fonction permettant de l’appeler.
  • arguments représentent les paramètres de la fonction (une fonction n’a pas forcément de paramètres).
  • return est l’instruction permettant de retourner un résultat.

WARNING

Si votre fonction doit retourner un entier et que vous retournez un charactère, la compilation ne fonctionnera pas.

Si on revient à notre fonction main( ),

  • Par convention, elle retourne 0 donc un entier de type int que l’on détaillera après.
  • On connaît son nom.
  • Elle n’a pas besoin d’arguments/paramètres.

Ainsi en suivant le squelette, notre fonction main( ) devrait ressembler à cela :

int main( ){ 
   // Instructions de la fonction main( )
   return 0;
}

WARNING

Sans oublier le return 0; puisqu’on définit que la fonction principale revoie un entier.

Ainsi, ce tout petit code nous permet d’introduire des spécificités de la programmation C

  • Chaque fonction est un bloc délimitée entre accolades { ... }.
  • Chaque instruction se termine avec un point-virgule ; qui permet de spécifier la fin d’une instruction.

Écrire des commentaires

En C il est possible d’écrire deux sortes de commentaires :

  • Sur une ligne //...
  • Sur plusieurs lignes /* ... */
// Un commentaire sur une ligne
/*
Un commentaire sur 
plusieurs lignes
*/

Afficher des informations dans la console

La fonction printf(...) permet d’afficher du texte ou des valeurs à l’écran. Il faut se souvenir que printf n’est pas directement utilisable, elle vient d’une bibliothèque appelée stdio.h. Ainsi cela implique que pour pouvoir utiliser la fonction printf il faudra spécifier en entête la bibliothèque. C’est donc là qu’intervient les #include évoqués lors du premier cours.

“Signature” de la fonction printf :

printf(const char* format, ...);
  • La fonction prend comme premier paramètre une chaîne de caractères entre "...". Cette dernière peut-être formatée ou non.
    On dit qu’une chaîne est formatée lorsqu’elle permet d’afficher des données sous un format donné. Le format est spécifié par un symbole pourcentage suivit du format correspondant : %....
  • Les autres paramètres sont les valeurs des différents formats spécifié dans la chaîne. Si aucun format, alors aucune valeur/variable à spécifier à la fonction.

Voici comment inclure la bibliothèque stdio.h dans votre fichier C et d’afficher ce bon vieux Hello world.

Pour rappel :

  • main( ) est le point d’entrée du programme qui renvoie un entier, 0.
  • Pour afficher une donnée on utilise printf issu de la bibliothèque stdio.h.
#include<stdio.h>
 
int main( ){
   printf("Hello world");
   return 0;
}
Hello world

Et voilà, votre premier main en langage C, plutôt cool non ? Bon je suis d’accord, on a pas encore vu grand chose, mais déjà quelle fierté d’avoir son premier code qui fonctionne !!

TIP

Les header (= include) se font au début du code, c’est à dire avant toutes les fonctions. La quasi totalité du temps, les premières lignes de vos fichiers C seront des include.

Grossièrement votre code devrait ressembler au squelette suivant :

#include<...>
#include<...>
//... 
 
// Toutes les fonctions
 
// À la fin, la fonction main
int main( ){ 
   // Instructions
   return 0;
}

Les types usuels

Bon je suis d’accord avec toi, ça fait plusieurs fois que je parle de type. C’est donc le moment idéal pour les introduire un à un. Prenons les plus fréquents pour commencer. Les autres pourront être introduits au moment voulu.

Le type caractère

Le mot clé char désigne un objet de type caractère, ce dernier peut contenir n’importe quel caractère que la machine utilise. Le plus souvent, un objet de type char est codé sur 1 octet (= 8 bits) et les caractères pris en compte sont ceux qui correspondent au codage ASCII.

DecimalHexCharDecimalHexCharDecimalHexCharDecimalHexChar
000[NULL]3220[SPACE]6440@9660`
101[START OF HEADING]3321!6541A9761a
202[START OF TEXT]3422"6642B9862b
303[END OF TEXT]3523#6743C9963c
404[END OF TRANSMISSION]3624$6844D10064d
505[ENQUIRY]3725%6945E10165e
606[ACKNOWLEDGE]3826&7046F10266f
707[BELL]3927'7147G10367g
808[BACKSPACE]4028(7248H10468h
909[HORIZONTAL TAB]4129)7349I10569i
100A[LINE FEED]422A*744AJ1066Aj
110B[VERTICAL TAB]432B+754BK1076Bk
120C[FORM FEED]442C,764CL1086Cl
130D[CARRIAGE RETURN]452D-774DM1096Dm
140E[SHIFT OUT]462E.784EN1106En
150F[SHIFT IN]472F/794FO1116Fo
1610[DATA LINK ESCAPE]483008050P11270p
1711[DEVICE CONTROL 1]493118151Q11371q
1812[DEVICE CONTROL 2]503228252R11472r
1913[DEVICE CONTROL 3]513338353S11573s
2014[DEVICE CONTROL 4]523448454T11674t
2115[NEGATIVE ACKNOWLEDGE]533558555U11775u
2216[SYNCHRONOUS IDLE]543668656V11876v
2317[END OF TRANS. BLOCK]553778757W11977w
2418[CANCEL]563888858X12078x
2519[END OF MEDIUM]573998959Y12179y
261A[SUBSTITUTE]583A:905AZ1227Az
271B[ESCAPE]593B;915B[1237B{
281C[FILE SEPARATOR]603C<925C\1247C|
291D[GROUP SEPARATOR]613D=935D]1257D}
301E[RECORD SEPARATOR]623E>945E^1267E~
311F[UNIT SEPARATOR]633F?955F_1277F[DEL]

TIP

Les caractères en C peuvent être utilisés comme des entiers, il est donc possible de faire des opérations arithmétiques sur eux.

Exemple :
On considère une variable a qui contient le caractère 'a' et on souhaite afficher la lettre b à partir de a. On sait grâce à la table ASCII que la différence entre le code de b et le code de a vaut 1, il faut donc ajouter 1 à la variable contenant 'a' pour obtenir b.

#include<stdio.h>
 
int main( ){
   char a = 'a';
   char b = a+1;
   printf("%c", b);
}
b

WARNING

Une variable de type char ne peut contenir qu’un seul et unique caractère. Pour pouvoir stocker plusieurs caractères, il faudra utiliser :

  • Un tableau de char
  • Un pointeur vers char

Ces deux aspects seront évoqués plus tard dans ce cours. cours 6 et 7

En conséquence, cela implique directement qu’une variable de type char contenant plusieurs caractères déclenchera une erreur et empêchera la compilation de votre programme.

Tip

Un objet de type char ne peut contenir qu’un seul caractère, et en réalité chaque caractère représente une valeur numérique qui correspond en fait à son codage dans la table ASCII.

Ainsi, on peut avoir besoin d’afficher la valeur d’une variable de type char sous forme de caractère ou sous forme numérique. Cela est évidemment possible en langage C.

Voilà comment procéder :

#include<stdio.h>
 
int main( ){
   char a = 'A';
   printf("%c", a); // Afficher le caractère
   printf("%d", a); // Afficher la valeur numérique
}
A
65

65 représente le code ASCII de la lettre A.

Pour le moment, ne nous préoccupons pas de %c et %d, on en reparle un peu après. Pour spoiler un peu on peut les voir comme des formats qui permettent de dire je veux afficher la valeur dans un certain type de la variable truc.

Le type entier

Les entiers sont représentés par le mot clé int, qui peut être précédé :

  • d’un attribut de précision short ou long
  • d’un attribut de représentatio signed ou unsigned

Remarque

La taille d’une variable dépend aussi de la machine sur laquelle vous codez.

TypeTaille (en octets)Plage de valeur possible
int4-2 147 483 648 à 2 147 483 647
unsigned int40 à 4 294 967 295
short ou short int2-37 768 à 37 767
unsigned short20 à 65 535
long ou long int4 ou 8dépend du pc
unsigned long4 ou 8uniquement valeurs positive
long long8très grands nombres
unsigned long long8les positifs

Remarque

  • unsigned pour des valeurs positives.
  • signed pour les valeurs positives et négatives.
  • short petite taille
  • long grande taille
  • La taille des entiers selon le format peut dépendre aussi du système sur Windows par exemple on note certaines différences entre 32 et 64 bits. Le compilateur peux aussi jouer un rôle.

Exemple :
Si on souhaite définir un entier non signé, il suffit de spécifier le type de l’entier choisit, un nom de variable puis une valeur. Dans la pratique si on choisit que la variable var contiendra 155 alors, en programmation C on écrira :

unsigned int var = 155;

TIP

La taille d’un short int est comprise entre la taille d’un objet de type char et la taille d’un objet de type int, on peut aussi le noter :

sizeof(char) <= sizeof(short int) <= sizeof(int)

L’opérateur sizeof

L’opération sizeof permet de déterminer la taille (en octets) d’un type de données ou d’une variable. Ce dernier se révèle très utilise lorsqu’il faut gérer la mémoire, et pour travailler avec les structures complexes.

Utiliser sizeof pour un type de données

Il est possible d’obtenir la taille d’un type de donnée spécifié lors de l’appel de l’opérateur.

Remarque

Les types de données sont int, char, float, …

Ainsi, il suffit d’écrire sizeof(type_de_donnees).

Exemple :

#include<stdio.h>
 
int main( ){
   printf("Taille d'un entier : %d", sizeof(int));
   printf("Taille d'un caractère : %d", sizeof(char));
   printf("Taille d'un flottant : %d", sizeof(float));
   return 0;
}

Warning

La taille des données peut changer selon votre système mais en général, les tailles données ci-dessus sont dans la majeure partie des cas celles-ci.

Utiliser sizeof sur une variable

#include<stdio.h>
 
int main( ){
   int a = 1452; 
   printf("Taille de la variable a : %d", sizeof(a));
   return 0;
}

Il est évidemment possible de calculer la taille des tableaux et des structures mais nous verrons ces aspects plus tard dans ce cours.

Remarque

L’opérateur sizeof est évalué à la compilation, ce qui signifie qu’il n’a pas d’impact sur la performance à l’exécution.

Les types flottants

En langage C, le type flottant (floating-point) est utilisé pour représenter les nombres réels avec une partie décimale. Il existe deux types flottants :

  • float précision simple
    • généralement sur 32bits
    • environ 7 chiffres significatifs
  • double précision double
    • généralement sur 64bits
    • environ 15-16 chiffres significatifs

WARNING

Le nombre de chiffres significatif représente la précision avec laquelle est donnée un nombre.

Les valeurs minimales et maximales sont définies dans le header <float.h> :

  • FLT_MIN
  • FLT_MAX
  • DBL_MIN
  • DBL_MAX

WARNING

  • Il est possible de convertir un flottant vers un entier, cela peut néanmoins entraîner une parte de précision.
  • Les arrondis peuvent entraîner des erreurs à cause de leur représentation en binaire.

Exemple :

#include <stdio.h>
 
int main( ){
   float var = 14.5f;
   double val = 14.2;
}

TIP

Par défaut, les flottant sont de type double alors c’est pour cette raison que si il est de type float on ajoute un f à la fin.

Caster une variable en C

Le terme caster une variable” ça veut dire convertir un type de variable en un autre. On utilise ainsi la syntaxe suivante en langage C.

(type_var) var

Exemple :

#include <stdio.h>
 
int main() {
    double x = 5.8;
    int y;
 
    y = (int) x;  // cast explicite de double vers int
 
    printf("x = %f\n", x);
    printf("y = %d\n", y);
 
    return 0;
}
x = 5.8
y = 5

Exemple :

int a = 10;
double b = 3.5;
 
// Cast implicite (devient un double)
double result1 = a + b;   // a est converti en double
 
// Cast explicite
int result2 = (int)b;     // b devient 3

Portée des variables

La portée des variables en C (aussi appelée scope), c’est la partie du programme dans laquelle une variable est accessible (où tu peux l’utiliser). Il y a plusieurs types de portée selon où et comment tu déclares ta variable.

Warning

Une variable doit être déclarée et initialisée avant d’être utilisée… !

Prenons l’instruction suivante :

int j = 0, c;

En C, on peut déclarer plusieurs variables en même temps sans pour autant les initialiser. Hors, quand on est en C, si on initialise pas les variables, elles ont ce qu’on appelle une valeur indéfinie, ce qui signifie que tu ne peux pas savoir ce qu’elle contient.

Du coup dans l’instruction ci-dessus, tu initialises bien j à 0, donc ta variable j stocke bien la valeur 0. Alors que pour la variable c, tu ne lui donne pas de valeur.
Ainsi, quand tu voudras afficher les valeurs de j et de c, tu obtiendra pour c une valeur différente à chaque exécution du programme.

Exemple :

#include <stdio.h>
 
int main() {
    int j = 0, c;
    printf("j=%d et c=%d", j, c);
    return 0;
}

Suivit de trois exécutions du programme.

j=0 et c=-1392444720

j=0 et c=-1370719536

j=0 et c=1561864912

Ainsi, tant qu’une variable n’est pas explicitement initialisée/définie, alors elle contient une valeur indéterminée.

Variable locale

C’est la déclaration à l’intérieur d’un bloc. Dans ce cas la variable est dite LOCALE et est accessible uniquement dans ce même bloc.

Exemple :

void maFonction() {
    int x = 5; // portée locale
    printf("%d\n", x); // Pas d'erreur ici
}
printf("%d\n", x); // Erreur, x n'est pas visible ici

Variable globale

Déclaration en dehors de toute fonction, elle est appelée variable GLOBALE et est accessible partout dans le fichier.

Exemple :

int y = 10; // portée globale
 
void afficher() {
    printf("%d\n", y);
}

TIP

On peut aussi avoir accès à une variable globale partout dans d’autres fichiers en l’exportant avec extern.

Exemple d’export de variable globale dans d’autres fichiers
  • Fichier : fichier1.c
int compteur = 0;  // Définition de la variable globale
...
  • Fichier : fichier2.c
extern int compteur;  // Déclaration : "Je sais qu'elle est définie ailleurs"
...

Retour sur printf( )

Je ne sais pas si tu as remarqué mais quand on veut afficher la taille ou la valeur d’une variable par exemple, on précise à l’intérieur de printf ce qu’on appelle le format. En gros, on dit qu’on souhaite afficher la valeur d’une certaine variable dans un certain format.

On utilise le caractère % pour chaque variable suivit de l’initiale associé au format dans lequel on souhaite afficher la variable.

On considère deux variables que l’on souhaite afficher dans un format bien précis alors de manière générale, on utilisera printf comme suit :

printf("message %format1 ... %format2", var1, var2);

Exemple :
On souhaite afficher la valeur entière, puis caractère d’une variable. Ainsi on codera :

#include <stdio.h>
 
int main( ){
   char lettre = "A";
   printf("Format entier : %d et format caractère : %c.", lettre, lettre);
   return 0;
} 
Format entier : 65 et format caractère A.

Tableau des principaux formats valides

FormatType de l’argument affichéFormat d’affichageExempleBornes usuelles
%ccaractère (char)ASCIIa G usigned char : à
unsigned char : à
%hd %hu %hXentier short int signé entier short int non signé hexadécimal (unsigned short)Base 10 Base 10 Base 16-12 463 90AFshort signé : à
unsigned short : à
%ld %lu %lXentier long int signé entier long int non signé hexadécimal (unsigned long)Base 10 Base 10 Base 16-1289 46399 B4E98A0Flong signé (64 bits) : à
unsigned long : à
%d %Xentier int signé entier int en hexadécimalBase 10 Base 16-546 9A0Fint signé (32 bits) : à
unsigned int : à
%le %lfréel double précision doublescientifique ou flottant-3.195874 -45e-7environ à (15-16 chiffres de précision)
%e %fréel simple précision floatscientifique ou flottant-3.195874 -45e-7environ à (6-7 chiffres de précision)
%schaîne de caractèresHello worlddépend de la mémoire disponible

Il est aussi possible d’ajouter des options entre % et la lettre qui définit le format.

  • Ajouter un entier qui précise sur combien de caractère est affiché la variable. Par exemple, si on souhaite afficher une variable entière sur 5 caractère on utilisera le format %5d.

WARNING

Le texte est aligné automatiquement à droite, alors lorsque la variable s’affiche sur plus de caractère, le nombre fournit est ignoré.

Exemple :

#include <stdio.h>
 
int main()
{
    int n = 4;
    printf("Le nombre n vaut %6d et voilà.", n);
    return 0;
}
Le nombre n vaut      4 et voilà.
  • Pour aligner à gauche en utilise le signe -.

Exemple :

#include <stdio.h>
 
int main()
{
    int n = 4;
    printf("Le nombre n vaut %-6d et voilà.", n);
    return 0;
}
Le nombre n vaut 4      et voilà.
  • On peut ajouter deux nombres séparés par un point . très utilise sur les réels pour spécifier sur combien de caractère il est affiché puis sa précision.

    %nbChar.precisionFORMAT
    

Exemple :
On souhaite afficher le nombre sur 10 caractères avec une précision après la virgule de 2.

#include <stdio.h>
 
int main()
{
    int n = 12.874;
    printf("Le nombre n vaut %10.2lf et voilà.", n);
    return 0;
}
Le nombre n vaut 12.87      et voilà.

Il existe aussi des constantes caractères permettant d’effectuer un sout de ligne par exemple.

constante caractèresignification
\nsaut de ligne
\ttabulation
\rretour chariot
\fsaut de page
\'apostrophe
\?point d’interrogation

Les constantes

Justement et si on parlait des constantes en programmation C.

Généralités

En C une constante représente une donnée dont la valeur ne peux pas changer après sa définition. Contrairement aux variables, les constantes garantissent que leur valeur ne changera pas tout au long du programme.
Il existe deux manières de déclarer des constantes :

  • Avec le mot clé const
  • Avec la directive préprocesseur #define

On utilise les constantes pour une meilleure lisibilité, maintenabilité du code et sécurité puisqu’elle empêche la modification de la valeur.

Initialiser une constante

En utilisant le mot clé const

const int TEINTE = 14;
 
TEINTE = 130; // Erreur : on ne peut pas modifier une constante
  • Toute modification impliquera une erreur de compilation

  • La constante doit être initialisée pendant sa déclaration

    const int VITESSE;
    VITESSE = 140; 
     
    // Erreur à la compilation

Avec #define

#define PI 3.14159
  • Dans notre exemple PI est une constante symbolique.
  • Le préprocesseur remplace chaque occurrence de PI par 3.14159 dans le programme.
  • Pas de type directement associé (attention aux erreurs)

Quelques conseil

Tip

  • Utilisez const pour des constantes typées.
  • Utilisez #define pour des macros, ou des constantes globales (valeur fixe, configuration, …)

Les constantes prédéfinies

Il existe évidemment de multiples constantes prédéfinies, on en a vu quelques unes comme FLT_MIN et FLT_MAX dans l’entête <float.h>.

Suivant