Mini-cours de C

Partie II

Vous connaissez déjà les bases du C. Le cours sera donc court.

Avant le C, existait le "A" et le "B" ... Le C apparu en 1972 - durant le programme Apollo, du temps d'Unix, avant Linux et bien avant Microsoft - aux États-Unis, à une époque où la mémoire coûtait très chère. Depuis, on n'a rien fait de mieux ! Il n'existe pas un seul logiciel qui ne peut pas être écrit en C.

Le "D" n'a jamais réussi à convaincre.

Vous savez que tous les langages de programmation utilisent : des variables, des structures de contrôles (conditionnelles et répétitives) et des fonctions.

Un octet

Définition

Les variables

La grosse différence avec le JavaScript et le PHP est qu'en C (et en C++) toute variable à un type.

En JavaScript, on place le mot-clé var ou let puis un espace puis un nom de variable suivi d'un point virgule. (Et, pour les constantes : const puis un espace puis un nom de variable puis le signe = puis la valeur puis le point-virgule.)
En PHP, on place le caractère $ devant le nom de la variable : $maVariable;

Le type permet de donner une taille de la variable en mémoire et permet au compilateur de créer des programmes plus rapides. Les langages modernes sont plus souples (au niveau du codage) et les programmes issus de ces langages déterminent eux-même le type en fonction de la valeur. Ce travail supplémentaire les ralentit. Si le programme est écrit en C, l'OS sait exactement la taille requise en mémoire pour que le programme fonctionne avant même qu'il soit utile à l'utilisateur. Depuis 1970, la mémoire des PC a beaucoup grandi. De plus, les processeurs sont devenus de plus en plus rapides.

Les entiers

Les 6 types de nombres entiers sont :

*) Le nombre d'octets dépend du compilateur (ici, 32 bits).

#include <stdio.h>
#include <limits.h>

int main()
{
  printf("-----------------------------------\n");
  printf("Type            int : min=%12d max=%12d\n",INT_MIN,INT_MAX);
  printf("Type      short int : min=%12d max=%12d\n",SHRT_MIN,SHRT_MAX);
  printf("Type      long  int : min=%12d max=%12d\n",LONG_MIN,LONG_MAX);
  printf("-----------------------------------\n");
  printf("Type            unsigned int : min=%u max=%12u\n",0,UINT_MAX);
  printf("Type      unsigned short int : min=%u max=%12u\n",0,USHRT_MAX);
  printf("Type      unsigned long  int : min=%u max=%12u\n",0,ULONG_MAX);
  printf("-----------------------------------\n");

}

En C, il est possible d'utiliser le moins de mémoire possible. Et, donc, à processeur égal, de créer les programmes les plus rapides. Toutefois, en tant que débutant, le type int est suffisant.

Entiers négatifs

Le dernier bit est le bit de signe. S'il vaut "1", le nombre est négatif.

La position d'un bit se compte de droite à gauche.

Si le nombre entier est codé sur 1 octet :

1111 1111 = -1
1000 0000 = -128

Si le nombre entier est codé sur 2 octets :

1111 1111 1111 1111 = -1
1000 0000 0000 0000 = -32 768

Les réels

Les 2 types de nombres réels sont :

#include <stdio.h>
#include <float.h>

int main()
{
   printf("The maximum value of float = %.10e\n", FLT_MAX);
   printf("The minimum value of float = %.10e\n", FLT_MIN);
   printf("------------------------------\n");
   printf("The maximum value of double = %.10e\n", DBL_MAX);
   printf("The minimum value of double = %.10e\n", DBL_MIN);
   printf("------------------------------\n");
   printf(" x = 1.17e-2   = %f\n", x); //  x = 1.17e-2 = 0.011700
   printf("-x = -1.17e-2  = %f\n", -x); // -x = -1.17e-2 = -0.011700
   float y=1.17e+2;
   printf(" y = 1.17e+2   = %f\n", y); //  y = 1.17e+2 = 117

}

1.17e-2 = 1.17 / 102 = 1.17 / 100 = 0.0117
1.17e-2 = 1.17 * 102 = 1.17 * 100 = 117
1.1754943508e-038 = 1.1754943508 / 1038
= 1.1754943508 / 100000000000000000000000000000000000000
= 0.000000000000000000000000000000000000011754943508

Si 1 vaut 1 mètre => 0.001 vaut un millimètre => 0.000001 vaut un micron.
Donc, 0.00000000000000000000000000000000000001, c'est très très petit

Le caractère

Les 2 types de caractères sont :

En C, un char est un petit nombre entier, qui n'utilise qu'un octet en mémoire et qui correspond un code de la table ASCII

N'utilisez que les codes compris entre 32 et 126.

Le premier caractère (imprimable) est l'espace (donc invisible). Il correspond au code ASCII 32.

#include <stdio.h>

int main()
{
  // liste des caractères autorisés en C
  printf("Liste des caractEres autorisEs en C :\n\n");
  for(int i=32;i<127;i++)printf("%c ",i);
  printf("\n");
}

