Partager cette page

Apprendre X en Y minutes

Où X=C

Le C est le langage de plus bas niveau que la plupart des programmeurs seront amenés à utiliser, mais ceci est largement conpensé par sa vitesse brute.

À propos des options de compilation

Par défaut, gcc et clang sont assez silencieux sur les avertissements et les erreurs de compilation, qui peuvent être des informations très utiles. L'utilisation explicite d'options de compilation plus strictes est recommandée. Voici quelques valeurs par défaut recommandées:

-Wall -Wextra -Werror -O2 -std=c99 -pedantic

Pour plus d'informations sur ce que font ces options ainsi que sur d'autres, vous pouvez consulter la page du manuel de votre compilateur C (par exemple man 1 gcc) ou recherchez simplement en ligne.

// Les commentaires sur une ligne commencent par // - valable seulement pour C99 et plus tard.

/*
Les commentaires multilignes resemblent à ceci. Ils restent valables en C89.
*/

/*
Les commentaires multilignes ne s'emboîtent pas /* Attention */  // Le commentaire se termine sur cette ligne...
*/ // ...pas ici !

// Constante : #define <nom>
// Les constantes sont écrites en majuscules par convention, pas d'obligation
#define DAYS_IN_YEAR 365

// Les constantes d'énumeration sont aussi une façon de déclarer des valeurs.
// Toutes les instructions doivent se terminer par un point-virgule.
enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT};
// MON vaut 2 automatiquement, TUE vaut 3, etc.

// Import de fichiers d'en-tête avec #include
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// Pour vos propres fichiers d'en-tête, utilisez les doubles quotes au lieu des crochets sup. et inf.:
//#include "my_header.h"

// Declarez les signatures des fonctions auparavant dans un fichier .h, ou en haut de votre
// fichier .c.
void function_1();
int function_2(void);

// Si vous voulez appeler une fonction avant de la définir,
// vous pouvez déclarer son prototype
// (types des arguments et de la valeur renvoyée)
int add_two_ints(int x1, int x2); // prototype de la fonction
// bien que `int add_two_ints (int, int);` soit également valide (pas besoin de nommer les arguments),
// il est recommandé de nommer les arguments dans le prototype pour faciliter l'inspection.

// Le point d'entrée de votre programme est une fonction appélée
// main avec une sortie de type entier.
int main(void) {
  // votre programme
}

