Leçon 1

C

  • Aujourd'hui, nous allons apprendre un nouveau langage, C : un langage de programmation qui possède toutes les fonctionnalités de Scratch et plus encore, mais peut-être un peu moins convivial puisqu'il est uniquement en texte :

      #include <stdio.h>
    
      int main(void)
      {
          printf("hello, world\n");
      }
    
    • Bien que les mots soient nouveaux, les idées sont exactement les mêmes que les blocs « lorsque le drapeau vert est cliqué » et « dire (hello, world) » dans Scratch : bloc intitulé 'lorsque le drapeau vert est cliqué', bloc intitulé 'dire (hello, world)'
  • Bien que cryptique, n'oubliez pas que 2/3 des étudiants de CS50 n'ont jamais suivi de CS auparavant, alors ne soyez pas intimidé ! Et bien qu'au début, pour emprunter une expression du MIT, essayer d'absorber tous ces nouveaux concepts puisse vous donner l'impression de boire à même une bouche d'incendie, soyez assuré qu'à la fin du semestre, nous aurons acquis les compétences et l'expérience nécessaires pour apprendre et appliquer ces concepts.

  • Nous pouvons comparer de nombreuses constructions en C avec des blocs que nous avons déjà vus et utilisés dans Scratch. La syntaxe est bien moins importante que les principes, auxquels nous avons déjà été initiés.