Les codes 0 à 31 correspondent à des "caractères" (non imprimables) mais qui peuvent servir à l'impression, tel que le retour à la ligne dont le code ASCII est 10, mais qui, en pratique, est codé dans le texte : \n

#include <stdio.h>

int main()
{
  for(int i=0;i<5;i++){
    printf("%c",65+i);
    printf("%c",10);
  }
}

Notez aussi que :

  • le code d'une minuscule = code de la majuscule +32
  • le code d'un chiffre = ce chiffre +48
#include <stdio.h>

int main()
{
  printf("Le code ASCII d'une minuscule = celui de la majuscule +32\n");
  printf("La premiEre majuscule, A, vaut 65\n");
  printf("code ASCII (65+32) => %c\n\n",65+32);
  printf("Le code ASCII du premier chiffre, 0, vaut 48\n");
  printf("code ASCII 48 => %c\n",48);
}

Les chaînes de caractères

En C, on considère qu'une suite de caractères est un tableau de caractères.

char prenom[20];

Ici, le tableau "prenom" contient 20 cases.

Si votre prénom fait 7 caractères, 13 octets en mémoire sont inutilisés !

La différence entre un tableau de caractères et une chaîne de caractères est que le contenu de la dernière case d'une chaîne de caractères est NUL (code ASCII 0)

Dans la mémoire de l'ordinateur, il n'y a que des '0' et des '1'.
Une série de 8 bits forme un octet.
Un octet peut être représenté par un nombre décimal compris entre 0 et 255.
Si ce nombre décimal est compris entre 32 et 126, il peut aussi être représenté par une lettre.

Aucun ordinateur n'est capable de stocker une lettre, ni un nombre !
Il ne stocke que des octets (des séries de 8 bits).
Ce qui est affiché à l'écran n'est qu'une représentation de la mémoire.

#include <stdio.h>

void charToBits(int value, char bits[9])
{
  int mask = 0x01;
  for(unsigned i = 0; i < 8; ++i)
  {
    bits[7-i] = (value & mask) != 0 ? '1' : '0';
    value>>=1;
  }
  bits[8] = '\0';
}

main()
{
  char prenom[20]="Thibaut";

  char bits[9];
  printf("Contenu de la mEmoire prEsentE en binaire :\n");
  printf("|");
  for(int i=0;i<20;i++){
    charToBits(prenom[i],bits);
    printf(bits);
    printf(" ");
  }
  printf("|\n\n");

  /*printf("Contenu de la mEmoire prEsentE en nombres hExadEcimaux :\n");
  printf("|");
  for(int i=0;i<20;i++)printf("%x ",prenom[i]);
  printf("|\n\n");*/

  printf("Contenu de la mEmoire prEsentE en nombres entiers :\n");
  printf("|");
  for(int i=0;i<20;i++)printf("%d ",prenom[i]);
  printf("|\n\n");

  printf("Contenu de la mEmoire prEsentE en caractEres :\n");
  printf("|");
  for(int i=0;i<20;i++)printf("%c",prenom[i]);
  printf("|\n\n");
}

Les constantes

Une variable qui ne varie pas est ... une constante.
Pour interdire le changement du contenu d'une variable (après son initialisation), on ajoute le mot-clé const après ou avant le type.

double const PI = 3.1415926535;
const int TAILLE = 20;

Par convention, le nom des constantes est tapé en majuscules (pour les identifier plus rapidement dans le code).

Remplacez au maximum vos variables par des constantes, car vous diminuerez le nombre de bugs et le programme sera plus rapide. Préférez double const pi = 3.1415926535; à double pi = 3.1415926535;

Mieux, tapez #DEFINE PI 3.1415926535 juste en dessous des #include. Ainsi, lors de la pré-compilation du code source, tous les "PI" seront remplacés par "3.14159623535".

Les tableaux

Un tableau est une variable qui contient plusieurs données de même type.

int nombrEntiers[20];
double nombrReels[20];
char prenom[20];

Est mis entre crochets le nombre (entier) d'éléments du tableau (ou une constante entière).

int const TAILLE=20;
char prenom[TAILLE];

Dans le cadre de ce mini-cours, le type enum (apparu en 1989, ANSI C) n'est pas abordé.

En fin de compte, en C, c'est facile. On n'utilise pratiquement que 3 types : int, double et char.

En fait, tout programme C peut être écrit en n'utilisant que 2 types : int et double
Car, en pratique, un "char" est un petit "int".

A la limite, on pourrait même n'utiliser qu'un seul type !
En effet, l'ensemble des réels contient l'ensemble des entiers ...
Mais, dans ce cas, il faudra se priver des boucles for, des switch, du modulo, ...

Les booléens

En C, il n'existe pas de type booléen !

Il n'existe pas de true ou false.

Faux correspond à 0, et tout le reste (-1, 1, 117, -23, ...) correspond à vrai.

Les structures de contrôles ...

Attention, toute instruction comprise dans un bloc d'instructions - {...} - doit se terminer par un point-virgule. Même si cette instruction est unique (contrairement au JavaScript et PHP).