// Les arguments de la ligne de commande utilisés pour exécuter votre programme sont également passés à la fonction main
// argc étant le nombre d'arguments - le nom de votre programme compte pour 1
// argv est un tableau de tableaux de caractères - contenant les arguments eux-mêmes
// argv[0] = nom de votre programme, argv[1] = premier argument, etc.
int main (int argc, char** argv)
{
  // écriture en sortie à l'aide de printf, pour "print formatted"
  // %d est un entier, \n est une nouvelle ligne
  printf("%d\n", 0); // => Prints 0

  ///////////////////////////////////////
  // Types
  ///////////////////////////////////////

  // Les compilateurs qui ne sont pas conformes C99 nécessitent de déclarer
  // les variables en début de portée du bloc de code courant.
  // Les compilateurs qui SONT conformes C99 acceptent les déclarations
  // plus tard dans les fonctions.

  // Les entiers font généralement de 4 octets
  int x_int = 0;

  // Les entiers courts font généralement 2 octets
  short x_short = 0;

  // Les caractères ont une taille fixée à 1 octet
  char x_char = 0;
  char y_char = 'y'; // les caractères littéraux sont entre apostrophes ''

  // Les entiers longs font généralement 4 à 8 octets; les entiers longs de type long font
  // 8 octets
  long x_long = 0;
  long long x_long_long = 0;

  // les nombres à virgule sont généralement des nombres à virgule flottantes de 32 bits
  float x_float = 0.0f; // 'f' est le suffixe qui indique ici le littéral à virgule flottante

  // les nombres de type double sont généralement des nombres à virgule flottante de 64 bits
  double x_double = 0.0; // les nombres réels sans suffixe sont de type double

  // Les types entier peuvent être non signés (plus grand ou égal à zero)
  unsigned short ux_short;
  unsigned int ux_int;
  unsigned long long ux_long_long;

  // Les caractères entre guillemets simples sont des entiers d'un jeu de caractères machine.
  '0'; // => 48 dans le jeu de caractères ASCII.
  'A'; // => 65 dans le jeu de caractères ASCII.

  // sizeof(T) vous rend la taille d'une variable de type T en octets.
  // sizeof obj revoie la taille en sortie de l'expression (variable, littéral, etc.).
  printf("%zu\n", sizeof(int)); // => 4 (sur la plupart des machines les mots font 4 octets)

  // Si l'argument de l'opérateur `sizeof` est une expression, alors son argument
  // n'est pas évalué (sauf les VLA - voir ci-dessous).
  // La valeur qu'elle donne dans ce cas est une constante évaluée à la compilation.
  int a = 1;
  // size_t est un type entier non signé d'au moins 2 octets utilisé pour représenter
  // la taille d'un objet.
  size_t size = sizeof(a++); // a++ n'est pas évalué
  printf("sizeof(a++) = %zu où a = %d\n", size, a);
  // affiche "sizeof(a++) = 4 où a = 1" (sur une architecture 32 bits)

  // Si l'argument de l'opérateur `sizeof` est une expression, alors son argument
  // n'est pas évalué (sauf les VLAs (voir ci-dessous)).
  // La valeur qu'elle donne dans ce cas est une constante évaluée au moment de la compilation.
  int a = 1;
  // size_t est un nombre de type entier non signé dont au moins 2 octets sont utilisés pour représenter
  // la taille d'un objet.
  size_t size = sizeof(a++); // a++ n'est pas évalué
  printf("sizeof(a++) = %zu where a = %d\n", size, a);
  // affiche "sizeof(a++) = 4 where a = 1" (sur une architecture 32-bit)

  // Les tableaux doivent être initialisés avec une taille concrète.
  char my_char_array[20]; // Ce tableau occupe 1 * 20 = 20 octets
  int my_int_array[20]; // Ce tableau occupe 4 * 20 = 80 octets
  // (en considérant des mots de 4 octets)

  // Vous pouvez ainsi initialiser un tableau à 0:
  char my_array[20] = {0};
  // où la partie "{0}" est appelée "initialiseur de tableau".
  // NOTEZ que vous vous en sortez sans déclarer explicitement la taille du tableau,
  // SI vous initialisez le tableau sur la même ligne. Ainsi, la déclaration suivante
  // est équivalent:
  char my_array[] = {0};
  // MAIS, alors vous devez évaluer la taille du tableau au moment de l'exécution, comme ceci:
  size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]);
  // ATTENTION Si vous adoptez cette approche, vous devez évaluer la taille *avant*
  // qui vous commenciez à transmettre le tableau à la fonction (voir la discussion ultérieure), car
  // les tableaux sont "rétrogradés" en pointeurs bruts lorsqu'ils sont passés à des fonctions
  // (donc l'instruction ci-dessus produira le mauvais résultat à l'intérieur de la fonction).

  // L'indexation commence à zero et utilise []
  my_array[0]; // => 0

  // Les tableaux sont modifiables ; c'est juste de la mémoire!
  my_array[1] = 2;
  printf("%d\n", my_array[1]); // => 2

  // En C99 (et optionnellement en C11), des tableaux à taille variable (VLAs)
  // peuvent également être déclarés. La taille d'un tel tableau n'a pas besoin d'être
  // une constante définie lors de la compilation:
  printf("Enter the array size: "); // demande à l'utilisateur la taille du tableau
  int array_size;
  fscanf(stdin, "%d", &array_size);
  int var_length_array[array_size]; // déclare le VLA
  printf("sizeof array = %zu\n", sizeof var_length_array);

  // Exemple:
  // > Enter the array size: 10
  // > sizeof array = 40

  // Les chaînes de caractères sont juste des tableaux de caractères de terminant par octet NULL (0x00),
  // représenté dans les chaînes de caractères par le caractère spécial '\0'.
  // (Nous n'avons pas besoin d'ajouter l'octet NULL dans les chaînes de caractères littérales; le compilateur
  // l'ajoute à la fin du tableau pour nous.)
  char a_string[20] = "This is a string";
  printf("%s\n", a_string); // %s insère une chaîne de caractères

  printf("%d\n", a_string[16]); // => 0
  // i.e., l'octet #17 est 0 (comme le sont 18, 19, et 20)

  // Si nous avons un caractère entre apostrophes, c'est un caractère littéral.
  // Ils sont de types `int`, et *non* `char` (pour des raisons historiques).
  int cha = 'a'; // bon
  char chb = 'a'; // bon aussi (conversion implicite de int vers char)

  // Tableaux dimension multiple:
  int multi_array[2][5] = {
    {1, 2, 3, 4, 5},
    {6, 7, 8, 9, 0}
  };
  // Accés au éléments:
  int array_int = multi_array[0][2]; // => 3

  ///////////////////////////////////////
  // Operateurs
  ///////////////////////////////////////

  // Raccourcis pour plusieurs déclarations:
  int i1 = 1, i2 = 2;
  float f1 = 1.0, f2 = 2.0;

  int b, c;
  b = c = 0;

  // Arithmétique
  i1 + i2; // => 3
  i2 - i1; // => 1
  i2 * i1; // => 2
  i1 / i2; // => 0 (0.5, mais tronqué après 0)

  // Vous devez convertir un entier en type 'float' pour obtenir un résultat en virgule flottante
  (float)i1 / i2; // => 0.5f
  i1 / (double)i2; // => 0.5 // Pareil avec double
  f1 / f2; // => 0.5, plus ou moins epsilon
  // Les nombres à virgule flottante et leurs calculs ne sont pas exacts

  // Modulo est aussi là
  11 % 3; // => 2

  // Les opérateurs de comparaison sont probablement familiers, mais
  // il n'y a pas de type booléen en C. On utilise plutôt des entiers
  // (C99 introduit _Bool ou bool).
  // 0 est faux, tout le reste est vrai.
  // (La comparaison les opérateurs donnent toujours 0 ou 1)
  3 == 2; // => 0 (faux)
  3 != 2; // => 1 (vrai)
  3 > 2; // => 1
  3 < 2; // => 0
  2 <= 2; // => 1
  2 >= 2; // => 1

  // C n'est pas Python - les comparaisons ne s'enchaînent pas.
  // Attention : la ligne ci-dessous se compilera, mais cela signifie `(0 < a) <2`.
  // Cette expression est toujours vraie, car (0 < a) peut être 1 ou 0.
  // Dans ce cas, c'est 1, car (0 < 1).
  int between_0_and_2 = 0 < a < 2;
  // Utilisez plutôt:
  int between_0_and_2 = 0 < a && a < 2;

  // La logique fonctionne avec les entiers
  !3; // => 0 (non logique)
  !0; // => 1
  1 && 1; // => 1 (et logique)
  0 && 1; // => 0
  0 || 1; // => 1 (ou logique)
  0 || 0; // => 0

  // Expression conditionnelle ternaire ( ? : )
  int e = 5;
  int f = 10;
  int z;
  z = (e > f) ? e : f; // => 10 "if e > f return e, else return f."

  // Opérateurs d'incrémentation et de décrémentation :
  int j = 0;
  int s = j++; // Renvoi j PUIS augmente j. (s = 0, j = 1)
  s = ++j; // Augmente j PUIS revoi j. (s = 2, j = 2)
  // pareil avec j-- et --j

  // Opérateurs de manipulation des bits !
  ~0x0F; // => 0xFFFFFFF0 (opérateur de négation, "complémentaire de 1", exemple d'un résultat pour un entier 32-bit)
  0x0F & 0xF0; // => 0x00 (opérateur AND)
  0x0F | 0xF0; // => 0xFF (opérateur OR)
  0x04 ^ 0x0F; // => 0x0B (opérateur XOR)
  0x01 << 1; // => 0x02 (opérateur de décalage à gauche - de 1)
  0x02 >> 1; // => 0x01 (opérateur de décalage à droite - de 1)

  // Soyez prudent lorsque vous effectuez un décalage sur des entiers signés - les éléments suivants ne sont pas définis:
  // - décalage du bit de signe d'un entier signé (int a = 1 << 31)
  // - décalage à gauche d'un nombre négatif (int a = -1 << 2)
  // - décalage par un offset qui est >= à la largeur du type LHS (partie de gauche de l'affectation):
  // int a = 1 << 32; // UB (comportement indéfini) si int a une taille de 32 bits

  ///////////////////////////////////////
  // Structures de contrôle
  ///////////////////////////////////////

  if (0) {
    printf("Je ne serai jamais exécuté\n");
  } else if (0) {
    printf("Moi aussi, je ne serai jamais exécuté\n");
  } else {
    printf("Je suis affiché\n");
  }

  // Les boucles while
  int ii = 0;
  while (ii < 10) { // TOUTE valeur plus petite que dix est vraie.
    printf("%d, ", ii++); // ii++ augmente ii APRES avoir utilisé sa valeur actuelle.
  } // => affiche "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  int kk = 0;
  do {
    printf("%d, ", kk);
  } while (++kk < 10); // ++kk augmente kk AVANT d'utiliser sa valeur actuelle.
  // => affiche "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  // Les boucles for
  int jj;
  for (jj=0; jj < 10; jj++) {
    printf("%d, ", jj);
  } // => affiche "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  // *NOTES* :
  // Les boucles et les fonctions DOIVENT posséder un corps.
  // Vous pouvez toutefois utiliser un bloc vide ({}) ou un point-virgule.
  int i;
  for (i = 0; i <= 5; i++) {
  }
  // Ou
  for (i = 0; i <= 5; i++); // Déconseillé, car facilement confondable

  // condition à choix multiples : switch()
  switch (a) {
  case 0: // les options doivent être des expressions intégrales *constantes* (telles que des énumérations)
    printf("Hey, 'a' equals 0!\n");
    break; // si vous ne mettez pas 'break', le cas d'après est exécuté sans tester le `case`
  case 1:
    printf("Huh, 'a' equals 1!\n");
    break;
    // Attention - sans le "break", l'execution se poursuit jusqu'au
    // prochain "break" atteint.
  case 3:
  case 4:
    printf("Look at that.. 'a' is either 3, or 4\n");
    break;
  default:
    // si `a` ne correspond à aucune option
    fputs("Erreur !\n", stderr);
    exit(-1);
    break;
  }

  // Utilisation de "goto" en C
  typedef enum { false, true } bool;
  bool disaster = false;
  int i, j;
  for(i=0; i<100; ++i)
  for(j=0; j<100; ++j)
  {
    if (i + j >= 150)
        disaster = true;
    if (disaster)
        goto error;
  }
  error:
  printf("Error occurred at i = %d & j = %d.\n", i, j);
  // Ceci affichera "Error occurred at i = 51 & j = 99."

  ///////////////////////////////////////
  // Transtypage
  ///////////////////////////////////////

  // Chaque valeur de C a un type, mais vous pouvez convertir le type d'une valeur en un autre type
  // si vous le souhaitez (avec quelques contraintes).

  int x_hex = 0x01; // Vous pouvez définir des variables avec des littéraux hexadécimaux

  // La conversion entre les types tentera de conserver leurs valeurs numériques
  printf("%d\n", x_hex); // => Affiche 1
  printf("%d\n", (short) x_hex); // => Affiche 1
  printf("%d\n", (char) x_hex); // => Affiche 1

  // Les types déborderont sans avertissement
  printf("%d\n", (unsigned char) 257); // => 1 (max = 255 si le caractère mesure 8 bits)

  // Pour déterminer la valeur maximale d'un `char`, d'un `signed char` et d'un `unsigned char`,
  // respectivement, utilisez les macros CHAR_MAX, SCHAR_MAX et UCHAR_MAX de <limits.h>

  // Les types entiers peuvent être convertis en types à virgule flottante et vice versa.
  printf("%f\n", (double) 100); // %f toujours utilisé pour un type double...
  printf("%f\n", (float)  100); // ...même avec un float.
  printf("%d\n", (char)100.0);

  ///////////////////////////////////////
  // Pointeurs
  ///////////////////////////////////////

  // Un pointeur est une variable déclarée pour stocker une adresse mémoire. Sa déclaration
  // vous indique également le type de données vers lequel il pointe. Vous pouvez récupérer l'adresse mémoire
  // de vos variables, puis jouer avec.

  int x = 0;
  printf("%p\n", (void *)&x); // Utilisez le caractère & pour récupérer l'adresse d'une variable
  // (%p met en forme un objet pointeur de type void *)
  // => Affiche une adresse en mémoire;

  // Les pointeurs sont préfixés du caractère * lors de la déclaration
  int *px, not_a_pointer; // px est un pointeur sur un entier
  px = &x; // Sauvegarde de l'adressse mémoire de x dans px
  printf("%p\n", (void *)px); // => Affiche une adresse mémoire
  printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer));
  // => Affiche "8, 4" sur un système classique 64-bit

  // Pour avoir la valeur située à l'adresse du pointeur,
  // on met * devant pour le déréférencer.
  // Remarque: oui, il peut être déroutant que '*' soit utilisé pour _les deux_ déclarer un
  // pointeur et le déréférencement.
  printf("%d\n", *px); // => Affiche 0, la valeur de x

  // Vous pouvez également modifier la valeur vers laquelle pointe le pointeur.
  // Nous devons mettre la déréférence entre parenthèses car
  // ++ a une priorité plus élevée que *.
  (*px)++; // Augmente de 1 la valeur pointée par px
  printf("%d\n", *px); // => Affiche 1
  printf("%d\n", x); // => Affiche 1

  // Les tableaux sont un bon moyen d'allouer un bloc de mémoire contigu
  int x_array[20]; //déclare un tableau de taille 20 (la taille ne pourra être changée)
  int xx;
  for (xx = 0; xx < 20; xx++) {
    x_array[xx] = 20 - xx;
  } // Initialise x_array à 20, 19, 18,... 2, 1

  // Declare un pointeur sur un type entier et l'initialise pour pointer sur x_array
  int* x_ptr = x_array;
  // x_ptr pointe maintenant vers le premier élément du tableau (l'entier 20).
  // Cela fonctionne car les tableaux s'interprètent souvent comme pointeurs vers leur premier élément.
  // Par exemple, lorsqu'un tableau est transmis à une fonction ou affecté à un pointeur,
  // il est converti implicitement en pointeur.
  // Exceptions : lorsque le tableau est l'argument de l'opérateur `&` (adresse de):
  int arr[10];
  int (*ptr_to_arr)[10] = &arr; // &arr n'est PAS de type `int *`!
  // Il est de type "pointeur sur tableau" (de dix entiers).
  // ou losqu'une chaine de caractères littérale est utilisée pour initialiser un tableau de caractères:
  char otherarr[] = "foobarbazquirk";
  // ou lorsqu'il est l'argument de l'opérateur `sizeof` ou `alignof`:
  int arraythethird[10];
  int *ptr = arraythethird; // équivalent à int *ptr = &arr[0];
  printf("%zu, %zu\n", sizeof(arraythethird), sizeof(ptr));
  // affiche probablement "40, 4" ou "40, 8"

  // L'augmentation ou la diminution d'un pointeur se fait suivant son type
  // (c'est ce qu'on appelle l'arithmétique du pointeur)
  printf("%d\n", *(x_ptr + 1)); // => Affiche 19
  printf("%d\n", x_array[1]); // => Affiche 19

  // You can also dynamically allocate contiguous blocks of memory with the
  // standard library function malloc, which takes one argument of type size_t
  // representing the number of bytes to allocate (usually from the heap, although this
  // may not be true on e.g. embedded systems - the C standard says nothing about it).
  // Vous pouvez également allouer dynamiquement des blocs de mémoire contigus avec la
  // fonction malloc de la bibliothèque standard, qui prend un argument de type size_t
  // représentant le nombre d'octets à allouer (généralement à partir du tas, bien que cela
  // peut ne pas être vrai par exemple dans les systèmes embarqués - la norme C ne dit rien à ce sujet).
  int *my_ptr = malloc(sizeof(*my_ptr) * 20);
  for (xx = 0; xx < 20; xx++) {
    *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
  } // Initialise la mémoire à 20, 19, 18, 17... 2, 1 (comme entiers)

  // Soyez prudent en passant des valeurs fournies par l'utilisateur à malloc! Si vous le souhaitez
  // pour plus de sécurité, vous pouvez utiliser calloc à la place (qui, contrairement à malloc, met également à zéro la mémoire)
  int* my_other_ptr = calloc(20, sizeof(int));

  // Notez qu'il n'y a pas de méthode standard pour obtenir la longueur d'un
  // tableau alloué dynamiquement en C. Pour cette raison, si vos tableaux sont
  // manipulés par votre programme, vous avez besoin d'une autre variable
  // pour garder une trace du nombre d'éléments (taille) d'un tableau. Voir la
  // section fonctions pour plus d'informations.
  size_t size = 10;
  int *my_arr = calloc(size, sizeof(int));
  // Add an element to the array
  size++;
  my_arr = realloc(my_arr, sizeof(int) * size);
  if (my_arr == NULL) {
    //N'oubliez pas de vérifier l'échec de la fonction realloc!
    return 0;
  }
  my_arr[10] = 5;

  // Déréférencer de la mémoire que vous n'avez pas allouée donne des
  // UBs "undefined behaviors" ou "comportement imprévisibles",
  printf("%d\n", *(my_ptr + 21)); // => Peut crash, afficher n'importe quoi, ou autre

  // Lorsque vous avez terminé avec un bloc de mémoire alloué via malloc, vous devez le libérer,
  // ou bien personne d'autre ne pourra l'utiliser jusqu'à la fin de votre programme :
  // (cela s'appelle une "fuite de mémoire")
  free(my_ptr);

  // Les chaînes de caractères sont des tableaux de caractères, mais ils sont généralement représentés comme
  // pointeur-vers-caractère (qui est un pointeur vers le premier élément du tableau).
  // Il est recommandé d'utiliser un `const char *' pour référencer une chaîne littérale de caractères,
  // car les chaînes de caractères littérales ne doivent pas être modifiées (i.e. "foo"[0] = 'a' est INCORRECT.)
  const char *my_str = "This is my very own string literal";
  printf("%c\n", *my_str); // => 'T'

  // Ce n'est pas le cas si la chaîne de caractères est un tableau
  // (potentiellement initialisée avec une chaîne littérale)
  // qui réside dans la mémoire réinscriptible, comme dans:
  char foo[] = "foo";
  foo[0] = 'a'; // ceci est correct, foo contient maintenant "aoo"

  function_1();
} // fin de la fonction main