bonjour, le monde

  • Le bloc « quand le drapeau vert est cliqué » dans Scratch démarre le programme principal ; le clic sur le drapeau vert déclenche l'exécution du bon jeu de blocs ci-dessous. En C, la première ligne pour le même est « int main(vide) », que nous apprendrons plus en détail au cours des prochaines semaines, suivie d'une accolade ouvrante « { » et d'une accolade fermante « } », englobant tout ce qui devrait être dans le programme.

      int main(vide)
      {
    
      }
    
  • Le bloc « dire (bonjour, le monde) » est une fonction, et correspond à « printf("bonjour, le monde"); ». En C, la fonction pour imprimer quelque chose à l'écran est « printf », où « f » signifie « format », ce qui signifie que nous pouvons formater la chaîne imprimée de différentes manières. Ensuite, nous utilisons des parenthèses pour transmettre ce que nous voulons imprimer. Nous devons utiliser des guillemets doubles pour encadrer notre texte afin qu'il soit compris comme du texte, et enfin, nous ajoutons un point-virgule « ; » pour terminer cette ligne de code.

  • Pour que notre programme fonctionne, nous avons également besoin d'une autre ligne en haut, une ligne d'en-tête « #include » qui définit la fonction « printf » que nous voulons utiliser. Quelque part sur notre ordinateur, il existe un fichier, « stdio.h », qui inclut le code qui nous permet d'accéder à la fonction « printf », et la ligne « #include » indique à l'ordinateur d'inclure ce fichier dans notre programme.

  • Pour écrire notre premier programme en Scratch, nous avons ouvert le site Web de Scratch. De la même manière, nous utiliserons le [bac à sable CS50] (https://sandbox.cs50.io/) pour commencer à écrire et à exécuter du code de la même manière. Le bac à sable CS50 est un environnement virtuel basé sur le cloud avec les bibliothèques et les outils déjà installés pour écrire des programmes dans différentes langues. En haut, il y a un simple éditeur de code, dans lequel nous pouvons taper du texte. Ci-dessous, nous avons une fenêtre de terminal, dans laquelle nous pouvons taper des commandes :
    ![deux panneaux, le haut intitulé hello.c, le bas intitulé Terminal] (https://cs50.harvard.edu/x/2020/notes/1/cs50_sandbox.png)

  • Nous taperons notre code précédent dans la partie supérieure, après avoir utilisé le signe « + » pour créer un nouveau fichier appelé « hello.c » :
    ![bonjour, le monde dans l'éditeur] (https://cs50.harvard.edu/x/2020/notes/1/editor.png)

  • Nous terminons le fichier de notre programme par « .c » par convention, pour indiquer qu'il est destiné à un programme C. Notez que notre code est coloré, de sorte que certaines choses sont plus visibles.

Compilateurs

  • Une fois que nous avons enregistré le code que nous avons écrit, qui est appelé code source, nous devons le convertir en code machine, des instructions binaires que l'ordinateur comprend directement.
  • Nous utilisons un programme appelé compilateur pour compiler notre code source en code machine.
  • Pour ce faire, nous utilisons le panneau Terminal, qui dispose d'une invite de commande. Le $ à gauche est une invite, après laquelle nous pouvons taper des commandes.
    • Nous tapons clang hello.c (où clang signifie « langages C », un compilateur écrit par un groupe de personnes). Mais avant d'appuyer sur Entrée, nous cliquons sur l'icône de dossier en haut à gauche de CS50 Sandbox. Nous voyons notre fichier, hello.c. Nous appuyons donc sur Entrée dans la fenêtre du terminal et voyons que nous avons maintenant un autre fichier, appelé a.out (abréviation de « sortie d'assemblage »). À l'intérieur de ce fichier se trouve le code de notre programme, en binaire. Nous pouvons maintenant taper ./a.out dans l'invite du terminal pour exécuter le programme a.out dans notre dossier actuel. Nous venons d'écrire, de compiler et d'exécuter notre premier programme !

Chaîne de caractères

  • Cependant, après avoir exécuté notre programme, nous voyons hello, world$, avec la nouvelle invite sur la même ligne que notre sortie. Il s'avère que nous devons spécifier précisément que nous avons besoin d'une nouvelle ligne après notre programme, nous pouvons donc mettre à jour notre code pour inclure un caractère de nouvelle ligne spécial, \n :

      #include <stdio.h>
    
      int main(void)
      {
          printf("hello, world\n");
      }
    
    • Maintenant, nous devons penser à recompiler notre programme avec clang hello.c avant de pouvoir exécuter cette nouvelle version.
  • La ligne 2 de notre programme est intentionnellement vide puisque nous voulons démarrer une nouvelle section de code, un peu comme on commence de nouveaux paragraphes dans les essais. Ce n'est pas strictement nécessaire pour que notre programme s'exécute correctement, mais cela aide les humains à lire plus facilement des programmes plus longs.

  • Nous pouvons également changer le nom de notre programme de a.out à autre chose. Nous pouvons passer des arguments de ligne de commande, ou des options supplémentaires, aux programmes dans le terminal, en fonction de ce que le programme est écrit pour comprendre. Par exemple, nous pouvons taper clang -o hello hello.c, et -o hello indique au programme clang d'enregistrer la sortie compilée en tant que hello. Ensuite, nous pouvons simplement exécuter ./hello.
  • Dans notre invite de commande, nous pouvons exécuter d'autres commandes, comme ls (liste), qui affiche les fichiers dans notre dossier actuel :

      $ ls
      a.out* hello* hello.c
    
    • L'astérisque, *, indique que ces fichiers sont exécutables, ou qu'ils peuvent être exécutés par notre ordinateur.
  • Nous pouvons utiliser la commande rm (supprimer) pour supprimer un fichier :

      $ rm a.out
      rm: supprimer le fichier régulier 'a.out' ?
    
    • Nous pouvons taper y ou yes pour confirmer, et utiliser à nouveau ls pour voir qu'il a effectivement disparu.
  • Maintenant, essayons d'obtenir des informations de l'utilisateur, comme nous l'avons fait dans Scratch lorsque nous voulions dire « bonjour, David » :
    Capture d'écran de blocs « demander quel est votre nom ? et attendre », « dire joindre bonjour, réponse »

      string answer = get_string("What's your name?\n");
      printf("hello, %s\n", answer);
    
    • Tout d'abord, nous avons besoin d'une chaîne de caractères, ou d'un morceau de texte (plus précisément, zéro ou plusieurs caractères dans une séquence entre guillemets doubles, comme "", "ba" ou "bananes"), que nous pouvons demander à l'utilisateur, avec la fonction get_string. Nous passons l'invite, ou ce que nous voulons demander à l'utilisateur, à la fonction avec "What is your name?\n" entre parenthèses. Sur la gauche, nous voulons créer une variable, answer, dont la valeur sera ce que l'utilisateur saisit. (Le signe égal = définit la valeur de droite à gauche.) Enfin, le type de variable que nous voulons est string, nous précisons donc cela à gauche de answer.
    • Ensuite, à l'intérieur de la fonction printf, nous voulons la valeur de answer dans ce que nous imprimons en retour. Nous utilisons un espace réservé pour notre variable de chaîne, %s, dans la phrase que nous voulons imprimer, comme "hello, %s\n", puis nous donnons à printf un autre argument, ou une option, pour lui dire que nous voulons que la variable answer soit remplacé.
  • Si nous avons fait une erreur, comme écrire printf("hello, world"\n); avec \n en dehors des guillemets doubles pour notre chaîne, nous verrons une erreur de notre compilateur :

      $ clang -o hello hello.c
      hello.c:5:26: error: expected ')'
          printf("hello, world"\n);
                               ^
      hello.c:5:11: note: to match this '('
          printf("hello, world"\n);
                ^
      1 error generated.
    
    • La première ligne de l'erreur nous dit de regarder hello.c, ligne 5, colonne 26, où le compilateur attendait une parenthèse fermante, au lieu d'une barre oblique inverse.
  • Pour simplifier les choses (du moins pour le début), nous inclurons une bibliothèque, ou un ensemble de code, de CS50. La bibliothèque nous fournit le type de variable string, la fonction get_string, et plus encore. Il suffit d'écrire une ligne en haut pour inclure le fichier cs50.h :

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          string name = get_string("What's your name?\n");
          printf("hello, name\n");
      }
    
  • Créons donc un nouveau fichier, string.c, avec ce code :

      #include <stdio.h>
    
      int main(void)
      {
          string name = get_string("What's your name?\n");
          printf("hello, %s\n", name);
      }
    
  • Maintenant, si nous essayons de compiler ce code, nous obtenons de nombreuses erreurs. Parfois, une erreur signifie que le compilateur commence alors à interpréter le code correct de manière incorrecte, générant plus d'erreurs qu'il n'y en a réellement. Nous commençons donc par notre première erreur :

      $ clang -o string string.c
      string.c:5:5: error: use of undeclared identifier 'string'; did you mean 'stdin'?
        string name = get_string("What's your name?\n");
        ^~~~~~
        stdin
      /usr/include/stdio.h:135:25: note: 'stdin' declared here
      extern struct _IO_FILE *stdin;          /* Standard input stream.  */
    
    • Nous ne voulions pas dire stdin (« entrée standard ») au lieu de string, donc ce message d'erreur n'était pas utile. En fait, nous devons importer un autre fichier qui définit le type string (en réalité une roue d'entraînement de CS50, comme nous le découvrirons dans les semaines à venir).
  • Nous pouvons donc inclure un autre fichier, cs50.h, qui inclut également la fonction get_string, entre autres.

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          string name = get_string("What's your name?\n");
          printf("hello, %s\n", name);
      }
    
  • Maintenant, lorsque nous essayons de compiler notre programme, nous n'avons qu'une seule erreur :

      $ clang -o string string.c
      /tmp/string-aca94d.o: In function `main':
      string.c:(.text+0x19): undefined reference to `get_string'
      clang-7: error: linker command failed with exit code 1 (use -v to see invocation)
    
    • Il s'avère que nous devons également indiquer à notre compilateur d'ajouter notre fichier de bibliothèque CS50 spécial, avec clang -o string string.c -lcs50, avec -l pour « lier ».
  • Nous pouvons même faire abstraction de cela et simplement taper make string. Nous voyons que, par défaut dans le bac à sable CS50, make utilise clang pour compiler notre code à partir de string.c en string, avec tous les arguments, ou drapeaux, nécessaires transmis.

Blocs Scratch en C

  • Le bloc “mettre [compteur] à (0)” crée une variable ; en C, nous écririons int compteur = 0 ;, où int précise que notre variable est un nombre entier :
    bloc nommé 'mettre compteur à (0)'

  • “incrémenter [compteur] de (1)” équivaut en C à compteur = compteur + 1 ;. (En C, le symbole = n’est pas un signe d’égalité, comme dans une équation où compteur serait égal à compteur + 1. Au lieu de cela, = est un opérateur d’affectation qui signifie « copier la valeur de droite dans la valeur de gauche ».) Et remarquez que nous n’avons plus besoin de préciser int, car nous supposons que nous avons déjà spécifié auparavant que compteur est un int, doté d’une valeur existante. Nous pouvons également écrire compteur += 1 ; ou compteur++ ; qui sont tous deux du « sucre syntaxique », à savoir des raccourcis qui ont le même effet avec moins de caractères à taper.
    bloc nommé 'incrémenter compteur de (1)'

  • Une condition correspondrait à :
    bloc nommé 'si < (x) < (y) > alors', qui contient un bloc nommé 'dire (x est inférieur à y)'

      if (x < y)
      {
          printf("x est inférieur à y\n");
      }
    
    • Remarquez qu’en C nous utilisons les caractères { et } (ainsi que l’indentation) pour indiquer comment les lignes de code doivent être imbriquées.
  • Nous pouvons également avoir des conditions if-else :
    bloc nommé 'si < (x) < (y) > alors', qui contient un bloc nommé 'dire (x est inférieur à y)', qui contient également un bloc 'sinon', contenant un bloc nommé 'dire (x n'est pas inférieur à y)'

      if (x < y)
      {
          printf("x est inférieur à y\n");
      }
      else
      {
          printf("x n'est pas inférieur à y\n");
      }
    
    • Remarquez que les lignes de code qui ne constituent pas elles-mêmes une action (if... et les accolades) ne se terminent pas par un point-virgule.
  • Et même else if : bloc nommé 'si < (x) < (y) > alors', qui contient un bloc nommé 'dire (x est inférieur à y)', qui contient également un bloc 'sinon', à l'intérieur duquel se trouve un bloc imbriqué nommé 'si < (x) > (y) > alors', contenant un bloc nommé 'dire (x est supérieur à y)', qui contient également un bloc 'sinon', à l'intérieur duquel se trouve un bloc nommé 'si < (x) = (y) > alors', contenant un bloc nommé 'dire (x est égal à y)'

      if (x < y)
      {
          printf("x est inférieur à y\n");
      }
      else if (x > y)
      {
          printf("x est supérieur à y\n");
      }
      else if (x == y)
      {
          printf("x est égal à y\n");
      }
    
    • Remarquez que pour comparer deux valeurs en C, nous utilisons ==, soit deux signes égal.
    • Et logiquement, nous n’avons pas besoin du if (x == y) dans la condition finale, car c’est le seul cas restant et nous pouvons simplement dire else.
  • Les boucles peuvent être écrites comme suit :
    bloc nommé 'pour toujours', qui contient un bloc nommé 'dire (bonjour, monde)'

      while (true)
      {
          printf("bonjour, monde\n");
      }
    
    • Le mot-clé while nécessite également une condition, nous utilisons donc true comme expression booléenne pour nous assurer que notre boucle fonctionnera indéfiniment. Notre programme vérifiera si l’expression est évaluée à true (ce qui sera toujours le cas ici), puis exécutera les lignes à l’intérieur des accolades. Ensuite, il répétera cette opération jusqu’à ce que l’expression ne soit plus vraie (ce qui ne changera pas dans ce cas).
  • Nous pourrions faire quelque chose un certain nombre de fois avec while :
    bloc nommé 'répéter (50)', qui contient un bloc nommé 'dire (bonjour, monde)'

      int i = 0;
      while (i < 50)
      {
          printf("bonjour, monde\n");
          i++;
      }
    
    • Nous créons une variable i et la définissons sur 0. Ensuite, tant que i < 50, nous exécutons des lignes de code, et nous ajoutons 1 à i après chaque exécution.
    • Les accolades autour des deux lignes à l’intérieur de la boucle while indiquent que ces lignes se répéteront, et nous pouvons ajouter des lignes à notre programme après si nous le souhaitons.
  • Pour effectuer la même répétition, nous pouvons plus couramment utiliser le mot-clé for :

      for (int i = 0; i < 50; i++)
      {
          printf("bonjour, monde\n");
      }
    
    • Encore une fois, nous créons d’abord une variable nommée i et la définissons sur 0. Ensuite, nous vérifions que i < 50 à chaque fois que nous atteignons le début de la boucle, avant d’exécuter le code à l’intérieur. Si cette expression est vraie, alors nous exécutons le code à l’intérieur. Enfin, après avoir exécuté le code à l’intérieur, nous utilisons i++ pour ajouter un à i et la boucle se répète.

Types, formats, operateurs

  • On peut utiliser d'autres types pour nos variables :
    • bool, une expression booléenne de valeur true ou false
    • char, un seul caractère comme a ou 2
    • double, une valeur à virgule flottante avec encore plus de chiffres
    • float, une valeur à virgule flottante, ou un nombre réel avec une valeur décimale
    • int, des entiers jusqu'à une certaine taille, ou nombre de bits
    • long, des entiers avec plus de bits, ce qui leur permet de compter plus haut
    • string, une chaîne de caractères
  • Et la bibliothèque CS50 dispose des fonctions correspondantes pour obtenir des entrées de différents types :
    • get_char
    • get_double
    • get_float
    • get_int
    • get_long
    • get_string
  • Pour printf aussi, il existe différents espaces réservés pour chaque type :
    • %c pour les caractères
    • %f pour les flottants, doubles
    • %i pour les entiers
    • %li pour les longs
    • %s pour les chaînes
  • Et il existe des opérateurs mathématiques que nous pouvons utiliser :
    • + pour l'addition
    • - pour la soustraction
    • * pour la multiplication
    • / pour la division
    • % pour le reste

Plus d'exemples

  • Pour chacun de ces exemples, vous pouvez cliquer sur les liens du bac à sable pour lancer et modifier vos propres copies.
  • Dans int.c, nous récupérons et affichons un entier :

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          int age = get_int("Quel âge avez-vous ?\n");
          int jours = age * 365;
          printf("Vous avez au moins %i jours.\n", jours);
      }
    
    • Notez que nous utilisons %i pour afficher un entier.
    • Nous pouvons maintenant lancer make int et lancer notre programme avec ./int.
    • Nous pouvons combiner des lignes et supprimer la variable jours avec :

        int age = get_int("Quel âge avez-vous ?\n");
        printf("Vous avez au moins %i jours.\n", age * 365);
      
    • Ou même tout combiner sur une ligne :

        printf("Vous avez au moins %i jours.\n", get_int("Quel âge avez-vous ?\n") * 365);
      
    • Cependant, si une ligne devient trop longue ou trop compliquée, il peut être préférable de conserver deux ou trois lignes pour plus de lisibilité.

  • Dans float.c, nous pouvons récupérer des nombres décimaux (ce qu'on appelle des valeurs à virgule flottante en informatique, parce que la virgule décimale peut « flotter » entre les chiffres, en fonction du nombre ):

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          float prix = get_float("Quel est le prix ?\n");
          printf("Votre total est de %f.\n", prix * 1,0625);
      }
    
    • Maintenant, si nous compilons et lançons notre programme, nous verrons un prix affiché avec la taxe.
    • Nous pouvons spécifier le nombre de chiffres imprimés après la virgule décimale avec un espace réservé tel que %.2f pour deux chiffres après la virgule décimale.
  • Avec parity.c, nous pouvons vérifier si un nombre est pair ou impair :

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          int n = get_int("n : ");
    
          if (n % 2 == 0)
          {
              printf("pair\n");
          }
          else
          {
              printf("impair\n");
          }
      }
    
    • Avec l'opérateur % (modulo), nous pouvons obtenir le reste de n après sa division par 2. Si le reste est 0, nous savons que n est pair. Sinon, nous savons que n est impair.
    • De plus, des fonctions comme get_int de la bibliothèque CS50 effectuent une vérification des erreurs, où seules les entrées de l'utilisateur correspondant au type que nous voulons sont acceptées.
  • Dans conditions.c, nous transformons les extraits de conditions précédents en un programme :

      // Conditions et opérateurs relationnels
    
      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          // Demander à l'utilisateur de saisir x
          int x = get_int("x : ");
    
          // Demander à l'utilisateur de saisir y
          int y = get_int("y : ");
    
          // Comparer x et y
          if (x < y)
          {
              printf("x est inférieur à y\n");
          }
          else if (x > y)
          {
              printf("x est supérieur à y\n");
          }
          else
          {
              printf("x est égal à y\n");
          }
      }
    
    • Les lignes commençant par // sont des commentaires ou des notes pour les humains que le compilateur ignorera.
    • Pour que David compile et exécute ce programme dans son bac à sable, il devait d'abord exécuter cd src1 dans le terminal. Cela change le répertoire, ou dossier, en celui dans lequel il a enregistré tous les fichiers sources du cours. Ensuite, il pouvait exécuter make conditions et ./conditions. Avec pwd, il peut voir qu'il se trouve dans un dossier src1 (à l'intérieur d'autres dossiers). Et cd tout seul, sans arguments, nous ramènera à notre dossier par défaut dans le bac à sable.
  • Dans agree.c, nous pouvons demander à l'utilisateur de confirmer ou d'infirmer quelque chose :

      // Opérateurs logiques
    
      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          // Demander à l'utilisateur d'accepter
          char c = get_char("Êtes-vous d'accord ?\n");
    
          // Vérifier si l'utilisateur est d'accord
          if (c == 'Y' || c == 'y')
          {
              printf("D'accord.\n");
          }
          else if (c == 'N' || c == 'n')
          {
              printf("Pas d'accord.\n");
          }
      }
    
    • Nous utilisons deux barres verticales, ||, pour indiquer un « ou » logique, indiquant que l'une ou l'autre expression peut être vraie pour que la condition soit suivie.
    • Et si aucune des expressions n'est vraie, rien ne se passera puisque notre programme n'a pas de boucle.
  • Mettons en oeuvre le programme de toux depuis la semaine 0 :

      #include <stdio.h>
    
      int main(void)
      {
          printf("toux\n");
          printf("toux\n");
          printf("toux\n");
      }
    
  • Nous pourrions utiliser une boucle for :

      #include <stdio.h>
    
      int main(void)
      {
          for (int i = 0; i < 3; i++)
          {
              printf("toux\n");
          }
      }
    
    • Par convention, les programmeurs ont tendance à commencer à compter à 0, ainsi i aura le valeurs 0, 1 et 2 avant de s'arrêter, pour un total de trois itérations. Nous pourrions également écrire for (int i = 1; i <= 3; i++) pour le même effet final.
  • Nous pouvons déplacer la ligne printf vers sa propre fonction:

      #include <stdio.h>
    
      void toux(void);
    
      int main(void)
      {
          for (int i = 0; i < 3; i++)
          {
              toux();
          }
      }
    
      void toux(void)
      {
          printf("toux\n");
      }
    
    • Nous déclarons une nouvelle fonction avec void toux(void);, avant que notre fonction main ne l'appelle. Le compilateur C lit notre code de haut en bas, nous devons donc lui indiquer que la fonction toux existe, avant que nous l'utilisions. Puis, après notre fonction main, nous pouvons implémenter la fonction toux. De cette façon, le compilateur sait que la fonction existe, et nous pouvons garder notre fonction main près du début.
    • Et notre fonction toux ne prend aucune entrée, nous avons donc toux(void).
  • Nous pouvons abstraire d'avantage toux :

      #include <stdio.h>
    
      void toux(int n);
    
      int main(void)
      {
          toux(3);
      }
    
      void toux(int n)
      {
          for (int i = 0; i < n; i++)
          {
              printf("toux\n");
          }
      }
    
    • Maintenant, quand nous voulons afficher "toux" un certain nombre de fois, nous pouvons simplement appeler la même fonction. Notez qu'avec void toux(int n), nous indiquons que la fonction toux prend en entrée un int, auquel nous nous référons comme n. Et à l'intérieur de toux, nous utilisons n dans notre boucle for pour afficher "toux" le bon nombre de fois.
  • Regardons positive.c :

      #include <cs50.h>
      #include <stdio.h>
    
      int get_positive_int(void);
    
      int main(void)
      {
          int i = get_positive_int();
          printf("%i\n", i);
      }
    
      // Demander à l'utilisateur un entier positif
      int get_positive_int(void)
      {
          int n;
          do
          {
              n = get_int("%s", "Entier positif : ");
          }
          while (n < 1);
          return n;
      }
    
    • La bibliothèque CS50 n'a pas de fonction get_positive_int, mais nous pouvons en écrire une nous-mêmes. Notre fonction int get_positive_int(void) demande à l'utilisateur un int et retourne cet int, que notre fonction main stocke comme i. Dans get_positive_int, nous initialisons une variable, int n, sans encore lui assigner de valeur. Puis, nous avons une nouvelle construction, do ... while, qui fait quelque chose en premier, puis vérifie une condition, et répète cela jusqu'à ce que la condition ne soit plus vraie.
    • Une fois que la boucle se termine parce qu'on a un n qui n'est pas < 1, on peut le retourner avec le mot-clé return. Et de retour dans notre fonction main, on peut définir int i sur cette valeur.