... conditionnelles

if() [{...} [else [{...}]]]

Historiquement, ce type de structure conditionnelle est la plus ancienne.
Elle existe dans (quasi) tous les langages de programmation.
Elle fonctionne dans tous les cas.

En C, les conditions if sont comme en JavaScript, en PHP et C++. Fin !

En réalité, sur ce point, c'est le langage JavaScript qui est identique au C.

switch(){...}

Un switch est préférable à une série de if, car, si la valeur qui suit le case a déjà été indiquée, l'erreur sera détectée par le compilateur (pas dans une série de if).

switch(expression) {
    case x:
        code block
        break;
    case y:
        code block
        break;
    default:
        code block
}

Cette structure existe aussi en JavaScript, en PHP et en C++. Elle ne permet pas de résoudre tous les cas (si la clause default n'est pas utilisée, contrairement au if), car l'expression doit être d'un certain type. Toutefois, dans certaines situations, cette structure est préférable aux if, notamment dans les cas où les choix sont précis et limités, tel qu'un jour de semaine, un mois, ...

En C, l'expression - appelée sélecteur - doit être du type int ou char (ou enum).

Cette expression est souvent une variable, mais peut aussi être une fonction qui retourne une valeur qui correspond au type int ou char.

La variable qui suit le mot-clé case doit être du même type que le sélecteur.

Dans le cas - In the case - où la valeur de l'expression vaut la valeur de la variable, le code exécuté est celui qui suit les deux-points, jusqu'à la fin de cette structure.

Le mot clé break permet de sortir de cette structure (et d'exécuter la première instruction qui suit cette structure).

Le mot clé default signifie : dans tous les autres cas. Facultatif, il est placé en fin de structure.

Contrairement au if, si l'évaluation de l'expression vaut zéro, les instructions à l'intérieur du corps du switch seront exécutées dans le cas où un case correspond à cette valeur.

En JavaScript, en PHP, le sélecteur peut aussi être du "type" string ou réels.

L'opérateur ternaire ne sera pas abordé dans ce mini-cours.

Pour des raisons historiques, le branchement inconditionnel - via les mot-clés goto et label - a été maintenu. Toutefois, cette façon de coder est incompatible avec la programmation moderne.

... répétitives

Attention, une boucle infinie peut planter le programme.

while (condition) {...}

Historiquement, ce type de boucle est la plus ancienne.
Elle existe dans (quasi) tous les langages de programmation.
Elle fonctionne dans tous les cas.

En C, la boucle while est comme en JavaScript. Fin !

En réalité, sur ce point, c'est le langage JavaScript qui est identique au C.
Rappel : Cette boucle peut être utilisée dans tous les cas (même si on ne connaît pas le nombre de tours qu'elle fera avant d'entrer dans la boucle), mais ...

Attention, il faut que la variable utilisée dans la condition varie dans le corps de cette boucle, de sorte qu'une sortie de cette boucle survient sûrement.

Ce type de boucle est aussi utilisée, à l'identique, en JavaScript, en PHP et en C++.

do {...} while (condition);

Cette boucle est toujours exécutée au moins une fois, car la condition de répétitions est située à la fin.

Attention, de ne pas oublier le point-virgule après les parenthèses de la condition.

Ce type de boucle est aussi utilisée, à l'identique, en JavaScript, en PHP et en C++

Même remarque qu'avec la while.
Attention, il faut que la variable utilisée dans la condition varie dans le corps de cette boucle.

for

Ce type de boucle est le type le plus utilisé.

En C, la boucle for est comme en JavaScript. Fin !

En réalité, sur ce point, c'est le langage JavaScript qui est identique au C.
Rappel : En principe, le nombre de tours devrait être connu avant d'entrer dans la boucle for

Les fonctions

Idem qu'en JavaScript, sauf qu'il ne faut pas utiliser le mot clé function, mais qu'il faut indiquer le type du retour et le type des arguments.

Si la fonction retourne quelque chose, le type est indiqué devant le nom de cette fonction. Ci dessous, la fonction retourne un nombre de type float :

float maFonction ( int argument1, char argument2 ){
  ...
  return 3,1415926535;
}

Si la fonction ne retourne rien, on utilise alors le mot-clé void, ainsi :

void maFonction2 ( int argument1, char argument2 ){
  ...
}

Dans ce type de fonction, le mot-clé return n'est pas utilisé (ou l'est pour sortir prématurément si une condition n'est pas respectée)

Les commentaires

En C, C++, Java, JavaScript et PHP, les commentaires sont codés de la même façon.

int a = 4; // commentaire de fin de ligne

/* Commentaire sur
   plusieurs lignes
   de code
*/

Vous pouvez coder "comme en C" dans de nombreux autres langages (même si ce n'est pas toujours documenté).
Ainsi, en Javascript, if (!0) alert("En JavaScript, aussi, !0 vaut true");
Le C est vraiment le père de tous les langages modernes.

Le C (normalisé en 1989 et appelé ANSI-C) ne contient que 32 mot-clés.