///////////////////////////////////////
// Fonctions
///////////////////////////////////////

// Sytaxe pour déclarer une fonction:
// <type de sortie> <nom de la fonstion>(<arguments>)

int add_two_ints(int x1, int x2)
{
  return x1 + x2; // Utilisez return pour retourner une valeur de sortie
}

/*
Les fonctions sont appelées par valeur. Lorsqu'une fonction est appelée, les arguments passés à
à la fonction sont une copie des arguments originaux (sauf pour les tableaux). Tout ce que vous
allez faire aux arguments de la fonction ne changera pas la valeur des arguments
originaux avant appel de la fonction.

Utilisez les pointeurs si vous avez besoin de modifier la valeur d'origine des arguments.

Exemple : inversion sur place d'une chaîne de caractères
*/

// Une fonction void ne retourne aucune valeur
void str_reverse(char *str_in)
{
  char tmp;
  size_t ii = 0;
  size_t len = strlen(str_in); // `strlen()` fait partie de la bibliothèque standard c
                               // REMARQUE : la longueur renvoyée par `strlen` N'INCLUT PAS
                               //            l'octet NULL de fin ('\0').
  for (ii = 0; ii < len / 2; ii++) { // en C99 vous pouvez déclarer directement le type de `ii` ici
    tmp = str_in[ii];
    str_in[ii] = str_in[len - ii - 1]; // ii-ème caractère depuis la fin
    str_in[len - ii - 1] = tmp;
  }
}
// REMARQUE : le fichier d'entête string.h a besoin d'être inclus pour utiliser strlen()