Écrans

  • Nous pourrions avoir besoin d'un programme qui imprime une partie d'écran d'un jeu vidéo comme Super Mario Bros. Dans mario0.c, nous avons :

      // Affiche une ligne de 4 points d'interrogation
    
      #include <stdio.h>
    
      int main(void)
      {
          printf("????\n");
      }
    
  • Nous pouvons demander à l'utilisateur un nombre de points d'interrogation, puis les imprimer avec mario2.c :

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          int n;
          do
          {
              n = get_int("Largeur : ");
          }
          while (n < 1);
          for (int i = 0; i < n; i++)
          {
              printf("?");
          }
          printf("\n");
      }
    
  • Et nous pouvons imprimer un ensemble bidimensionnel de blocs avec mario8.c :

      // Affiche une grille de briques n-par-n avec une boucle
    
      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          int n;
          do
          {
              n = get_int("Taille : ");
          }
          while (n < 1);
          for (int i = 0; i < n; i++)
          {
              for (int j = 0; j < n; j++)
              {
                  printf("#");
              }
              printf("\n");
          }
      }
    
  • Notez que nous avons deux boucles imbriquées, où la boucle externe utilise i pour tout faire à l'intérieur n fois, et la boucle interne utilise j, une variable différente, pour faire quelque chose n fois pour chacune de ces fois. En d'autres termes, la boucle externe imprime n « lignes » ou lignes, et la boucle interne imprime n « colonnes » ou caractères # dans chaque ligne.

  • D'autres exemples non abordés en cours sont disponibles sous « Code source » pour Semaine 1.

Mémoire, imprécision et dépassement de capacité

  • Notre ordinateur possède une mémoire, dans des puces de matériel appelées RAM, mémoire à accès aléatoire. Nos programmes utilisent cette RAM pour stocker des données pendant leur exécution, mais cette mémoire est finie. Donc, avec un nombre fini de bits, nous ne pouvons pas représenter tous les nombres possibles (dont il existe un nombre infini). Ainsi, notre ordinateur dispose d'un certain nombre de bits pour chaque flottant et entier, et doit arrondir à une valeur décimale donnée à un certain moment.
  • Avec floats.c, nous pouvons voir ce qui se passe lorsque nous utilisons des flottants :

      #include <cs50.h>
      #include <stdio.h>
    
      int main(void)
      {
          // Invite l'utilisateur à saisir x
          float x = get_float("x: ");
    
          // Invite l'utilisateur à saisir y
          float y = get_float("y: ");
    
          // Effectue une division
          printf("x / y = %.50f\n", x / y);
      }
    
    • Avec %50f, nous pouvons spécifier le nombre de décimales affichées.
    • Hum, maintenant nous obtenons …

        x: 1
        y: 10
        x / y = 0.10000000149011611938476562500000000000000000000000
      
    • Il s'avère que cela s'appelle l'imprécision en virgule flottante, où nous n'avons pas assez de bits pour stocker toutes les valeurs possibles, de sorte que l'ordinateur doit stocker la valeur la plus proche possible de 1 divisé par 10.

  • Nous pouvons voir un problème similaire dans overflow.c :

      #include <stdio.h>
      #include <unistd.h>
    
      int main(void)
      {
          for (int i = 1; ; i *= 2)
          {
              printf("%i\n", i);
              sleep(1);
          }
      }
    
    • Dans notre boucle pour, nous définissons i sur 1 et le multiplions par *= 2. (Et nous allons continuer à le faire indéfiniment, donc il n'y a pas de condition que nous vérifions.)
    • Nous utilisons également la fonction sleep de unistd.h pour permettre à notre programme de faire une pause à chaque fois.
    • Maintenant, lorsque nous exécutons ce programme, nous voyons le nombre augmenter de plus en plus, jusqu'à :

        1073741824
        overflow.c:6:25: erreur d'exécution: dépassement d'entier signé: 1073741824 * 2 ne peut pas être représenté dans le type 'int'
        -2147483648
        0
        0
        ...
      
    • Il s'avère que notre programme a reconnu qu'un entier signé (un entier avec un signe positif ou négatif) ne pouvait pas stocker cette prochaine valeur et a imprimé une erreur. Ensuite, puisqu'il essayait de le doubler quand même, i est devenu un nombre négatif, puis 0.

    • Ce problème est appelé dépassement de capacité d'entier, où un entier ne peut être que si grand avant de manquer de bits et de « déborder ». Nous pouvons imaginer ajouter 1 à 999 en décimal. Le dernier chiffre devient 0, nous reportons le 1 pour que le chiffre suivant devienne 0, et nous obtenons 1000. Mais si nous n'avions que trois chiffres, nous nous retrouverions avec 000 car il n'y a pas de place pour mettre le 1 final !
  • Le problème Y2K est survenu parce que de nombreux programmes stockaient l'année civile avec seulement deux chiffres, comme 98 pour 1998 et 99 pour 1999. Mais à l'approche de l'an 2000, les programmes auraient stocké 00, ce qui entraînerait une confusion entre les années 1900 et 2000.

  • Un avion Boeing 787 avait également un bug où un compteur dans le générateur débordait après un certain nombre de jours de fonctionnement continu, car le nombre de secondes de fonctionnement ne pouvait plus être stocké dans ce compteur.
  • Donc, nous avons vu quelques problèmes qui peuvent survenir, mais maintenant nous comprenons pourquoi et comment les éviter.
  • Avec le problème de la semaine, nous utiliserons le CS50 Lab, basé sur le CS50 Sandbox, pour écrire quelques programmes avec des procédures pas à pas pour nous guider.