/*
char c[] = "This is a test.";
str_reverse(c);
printf("%s\n", c); // => ".tset a si sihT"
*/
/*
car nous ne pouvons renvoyer qu'une seule variable
pour changer les valeurs de plusieurs variables, nous passons des pointeurs
*/
void swapTwoNumbers(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
/*
int first = 10;
int second = 20;
printf("first: %d\nsecond: %d\n", first, second);
swapTwoNumbers(&first, &second);
printf("first: %d\nsecond: %d\n", first, second);
// les valeurs seront échangées
*/

/*
En ce qui concerne les tableaux, ils seront toujours transmis aux fonctions
comme pointeurs. Même si vous allouez statiquement un tableau comme `arr[10]`,
il est toujours passé en tant que pointeur vers le premier élément de tout appel de fonction.
Encore une fois, il n'y a pas de moyen standard pour connaître la taille d'un tableau
alloué dynamiquement en C.
*/
// La taille doit être transmise!
// Sinon, cette fonction n'a aucun moyen de connaître la taille du tableau.
void printIntArray(int *arr, size_t size) {
    int i;
    for (i = 0; i < size; i++) {
        printf("arr[%d] is: %d\n", i, arr[i]);
    }
}
/*
int my_arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int size = 10;
printIntArray(my_arr, size);
// affichera "arr[0] is: 1" etc
*/

// si vous faites référence à des variables externes en dehors de la fonction, vous devez utiliser le mot clé extern.
int i = 0;
void testFunc() {
  extern int i; //i ici utilise la variable externe i
}

// rendre les variables external privées au fichier source avec static:
static int j = 0; //les autres fichiers utilisant testFunc2() n'auront pas accés à la variable j
void testFunc2() {
  extern int j;
}

// Le mot clé static rend une variable inaccessible au code en dehors de
// l'unité de compilation. (Sur presque tous les systèmes, une "unité de compilation" est un
// fichier .c.) static peut être appliqué aussi bien aux variables globales (à l'unité de compilation),
// aux fonctions et aux variables locales aux fonctions. Lorsque vous utilisez static avec
// une variable locale à une fonction, la variable est effectivement globale et conserve sa
// valeur sur les appels de la fonction, mais n'est accessible que dans la fonction où
// elle a été déclarée. De plus, les variables statiques sont initialisées à 0 si pas
// déjà déclaré avec une autre valeur de départ.
// ** Vous pouvez également déclarer des fonctions statiques pour les rendre privées **

/////////////////////////////////////////////////
// Types et structures définis par l'utilisateur
/////////////////////////////////////////////////

// Typedef peuvent être utilisé pour créer un alias de type
typedef int my_type;
my_type my_type_var = 0;

// Struct est juste une collection de données, dont les membres sont positionnés séquentiellement,
// dans l'ordre où ils sont écrits::
struct rectangle {
  int width;
  int height;
};

// Il n'est généralement aps vrai que
// sizeof(struct rectangle) == sizeof(int) + sizeof(int)
// en raison du remplissage potentiel entre les membres de la structure (c'est pour des raisons
// d'alignement). [1]

void function_1()
{
  struct rectangle my_rec;

  // L'accés aux membres de la structure se fait avec .
  my_rec.width = 10;
  my_rec.height = 20;

  // Vous pouvez déclarer un pointeur vers une structure
  struct rectangle *my_rec_ptr = &my_rec;

  // Utiliser le déréférencement pour définir les membres du pointeur de structure...
  (*my_rec_ptr).width = 30;

  // préférez le raccourci -> par souci de lisibilité
  my_rec_ptr->height = 10; // Identique à (*my_rec_ptr).height = 10;
}

// Vous pouvez appliquer un typedef à une structure pour plus de commodité
typedef struct rectangle rect;

int area(rect r)
{
  return r.width * r.height;
}

// si vous avez de grandes structures, vous pouvez les passer "par pointeur" afin d'éviter la copie
// de toute la structure:
int areaptr(const rect *r)
{
  return r->width * r->height;
}

///////////////////////////////////////
// Pointeurs sur fonction
///////////////////////////////////////
/*
Au moment de l'exécution, les fonctions sont situées à des adresses de mémoire connues. Les pointeurs de fonction sont
un peu comme n'importe quel autre pointeur (ils stockent juste une adresse mémoire), mais peuvent être utilisés
pour appeler directement des fonctions et pour transmettre des gestionnaires de fonctions (ou des fonctions de rappel).
Cependant, la syntaxe de définition peut être source de confusion au départ.

Exemple: Utilisation de str_reverse à partir d'un pointeur
*/
void str_reverse_through_pointer(char *str_in) {
  // Définit une variable pointeur sur fonction, nommée f.
  void (*f)(char *); // La signature doit être la même que la fonction cible.
  f = &str_reverse; // Assigne l'adresse de la fonction réelle (déterminé au runtime)
  // f = str_reverse; marcherait aussi - les fonctions s'interprètent en pointeurs, comme les tableaux
  (*f)(str_in); // Il suffit d'appeler la fonction via le pointeur
  // f(str_in); // C'est une syntaxe alternative mais tout aussi valide pour l'appel.
}

/*
Tant que les signatures de fonction correspondent, vous pouvez affecter n'importe quelle fonction au même pointeur.
Les pointeurs de fonction sont généralement typés via typedef pour plus de simplicité et de lisibilité, comme suit:
*/

typedef void (*my_fnp_type)(char *);

// Puis utilisé lors de la déclaration de la variable réelle de pointeur:
// ...
// my_fnp_type f;


//Caractères spéciaux:
/*
'\a'; // caractère d'alerte (cloche)
'\n'; // caractère de nouvelle ligne
'\t'; // caractère de tabulation (texte justifié à gauche)
'\v'; // tabulation verticale
'\f'; // nouvelle page (flux de formulaire)
'\r'; // retour chariot
'\b'; // caractère de retour arrière
'\0'; // Caractère NULL. Habituellement mis à la fin des chaînes en C.
// bonjour\n\0. \0 utilisé par convention pour marquer la fin de la chaîne.
'\\'; // barre oblique inverse
'\?'; // point d'interrogation
'\' '; // simple guillemet
'\"'; // double quillemets
'\xhh'; // nombre hexadécimal. Exemple: '\xb' = caractère de tabulation verticale
'\0oo'; // nombre octal. Exemple: '\013' = caractère de tabulation verticale

//Format d'affichage:
"%d";    // entier
"%3d";   // entier avec une taille minimum de 3 digits (texte justifié à droite)
"%s";    // chaîne de caractères
"%f";    // nombre à virgule flottante
"%ld";   // nombre long
"%3.2f"; // minimum de 3 digits à gauche et 2 digits à droite nombre décimal à virgule flottante
"%7.4s"; // (peut être fait aussi avec une chaîne de caractères)
"%c";    // caratère
"%p";    // pointeur. REMARQUE: il est nécessaire de caster en (void *) le pointeur, avant de la passer
         //                comme argument à `printf`.
"%x";    // hexadecimal
"%o";    // octal
"%%";    // affiche %
*/

///////////////////////////////////////
// Ordre des évaluations
///////////////////////////////////////

//--------------------------------------------------------//
//        Operateurs                 | Associativité      //
//--------------------------------------------------------//
// () [] -> .                        | de gauche à droite //
// ! ~ ++ -- + = *(type)sizeof       | de droite à gauche //
// * / %                             | de gauche à droite //
// + -                               | de gauche à droite //
// << >>                             | de gauche à droite //
// < <= > >=                         | de gauche à droite //
// == !=                             | de gauche à droite //
// &                                 | de gauche à droite //
// ^                                 | de gauche à droite //
// |                                 | de gauche à droite //
// &&                                | de gauche à droite //
// ||                                | de gauche à droite //
// ?:                                | de droite à gauche //
// = += -= *= /= %= &= ^= |= <<= >>= | de droite à gauche //
// ,                                 | de gauche à droite //
//--------------------------------------------------------//

/******************************* Fichiers en-têtes **********************************

Les fichiers d'en-tête sont une partie importante de C car ils permettent l'interconnexion
des fichiers source C ce qui permet de simplifier le code et les définitions en les séparant
dans des fichiers séparés.

Les fichiers d'en-tête sont syntaxiquement similaires aux fichiers source C mais résident dans
des fichiers ".h". Ils peuvent être inclus dans votre fichier source C en utilisant la commande
du précompilateur #include "example.h", avec example.h existant dans le même répertoire
que le fichier C.
*/

/* Un garde-fou pour éviter que l'en-tête ne soit défini trop de fois. Ce          */
/* qui se produit dans le cas de dépendance cyclique, et que le contenu du fichier */
/* d'en-tête est déjà défini.                                                      */
#ifndef EXAMPLE_H /* si EXAMPLE_H n'est pas déjà défini. */
#define EXAMPLE_H /* Definit la macro EXAMPLE_H. */

/* Des en-têtes peuvent être inclus dans d'autres en-têtes et donc par transitivité */
/* être inclus dans des fichiers qui incluent un en-tête.                           */
#include <string.h>

/* Les macros de fichiers source c peuvent être définies dans les en-têtes et utilisées dans les fichiers */
/* qui incluent ce fichier d'en-tête.                                                                     */
#define EXAMPLE_NAME "Dennis Ritchie"

/* Les macros de fonction peuvent aussi être définies.  */
#define ADD(a, b) ((a) + (b))

/* Remarquez les parenthèses entourant les arguments - c'est important pour                 */
/* assurer que a et b ne soient pas développés de manière inattendue (par exemple, pensez à */
/* MUL (x, y) (x * y); MUL (1 + 2, 3) s'étendrait à (1 + 2 * 3), produisant un              */
/* résultat incorrect)                                                                      */

/* Les structures et les typedefs peuvent être utilisés pour la cohérence entre les fichiers. */
typedef struct Node
{
    int val;
    struct Node *next;
} Node;

/* Il en va de même pour les énumérations. */
enum traffic_light_state {GREEN, YELLOW, RED};

/* Les prototypes de fonctions peuvent également être définis ici pour une utilisation dans plusieurs fichiers, */
/* mais c'est une mauvaise pratique de définir la fonction dans l'en-tête. Les définitions                      */
/* devraient plutôt être placées dans un fichier C.                                                             */
Node createLinkedList(int *vals, int len);

/* Au-delà des éléments ci-dessus, les autres définitions devraient être placées dans un fichier source C */
/* Les inclusions ou définitions excessives ne devraient pas non plus figurer dans                        */
/* un fichier d'en-tête mais placées plutôt dans des en-têtes séparés ou un fichier C.                    */

#endif /* Fin de la directive if du precompilateur. */

Lectures complémentaires

Il est très important d'utiliser un espacement et une indentation appropriés et d'être cohérent avec votre style de codage en général. Un code lisible est meilleur qu'un code intelligent et un code rapide. Pour un bon style de codage sain à adopter, consultez le Linux kernel coding style.

[1] Why isn’t sizeof for a struct equal to the sum of sizeof of each member?


Vous avez une suggestion ? Peut-être une correction ? Ouvrez un ticket sur GitHub, ou faites vous-même une pull request !

Version originale par Adam Bard, mis à jour par 9 contributeur(s).