In [ ]:
//%cflags: -lm -lGL -lGLU -lglfw
#include "math.h"
#include "time.h"
#include "stdio.h"
#include "stdlib.h"
#include "GL/glu.h"
#include "GLFW/glfw3.h"
#define PI 3.14159265358979323846
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define FOV 70 // Camera field of view, in degrees
#define MAP_WIDTH 64 // In number of cubes
#define MAP_DEPTH 64 // In number of cubes
#define MOVE_SPEED 4 // In world units per second
#define SIDE_LIGHTNESS 0.6 // Between 0 and 1
#define BOTTOM_LIGHTNESS 0.3 // Between 0 and 1
void check_gl_error() {
GLenum error = glGetError();
switch (error) {
case GL_NO_ERROR:
break;
case GL_INVALID_ENUM:
fprintf(stderr, "Invalid enum passed to last OpenGL call.\n");
break;
case GL_INVALID_VALUE:
fprintf(stderr, "Invalid value passed to last OpenGL call.\n");
break;
case GL_INVALID_OPERATION:
fprintf(stderr,
"Invalid operation passed to last OpenGL call.\n");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
fprintf(stderr, "Invalid OpenGL operation, the currently bound "
"framebuffer was not complete.\n");
break;
case GL_OUT_OF_MEMORY:
fprintf(stderr, "Not enough memory for the last OpenGL call.\n");
break;
default:
fprintf(stderr, "Unknown OpenGL error (code %i)\n", error);
break;
}
}
void error_callback(int error, const char* description) {
fprintf(stderr, "Error: %s\n", description);
}
typedef struct {
float x;
float y;
float z;
float dx;
float dy;
float dz;
float ax;
float ay;
double mouse_x;
double mouse_y;
char mouse_grabbed;
} Camera;
typedef struct {
float x;
float y;
float z;
int red;
int green;
int blue;
} Cube;
typedef struct {
GLFWwindow* window;
unsigned int window_width;
unsigned int window_height;
Camera camera;
unsigned int n_cubes;
Cube* cubes;
double last_frame_time;
double dt;
} Game;
Game game;
static void set_framebuffer_size(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
game.window_width = width;
game.window_height = height;
}
static void key_callback(GLFWwindow* window, int key, int scancode,
int action, int mods) {
if (action == GLFW_PRESS) {
switch (key) {
case GLFW_KEY_W:
game.camera.dz -= MOVE_SPEED;
break;
case GLFW_KEY_A:
game.camera.dx -= MOVE_SPEED;
break;
case GLFW_KEY_S:
game.camera.dz += MOVE_SPEED;
break;
case GLFW_KEY_D:
game.camera.dx += MOVE_SPEED;
break;
case GLFW_KEY_SPACE:
game.camera.dy += MOVE_SPEED;
break;
case GLFW_KEY_LEFT_CONTROL:
game.camera.dy -= MOVE_SPEED;
break;
case GLFW_KEY_ESCAPE:
glfwSetInputMode(game.window,
GLFW_CURSOR, GLFW_CURSOR_NORMAL);
game.camera.mouse_grabbed = 0;
break;
}
} else if (action == GLFW_RELEASE) {
switch (key) {
case GLFW_KEY_W:
game.camera.dz += MOVE_SPEED;
break;
case GLFW_KEY_A:
game.camera.dx += MOVE_SPEED;
break;
case GLFW_KEY_S:
game.camera.dz -= MOVE_SPEED;
break;
case GLFW_KEY_D:
game.camera.dx -= MOVE_SPEED;
break;
case GLFW_KEY_SPACE:
game.camera.dy -= MOVE_SPEED;
break;
case GLFW_KEY_LEFT_CONTROL:
game.camera.dy += MOVE_SPEED;
break;
}
}
}
static void mouse_button_callback(GLFWwindow* window,
int button, int action, int mods) {
if (action == GLFW_PRESS) {
if (button == GLFW_MOUSE_BUTTON_LEFT) {
glfwSetInputMode(game.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwGetCursorPos(game.window,
&game.camera.mouse_x, &game.camera.mouse_y);
game.camera.mouse_grabbed = 1;
}
}
}
static void mouse_move_callback(GLFWwindow* window,
double mouse_x, double mouse_y) {
if (game.camera.mouse_grabbed) {
game.camera.ay += (mouse_x - game.camera.mouse_x) / 300;
game.camera.ax += (mouse_y - game.camera.mouse_y) / 300;
if (game.camera.ax < -PI / 2) {
game.camera.ax = -PI / 2;
} else if (game.camera.ax > PI / 2) {
game.camera.ax = PI / 2;
}
game.camera.mouse_x = mouse_x;
game.camera.mouse_y = mouse_y;
}
}
void vertexA(Cube cube) {
glVertex3f(cube.x+.5, cube.y+.5, cube.z+.5);
}
void vertexB(Cube cube) {
glVertex3f(cube.x-.5, cube.y+.5, cube.z+.5);
}
void vertexC(Cube cube) {
glVertex3f(cube.x-.5, cube.y-.5, cube.z+.5);
}
void vertexD(Cube cube) {
glVertex3f(cube.x+.5, cube.y-.5, cube.z+.5);
}
void vertexE(Cube cube) {
glVertex3f(cube.x-.5, cube.y+.5, cube.z-.5);
}
void vertexF(Cube cube) {
glVertex3f(cube.x+.5, cube.y+.5, cube.z-.5);
}
void vertexG(Cube cube) {
glVertex3f(cube.x+.5, cube.y-.5, cube.z-.5);
}
void vertexH(Cube cube) {
glVertex3f(cube.x-.5, cube.y-.5, cube.z-.5);
}
void render_cube(Cube cube) {
// Makes the color darker to simulate lighting.
glColor3ub(cube.red * SIDE_LIGHTNESS,
cube.green * SIDE_LIGHTNESS,
cube.blue * SIDE_LIGHTNESS);
check_gl_error();
glBegin(GL_QUADS);
//
// Front
//
vertexA(cube);
vertexB(cube);
vertexC(cube);
vertexD(cube);
//
// Back
//
vertexE(cube);
vertexF(cube);
vertexG(cube);
vertexH(cube);
//
// Left
//
vertexF(cube);
vertexA(cube);
vertexD(cube);
vertexG(cube);
//
// Right
//
vertexB(cube);
vertexE(cube);
vertexH(cube);
vertexC(cube);
//
// Top
//
// Makes the color fully lit to simulate lighting.
glColor3ub(cube.red, cube.green, cube.blue);
vertexF(cube);
vertexE(cube);
vertexB(cube);
vertexA(cube);
//
// Bottom
//
// Makes the color very dark to simulate lighting.
glColor3ub(cube.red * BOTTOM_LIGHTNESS,
cube.green * BOTTOM_LIGHTNESS,
cube.blue * BOTTOM_LIGHTNESS);
vertexD(cube);
vertexC(cube);
vertexH(cube);
vertexG(cube);
glEnd();
check_gl_error();
}
float rad2deg(float angle) {
return 180 * angle / PI;
}
void render() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
check_gl_error();
// Sets the projection matrix to a perspective.
glMatrixMode(GL_PROJECTION);
check_gl_error();
glLoadIdentity();
check_gl_error();
gluPerspective(FOV, (float)game.window_width / game.window_height,
1, 1000);
// Moves and rotates the world so the camera is its origin.
glMatrixMode(GL_MODELVIEW);
check_gl_error();
glLoadIdentity();
check_gl_error();
Camera camera = game.camera;
glRotatef(rad2deg(camera.ax), 1, 0, 0);
glRotatef(rad2deg(camera.ay), 0, 1, 0);
glTranslatef(-camera.x, -camera.y, -camera.z);
for (unsigned int i = 0; i < game.n_cubes; i++) {
render_cube(game.cubes[i]);
}
}
void create_window() {
glfwWindowHint(GLFW_SAMPLES, 4);
game.window_width = WINDOW_WIDTH;
game.window_height = WINDOW_HEIGHT;
game.window = glfwCreateWindow(
game.window_width, game.window_height,
"OpenGL is hard, but rewarding", NULL, NULL);
glfwMakeContextCurrent(game.window);
// Sets game rendering to the same frequency as the screen frequency.
glfwSwapInterval(1);
}
void update_camera() {
game.camera.x += (
game.camera.dx * cos(game.camera.ay)
+ game.camera.dz * cos(game.camera.ay + PI / 2)) * game.dt;
game.camera.y += game.camera.dy * game.dt;
game.camera.z += (
game.camera.dx * sin(game.camera.ay)
+ game.camera.dz * sin(game.camera.ay + PI / 2)) * game.dt;
}
void loop() {
while (!glfwWindowShouldClose(game.window)) {
update_camera();
render();
// Puts on the screen the frame rendered to a temporary buffer.
glfwSwapBuffers(game.window);
// Runs all pending events.
glfwPollEvents();
double now = glfwGetTime();
game.dt = now - game.last_frame_time;
game.last_frame_time = now;
printf("FPS: %i Position: [%f %f %f] Direction: [%f %f]\r",
(int)round(1 / game.dt),
game.camera.x, game.camera.y, game.camera.z,
game.camera.ax, game.camera.ay);
}
}
unsigned char rand_char() {
return 255 * (float)rand() / RAND_MAX;
}
void create_map(unsigned int width, unsigned int depth) {
game.n_cubes = width * depth;
game.cubes = (Cube*)malloc(game.n_cubes * sizeof(Cube));
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
game.cubes[x + z*width] = (Cube){
x - width * .5, rand_char() / 120 - 5, z - depth * .5,
rand_char(), rand_char(), rand_char()};
}
}
}
int main() {
// Sets the pseudo-random generator seed using the current time.
srand(time(NULL));
glfwSetErrorCallback(error_callback);
if (!glfwInit()) {
fprintf(stderr, "Error while initializing GLFW.\n");
return 1;
}
create_window();
if (game.window == NULL) {
fprintf(stderr, "Cannot create a window.\n");
return 1;
}
glfwSetFramebufferSizeCallback(game.window, set_framebuffer_size);
glfwSetKeyCallback(game.window, key_callback);
glfwSetMouseButtonCallback(game.window, mouse_button_callback);
glfwSetCursorPosCallback(game.window, mouse_move_callback);
game.camera = (Camera){x: 0, y: 0, z: 0, dx: 0, dy: 0, dz: 0,
ax: 0, ay: 0, mouse_grabbed: 0};
create_map(MAP_WIDTH, MAP_DEPTH);
glEnable(GL_DEPTH_TEST);
check_gl_error();
glEnable(GL_CULL_FACE);
check_gl_error();
glCullFace(GL_BACK);
check_gl_error();
loop();
free(game.cubes);
glfwDestroyWindow(game.window);
glfwTerminate();
// Adds a new line to avoid getting a weird line due to previous \r.
puts("");
return 0;
}
Dès que vous avez fini les exercices ou pris un peu d’avance, améliorez cette démo !
Vous pouvez vous y mettre à plusieurs, mais ne parlez pas : chuchotez ou utilisez Slack.
Vous pouvez améliorer ce que vous souhaitez, mais en cas de manque d’inspiration, ce serait super de :
./demo 64x128
Créer un résolveur de sudoku. Il lira un fichier contenant le sudoku à résoudre.
En sortie, le programme donnera le sudoku résolu.
Au niveau algorithmique, c’est relativement brutal : il suffit de passer en revue les cases vides. Pour chaque case vide, il faut passer en revue les chiffres de 1 à 9 et ainsi trouver les chiffres possibles pour cette case. Pour chaque chiffre possible, il faut « ouvrir » une nouvelle branche d’arborescence permettant de tester des possibilités pour chacune des autres cases.
L’algorithme passe donc en revue les solutions sous forme d’arborescence, et dès qu’il trouve une solution, il s’arrête et l’affiche.
Oui, c’est le retour de la programmation récursive. Il est possible de réaliser ce programme sans récursivité, mais c’est nettement plus difficile.
Pour ceux qui aiment les défis, généraliser cet algorithme à n’importe quelle taille de sudoku rectangulaire régulier : 6×6, 12×12, 12×6, etc.
Difficile de trouver une bonne documentation pour C : il n’y en a pas vraiment d’officielle.
Toutefois, quelques sites contenant tout ce dont on a besoin :
Et concernant la démo, elle utilise GLFW, OpenGL et GLU (dont la documentation est incluse dans celle d’OpenGL 2.1)
Et si vous n’avez pas la foi : Google + StackOverflow !
C est le langage entre l’assembleur et tous les logiciels, y compris les autres langages de programmation.
L’assembleur est la forme textuelle du langage machine. L’assembleur est directement transposé en langage machine compréhensible par le processeur.
La quasi totalité des langages de programmation actuels sont écrits en C, particulièrement les langages compilés : Python, PHP, Java, JavaScript…
C est un langage dit bas niveau parce qu’il est proche du langage machine. Par opposition, les langages interprétés plus récents comme Python, PHP, Java, etc sont dit haut niveau.
C peut sembler archaïque, langage compilé d’il y a 45 ans.
Pourtant, tous les développeurs sont amenés à en faire dans leur vie pour ces raisons :
Bien qu’universel et très puissant, C est très exigeant et contient beaucoup de différences d’un système d’exploitation à un autre.
Pour ces raisons, il est conseillé d’utiliser à la place un langage interprété comme Python quand :
En pratique, dans la plupart des métiers informatiques, on fait peu de C.
Toutefois, ne pas connaître C risque de vous poursuivre toute votre vie professionnelle. Un bon développeur est toujours capable de lire du C, que ce soit pour juste compiler un programme déjà existant, ou pour améliorer une fonctionnalité de son propre langage de programmation.
Il existe des dizaines de compilateurs C.
Voici les plus utilisés aujourd’hui :
Nom | Licence | Support Linux | Support MacOS | Support Windows |
---|---|---|---|---|
GCC | Open source | Oui | Oui | Oui |
Clang | Open source | Oui | Oui | Oui |
Microsoft Visual C | Gratuit mais fermé | Non | Non | Oui |
Clang ≈ le futur de GCC
En attendant, on utilisera plutôt GCC.
On utilise uniquement GNU Compiler Collection, alias GCC.
Sous Ubuntu :
sudo apt install gcc
Sous Windows :
mingw-get-setup.exe
sur https://sourceforge.net/projects/mingw/files/Installer/)mingw32-gcc
%PATH%
le chemin des binaires de MinGW (C:\MinGW\bin
dans beaucoup de cas)Malgré cette installation plus complexe, il est toujours possible que vous ne puissiez pas exécuter gcc
directement dans votre invite de commandes Windows.
Windows : enfer pour développer correctement, privilégier n’importe quelle distribution de Linux.
Pour ceux qui galèrent, installer VirtualBox avec une machine virtuelle Ubuntu 17.10.
In [1]:
int main() {}
Pour le compiler :
first.c
gcc first.c -o first
Pour le lancer : ./first
In [2]:
#include "stdio.h"
int main() {
puts("Hello World!");
}
Ici, on a utilisé la fonction puts
permettant d’afficher du texte.
Cette fonction est contenue dans la librairie standard stdio
, d’où la première ligne.
La plupart des librairies standard de C commencent par std
. Ici, le io
signifie “Input/Output”.
Que se passe-t-il si j’essaie de renommer main
?
In [3]:
#include "stdio.h"
int hello() {
puts("Hello World!");
}
La compilation a fonctionné.
Par contre quand on lance le programme, on a une erreur et rien ne s’affiche !
Tous les programmes C qu’on souhaite lancer directement doivent contenir une fonction main
, c’est uniquement cette fonction qui sera exécutée au lancement du programme.
Bien entendu, les librairies qui ne sont pas faites pour être lancées directement peuvent ne pas avoir cette fonction.
In [ ]:
int age;
Il s’agit donc du type, ici int
, suivi du nom de la variable, fini par un ;
.
Toutes les instructions C doivent impérativement finir par ;
.
Définir la variable en l’attribuant :
In [ ]:
int age = 28;
On peut également définir plusieurs variables à la fois :
In [ ]:
int birth_year = 1989, this_year = 2017;
Le nombre d’octets d’un type varie d’un type de processeur à un autre. Des processeurs 32 bits et 64 bits auront donc certains types de longueur différente. Dans le tableau ci-dessous, il s’agit des tailles pour un processeur moderne 64 bits.
Notation | Nom français | Exemple | Nombre d’octets |
---|---|---|---|
char |
Octet, ou caractère ASCII | 'A' ou 65 |
1 |
short |
Petit nombre entier | 3725 |
2 |
int |
Nombre entier | 35816642 |
4 |
long |
Long nombre entier | 654987546216454298 |
8 |
float |
Nombre à virgule | 1.618033 |
4 |
double |
Nombre à virgule plus précis | 1.6180339887498 |
8 |
Quelques spécificités sont à noter :
char
, short
, int
, long
peuvent être signed
ou unsigned
. float
et double
sont toujours signés. Par défaut, les types short
, int
et long
sont signed
, mais char
n’est pas explicitement signé ou non par défaut.unsigned
est $0$ et le maximum est $2^{8n}-1$. Par exemple, unsigned short
va de $0$ à $65535$.signed
est $-2^{8n-1}$ et le maximum est $2^{8n-1}-1$, où $n$ est le nombre d’octets. Par exemple, signed short
va de $-32768$ à $32767$.void
. Il sert à indiquer qu’une fonction ne renvoie rien, mais on ne peut pas définir une variable comme void
.Notation | Nom | Types compatibles | Exemple |
---|---|---|---|
+ , - , * , / |
Opérations usuelles | Types numériques | -65 * (3 + 5.7) |
% |
Modulo | Types numériques | 12 % 5 |
== |
Égalité | Tous types | 13 == 5 |
!= |
Différence | Tous types | 5 != 3.5 |
< > |
Inférieur/supérieur | Types numériques | 2 < 8 |
<= >= |
Inférieur/supérieur ou égal | Types numériques | 8700 >= -32 |
&& |
« Et » booléen | Tous types | 0 && 1 |
|| |
« Ou » booléen | Tous types | 3 < 2 || 15 > 8 |
Opérations modifiant directement les objets (on dit qu’ils sont modifiés en place ou inplace) :
Notation | Nom | Types compatibles | Exemple |
---|---|---|---|
++ |
Incrémentation | Types numériques | age++ |
-- |
Décrémentation | Types numériques | age-- |
Toutes les fonctions permettant d’écrire à l’écran ou de demander à l’utilisateur de taper sont dans la librairie standard stdio
.
Pour se servir de ces fonctions, il faut donc toujours mettre #include "stdio.h"
au début du fichier.
Quelques caractères échappés à connaître pour tous les langages :
Notation | Nom |
---|---|
\n |
Retour à la ligne |
\t |
Tabulation |
\r |
« Retour chariot » : revient au début de la ligne en cours |
On a déjà vu puts(texte)
, qui permet d’afficher du texte à l’écran suivi d’un retour à la ligne.
Juste avant, on a vu printf(format, valeur1, valeur2, …)
qui permet d’afficher une ou plusieurs variables suivant un motif. C’est la fonction d’affichage qui vous sera la plus utile, elle peut même s’utiliser à la place de puts
. Exemples d’utilisation :
In [4]:
#include "stdio.h"
int main () {
printf("Bonjour");
printf(" tout le monde\n");
printf("J’ai %i ans\n", 28);
printf("Il fait %f °C.\n", 21.37);
printf("Il fait %.2f °C.\n", 21.37);
}
Afficher le tableau suivant. Il doit être le plus beau possible, avec des bordures continues, sans « trous ». Pour bien faire, il faut utiliser des box-drawing characters.
Language | Year | GitHub popularity |
---|---|---|
C | 1972 | #10 |
Python | 1990 | #2 |
PHP | 1994 | #5 |
Java | 1995 | #3 |
JavaScript | 1995 | #1 |
Pour info, la popularité sur GitHub est mesurée en nombre de pull request ouvertes en 2017. Voir https://octoverse.github.com/.
Définir une variable de type signed short
à sa plus grande valeur possible. Afficher cette variable, puis l’incrémenter et l’afficher à nouveau. Conclure.
In [ ]:
// Exercice 7
#include "stdio.h"
int main() {
printf("┏━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━┓\n");
printf("\033[1m"); // Changes the text to bold.
printf("┃ %10s ┃ %4s ┃ %17s ┃\n", "Language", "Year", "GitHub popularity");
printf("\033[0m"); // Resets text to normal weight.
printf("┡━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━┩\n");
printf("│ %10s │ %4s │ %17s │\n", "C", "1972", "#10");
printf("│ %10s │ %4s │ %17s │\n", "C++", "1984", "#6");
printf("│ %10s │ %4s │ %17s │\n", "Python", "1990", "#2");
printf("│ %10s │ %4s │ %17s │\n", "PHP", "1994", "#5");
printf("│ %10s │ %4s │ %17s │\n", "Java", "1995", "#3");
printf("│ %10s │ %4s │ %17s │\n", "JavaScript", "1995", "#1");
printf("└────────────┴──────┴───────────────────┘\n");
}
In [ ]:
// Exercice 8
#include "stdio.h"
int main () {
short i = 32767;
printf("%i\n", i);
i++;
printf("%i\n", i);
}
In [ ]:
#include "stdio.h"
int main() {
printf("Tape une lettre : ");
char letter = getchar();
printf("Tu as tapé %c !\n", letter);
}
Malheureusement, c’est insuffisant pour plus d’un caractère, ou pour un autre type.
In [ ]:
#include "stdio.h"
int main() {
unsigned int age;
printf("Quel âge as-tu ? ");
scanf("%u", &age);
printf("Oh, tu as %u ans !\n", age);
}
Contrairement à printf
, le format ne contient que le format de la valeur.
Autre problème, on ne peut pas passer directement des valeurs. À la place, on crée des variables et on les référence avec &
. On reviendra sur le concept de référence.
Se référer aux documentations pour l’ensemble des possibilités de printf
et scanf
.
Formats les plus importants communs à printf
et scanf
:
Notation | Type | Exemple d’entrée/sortie |
---|---|---|
%i |
short , int |
-8530 |
%u |
unsigned short /int |
12 |
%li |
long |
-546413432864 |
%u |
unsigned long |
165413179 |
%f |
float , double |
7.1325 |
%.1f |
float , double |
7.1 |
%c |
char |
a |
%s |
char* (on verra ça) |
abcdef |
In [ ]:
// Exercice 11
#include "stdio.h"
int main() {
float a, b;
printf("We’re going to do a multiplication.\n");
printf("Enter both terms separated by spaces or newlines.\n");
scanf("%f%f", &a, &b);
printf("%f × %f = %f\n", a, b, a * b);
}
In [5]:
//%cflags: -lm
#include "math.h"
#include "stdio.h"
float hypotenuse(float adjacent, float opposite) {
return sqrt(adjacent*adjacent + opposite*opposite);
}
int main() {
printf("%f\n", hypotenuse(50, 75));
}
Dans l’exemple précédent, le //%cflags: -lm
me permet d’ajouter des arguments à gcc
. Ne le recopiez pas dans la source, mais servez-vous en pour compiler.
Lors de ce cours, //%cflags:
sera utilisé uniquement pour ajouter lier des librairies au programme, de sorte que cela fonctionne. Ici, on lie la librairie math
dont la version compilée est m
en faisant -lm
. Pour lier OpenGL, on utilise -lGL
.
Dans cet exemple, on compile en faisant :
gcc hypotenuse.c -lm -o hypotenuse
Attention ! L’ordre des arguments est important, il faut toujours mettre après la source les arguments -l…
.
Puis on se sert du programme normalement :
./hypotenuse
Créer une fonction calculant pour tout $n$, $\sqrt[\leftroot{-2}\uproot{2}5]{n}$.
Pour rappel, $\sqrt[\leftroot{-2}\uproot{2}i]{n} ⇔ n^\frac{1}{i}$.
Se servir de cette fonction pour calculer $\sqrt[\leftroot{-2}\uproot{2}5]{5}$, $\sqrt[\leftroot{-2}\uproot{2}5]{32}$ et $\sqrt[\leftroot{-2}\uproot{2}5]{243}$.
In [ ]:
void main() {}
Pourtant on constate que le programme renvoie une erreur 202, bizarre, non ?
En fait, main
renvoie le code de retour (=return code) du programme.
Le return code permet de préciser une erreur, qui va interrompre un script par exemple.
Le code pour une absence d’erreur est 0. C’est le code par défaut.
Exemple d’erreur :
In [6]:
int main() {
return 1;
}
In [7]:
int main() {
int i = 3, return_code = 0;
if (i == 2) {
return_code = 1;
} else if (i == 5) {
return_code = 2;
} else {
return_code = 3;
}
return return_code;
}
Écrire une petite calculatrice gérant les opérations +
, -
, ×
et ÷
.
L’utilisateur doit d’abord saisir un premier nombre à virgule, puis un opérateur, puis un autre nombre.
Selon ces trois paramètres, le programme doit correctement renvoyer le résultat de l’opération, sinon afficher un message d’erreur et quitter avec le return code adapté lorsque les données sont invalides.
Exemple d’entrée/sortie :
28
*
2
= 56
ou
7+13
= 20
In [ ]:
// Exercice 14
#include "stdio.h"
#include "stdio.h"
int main() {
int n;
float a, b, result;
char operator;
n = scanf("%f %c %f", &a, &operator, &b);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n < 3) {
fprintf(stderr, "Invalid first operand (%f), operator (%c)"
" or second operand (%f).\n", a, operator, b);
return 1;
}
if (operator == '+') {
result = a + b;
} else if (operator == '-') {
result = a - b;
} else if (operator == '*') {
result = a * b;
} else if (operator == '/') {
result = a / b;
} else {
fprintf(stderr, "Invalid operator %c.\n", operator);
return 1;
}
printf("= %f\n", result);
}
In [ ]:
#include "stdio.h"
int main() {
char answer;
while (answer != 'y') {
puts("Processing…");
puts("Would you like to quit? [yN]");
answer = getchar();
}
}
Une variante de la boucle while
existe, le do
…while
. Il vérifie la condition à la fin de la boucle au lieu du début :
In [ ]:
#include "stdio.h"
int main() {
do {
puts("Processing…");
puts("Would you like to quit? [yN]");
} while (getchar() != 'y');
}
Améliorer la calculatrice pour qu’elle demande en boucle des calculs à l’utilisateur.
Le résultat précédent doit toujours être utilisé comme premier opérande de l’opération suivante.
Créer une fonction contenant la logique d’opération pour simplifier la lisibilité.
Exemple d’entrée/sortie :
7
+
23
= 30.000000
/3
= 10.000000
- 8
= 2.000000
/ 4
= 0.500000
In [ ]:
// Exercice 15
#include "stdio.h"
#include "math.h"
float calculate(float a) {
int n;
float b;
char operator;
// Consumes extra input characters
while (getchar() != ' ') {}
n = scanf("%c%f", &operator, &b);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n < 2) {
fprintf(stderr, "Invalid operator (%c) or second operand (%f).\n",
operator, b);
return NAN;
}
if (operator == '+') {
return a + b;
} else if (operator == '-') {
return a - b;
} else if (operator == '*') {
return a * b;
} else if (operator == '/') {
return a / b;
}
fprintf(stderr, "Invalid operator %c.\n", operator);
return NAN;
}
int main() {
float a, result;
int n = scanf("%f", &a);
if (n == 0) {
fprintf(stderr, "Invalid first operand.\n");
return 1;
}
while (1) {
result = calculate(a);
if (isnan(result)) {
return 1;
}
printf("= %f\n", result);
a = result;
}
}
In [8]:
#include "stdio.h"
int main() {
for (int i = 1; i <= 6; i++) {
printf("%i ", i);
}
}
Elle contient une condition, comme while
, mais également deux parties supplémentaires :
int i = 1
i++
In [ ]:
// Exercice 16
#include "stdio.h"
#include "math.h"
float calculate(float a) {
int n;
float b, result;
char operator;
// Consumes extra input characters
while (getchar() != ' ') {}
n = scanf("%c %f", &operator, &b);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n < 2) {
fprintf(stderr, "Invalid operator (%c) or second operand (%f).\n",
operator, b);
return NAN;
}
if (operator == '+') {
return a + b;
} else if (operator == '-') {
return a - b;
} else if (operator == '*') {
return a * b;
} else if (operator == '/') {
return a / b;
} else if (operator == '^') {
result = a;
for (int i = 1; i < b; i++) {
result *= a;
}
return result;
}
fprintf(stderr, "Invalid operator %c.\n", operator);
return NAN;
}
int main() {
float a, result;
int n = scanf("%f", &a);
if (n == 0) {
fprintf(stderr, "Invalid first operand.\n");
return 1;
}
while (1) {
result = calculate(a);
if (isnan(result)) {
return 1;
}
printf("= %f\n", result);
a = result;
}
}
Parfois, on a besoin d’arrêter une boucle avant qu’elle ait fini.
Pour cela, on utilise le mot-clé break
.
Il est souvent plus facile de faire appel à break
que d’utiliser do
…while
. De même, il est souvent peu adapté de mettre une condition à while
, car on va avoir besoin de tester une condition non pas au début ou la fin d’une boucle, mais au milieu :
In [ ]:
#include "stdio.h"
int main() {
while (1) {
puts("Would you like to quit? [yN]");
if (getchar() == 'y') {
break;
}
puts("Processing…");
}
}
De même, on peut utiliser break
au cours d’une boucle for
.
In [ ]:
//%cflags: -lm
// Exercice 17
#include "stdio.h"
#include "math.h"
float calculate(float a) {
int n;
float b;
char operator;
// Consumes extra input characters
while (getchar() != ' ') {}
n = scanf("%c%f", &operator, &b);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n < 2) {
fprintf(stderr, "Invalid operator (%c) or second operand (%f).\n",
operator, b);
return NAN;
}
if (operator == '+') {
return a + b;
} else if (operator == '-') {
return a - b;
} else if (operator == '*') {
return a * b;
} else if (operator == '/') {
return a / b;
} else if (operator == '^') {
return pow(a, b);
}
fprintf(stderr, "Invalid operator %c.\n", operator);
return NAN;
}
int main() {
float a, result;
int n = scanf("%f", &a);
if (n == 0) {
fprintf(stderr, "Invalid first operand.\n");
return 1;
}
while (1) {
result = calculate(a);
if (result == 666 || result == 7
|| (result >= 3.14 && result < 3.15)
|| (result >= 1.618 && result < 1.619)) {
printf("Secret goal reached. Exiting.\n");
return 0;
} else if (isnan(result)) {
return 1;
}
printf("= %f\n", result);
a = result;
}
}
In [9]:
int main () {
int i = 3, return_code = 0;
switch (i) {
case 2:
return_code = 1;
break;
case 5:
return_code = 2;
break;
default:
return_code = 3;
}
return return_code;
}
On précise break
sinon les case
et default
suivant un case
vrai sont également exécutés. Cette fonctionnalité un peu spéciale est parfois utile, mais la plupart du temps il est préférable de « casser » à la fin de chaque case.
Malheureusement, comme on le voit, le switch
…case
est souvent finalement plus long qu’un ensemble de if
s.
On privilégie souvent les if
s, mais on utilise des switch
…case
lorsqu’on a besoin de l’optimisation de vitesse qu’ils apporteent par rapport aux if
s.
In [ ]:
//%cflags: -lm
// Exercice 18
#include "stdio.h"
#include "math.h"
float calculate(float a) {
int n;
float b;
char operator;
// Consumes extra input characters
while (getchar() != ' ') {}
n = scanf("%c%f", &operator, &b);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n < 2) {
fprintf(stderr, "Invalid operator (%c) or second operand (%f).\n",
operator, b);
return NAN;
}
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
case '^':
return pow(a, b);
default:
fprintf(stderr, "Invalid operator %c.\n", operator);
return NAN;
}
}
int main() {
float a, result;
int n = scanf("%f", &a);
if (n == 0) {
fprintf(stderr, "Invalid first operand.\n");
return 1;
}
while (1) {
result = calculate(a);
if (result == 666 || result == 7
|| (result >= 3.14 && result < 3.15)
|| (result >= 1.618 && result < 1.619)) {
printf("Secret goal reached. Exiting.\n");
return 0;
} else if (isnan(result)) {
return 1;
}
printf("= %f\n", result);
a = result;
}
}
In [ ]:
int ages[20];
Pour assigner directement un tableau :
In [ ]:
int ages[6] = {8, 57, 32, 12, 26, 22};
Pour obtenir un élément à un index particulier (noter qu’on commence à 0 et non à 1) :
In [ ]:
ages[0];
Pour réassigner un élément :
In [ ]:
ages[0] = 9;
Écrire un programme permettant de faire les comptes.
L’utilisateur saisira jusqu’à 100 nombres à virgule jusqu’à ce qu’il ne saississe plus qu’une phrase avec des espaces, comme “End of month”.
Le programme affichera alors un tableau listant toutes les opérations avec le solde du compte après chaque opération.
Exemple d’entrée/sortie :
857.32
1500
-500
-3
-20
End of month
┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Amount ┃ Account balance ┃
┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ 857.32 │ 857.32 │
│ 1500.00 │ 2357.32 │
│ -500.00 │ 1857.32 │
│ -3.00 │ 1854.32 │
│ -20.00 │ 1834.32 │
└─────────┴─────────────────┘
In [ ]:
// Exercice 19
#include "stdio.h"
#include "string.h"
void main() {
int size;
float operations_amounts[100];
float amount;
for (size = 0; size < 100; size++) {
int n_parsed = scanf("%f", &amount);
if (n_parsed < 1) {
break;
}
operations_amounts[size] = amount;
}
printf("┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n");
printf("\033[1m"); // Changes the text to bold.
printf("┃ %10s ┃ %15s ┃\n",
"Amount", "Account balance");
printf("\033[0m"); // Resets text to normal weight.
printf("┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n");
float total = 0;
for (int i = 0; i < size; i++) {
amount = operations_amounts[i];
total += amount;
printf("│ %10.2f │ %15.2f │\n", amount, total);
}
printf("└────────────┴─────────────────┘\n");
}
In [ ]:
char name[60] = "Bonjour !";
Ici, le tableau contient donc :
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
B |
o |
n |
j |
o |
u |
r |
|
! |
\0 |
De la 11e à la 60e case, le tableau contient bien des valeurs, mais elles sont semi-aléatoires.
Dans cette variable name
, on pourra donc définir à nouveau une valeur, tant que sa longueur n’excède pas 60 octets.
À noter : la librairie string.h
contient de nombreuses fonctionalités utiles pour nous faciliter la vie.
Les plus intéressantes pour tout de suite sont strlen
et strcpy
.
Améliorer le programme de comptes pour pouvoir gérer la description de l’opération.
Désormais, l’utilisateur saisira des descriptions d’opérations bancaires suivis de nombres à virgule jusqu’à ce qu’il ne saississe plus qu’une phrase avec des espaces, comme “End of month”.
Exemple d’entrée/sortie :
Solde 857.32
Salaire 1500
Loyer -500
Chips -3
Disque -20
End of month
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Description ┃ Amount ┃ Account balance ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ Solde │ 857.32 │ 857.32 │
│ Salaire │ 1500.00 │ 2357.32 │
│ Loyer │ -500.00 │ 1857.32 │
│ Chips │ -3.00 │ 1854.32 │
│ Disque │ -20.00 │ 1834.32 │
└──────────────────────┴─────────┴─────────────────┘
In [ ]:
// Exercice 20
#include "stdio.h"
#include "string.h"
void main() {
int size;
char operations_names[100][20];
float operations_amounts[100];
char name[20];
float amount;
for (size = 0; size < 100; size++) {
int n_parsed = scanf("%s %f", name, &amount);
if (n_parsed < 2) {
break;
}
strcpy(operations_names[size], name);
operations_amounts[size] = amount;
}
printf("┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n");
printf("\033[1m"); // Changes the text to bold.
printf("┃ %20s ┃ %10s ┃ %15s ┃\n",
"Description", "Amount", "Account balance");
printf("\033[0m"); // Resets text to normal weight.
printf("┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n");
float total = 0;
for (int i = 0; i < size; i++) {
strcpy(name, operations_names[i]);
amount = operations_amounts[i];
total += amount;
printf("│ %20s │ %10.2f │ %15.2f │\n", name, amount, total);
}
printf("└──────────────────────┴────────────┴─────────────────┘\n");
}
In [ ]:
unsigned short goban[19][19];
Ici, on a créé un plateau de jeu de go (dit goban) de 19 cases de côté, soit 361 cases.
Pour dire que le joueur 1 pose une pierre en C12, on fait :
In [ ]:
goban[2][11] = 1;
Pour savoir s’il y a une pierre posée en H4, on fait appel à :
In [ ]:
goban[7][3];
Écrire un programme de tic-tac-toe. La plateau de jeu est affiché à nouveau à chaque tour. On demande tour à tour aux joueurs les coordonnées de là où ils veulent jouer. Bien entendu, le jeu doit s’arrêter dès qu’un joueur a gagné, et on doit empêcher les joueurs de tricher.
Exemple d’entrée/sortie :
A B C
┌───┬───┬───┐
1 │ │ │ │
├───┼───┼───┤
2 │ │ │ │
├───┼───┼───┤
3 │ │ │ │
└───┴───┴───┘
Player X? B2
A B C
┌───┬───┬───┐
1 │ │ │ │
├───┼───┼───┤
2 │ │ X │ │
├───┼───┼───┤
3 │ │ │ │
└───┴───┴───┘
Player O? C1
A B C
┌───┬───┬───┐
1 │ │ │ O │
├───┼───┼───┤
2 │ │ X │ │
├───┼───┼───┤
3 │ │ │ │
└───┴───┴───┘
In [ ]:
// Exercice 21
#include "stdio.h"
char board[3][3] = {{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}};
void display() {
printf(" A B C\n");
printf(" ┌───┬───┬───┐\n");
printf("1 │ %c │ %c │ %c │\n",
board[0][0], board[0][1], board[0][2]);
printf(" ├───┼───┼───┤\n");
printf("2 │ %c │ %c │ %c │\n",
board[1][0], board[1][1], board[1][2]);
printf(" ├───┼───┼───┤\n");
printf("3 │ %c │ %c │ %c │\n",
board[2][0], board[2][1], board[2][2]);
printf(" └───┴───┴───┘\n");
}
char get_winner() {
char player;
// Checks rows.
for (int row = 0; row < 3; row++) {
player = board[row][0];
if (player != ' ' && board[row][1] == player && board[row][2] == player) {
return player;
}
}
// Checks columns.
for (int col = 0; col < 3; col++) {
player = board[0][col];
if (player != ' ' && board[1][col] == player && board[2][col] == player) {
return player;
}
}
// Checks diagonals.
player = board[1][1];
if (player != ' ') {
if ((board[0][0] == player && board[2][2] == player)
|| (board[0][2] == player && board[2][0] == player)) {
return player;
}
}
return ' ';
}
void ask_for_input(char player) {
int n_parsed;
unsigned char col, row;
while (1) {
printf("Player %c? ", player);
n_parsed = scanf("%c%hhu", &col, &row);
// Consumes extra input characters
while (getchar() != '\n') {}
if (n_parsed == 2) {
col -= 65; // Converts to a number between 0 and 2.
row -= 1; // Converts to a number between 0 and 2.
if (col < 3 && row < 3 && board[row][col] == ' ') {
break;
}
}
printf("Invalid choice\n");
}
board[row][col] = player;
}
int main() {
char player = 'X';
while (1) {
display();
ask_for_input(player);
char winner = get_winner();
if (winner != ' ') {
display();
printf("%c won!\n", winner);
break;
}
// Ternary operator, equivalent to `if … else`.
player = player == 'X' ? 'O': 'X';
}
}
In [10]:
#include "stdio.h"
int main() {
int age = -106;
printf("%i %i %li %f",
(char)age, (unsigned char)age, (long)age, (float)age);
}
Notez que cela ne transforme pas les données stockées dans la RAM, mais bien la manière dont elles sont interprétées.
Ainsi, -106
en int
est représenté FFFFFF96
. En castant en char
, on conserve juste le dernier octet, 96
. En signed char
, 96
correspond à -106
, mais en unsigned char
il correspond à 150
.
Attention donc, le type casting n’est pas exactement une conversion, mais plutôt une interprétation différente de la même donnée.
Problème majeur avec les tableaux : ils ont une taille fixe. Au quotidien, on a sans arrêt besoin de lister des quantités indéterminées de données. Les pointeurs permettent de faire des tableaux de taille dynamique.
Les pointeurs sont un peu complexes à appréhender, mais il est primordial de les comprendre, ils sont omniprésents en C.
Un pointeur est en réalité une adresse de la RAM. à cette adresse seront stockées les données qui nous intéressent. Le &
vu lors du scanf
permet d’aller chercher l’adresse mémoire d’une variable, et ainsi de créer un pointeur. Par exemple :
In [11]:
#include "stdio.h"
int main() {
int age = 28;
int* age_pointer = &age;
printf("Mon âge, à l’adresse mémoire %lu, est %i.\n",
(unsigned long)age_pointer, age_pointer[0]);
age_pointer[0] = 29;
printf("Joyeux anniversaire ! %i ans déjà.\n", age_pointer[0]);
}
Beaucoup de choses dans l’exemple :
&age
permet d’obtenir l’adresse en RAM de la variable age
int*
est le type d’une adresse en RAM de variable int
age_pointer
contient donc l’adresse en RAM de age
: comme cela « pointe » vers age
, on dit que cette variable est un pointeurage_pointer
en unsigned long
pour pouvoir afficher quelle est l’adresse mémoire : en effet, un pointeur est lui-même stocké sous forme d’une adresse de 8 octets, permettant ainsi de le caster directement en unsigned long
age_pointer[0]
permet d’obtenir la donnée stockée à l’adresse mémoire stockée dans age_pointer
: ici, c’est donc la valeur de age
puisqu’age_pointer
pointe à l’adresse de age
.
In [ ]:
// Exercice 23
#include <stdio.h>
float divide(float* number) {
return number[0] / 3;
}
int main() {
float number;
scanf("%f", &number);
printf("%f\n", divide(&number));
}
In [12]:
#include "stdio.h"
int main() {
char name[50] = "Bertrand Bordage";
char* name_pointer = name;
printf("%c%c %s", name_pointer[0], name_pointer[7], &name_pointer[9]);
}
Quelques explications s’imposent encore.
Dans l’exemple précédent, on a utilisé à nouveau les mêmes notations, mais elles peuvent paraître incohérentes par rapport à précédemment. Pourtant tout est logique, voilà les raisons :
name_pointer
sans utiliser name
: c’est parce que name
n’est pas un char
mais un tableau de char, ce qui est un concept proche du pointeur, mais avec une taille fixe. Cette proximité permet de créer un pointeur directement à partir d’un tableau, sans &
.name_pointer[0]
correspond à la première lettre du tableau, et non au tableau lui-même : le pointeur est en fait une autre façon d’utiliser le tableau. L’adresse du pointeur est l’adresse de la première case du tableau, c’est pourquoi on ne fait pas name_pointer[0][0]
pour obtenir la première lettre.&name_pointer[9]
permet d’aller chercher la référence de la dixième case du tableau, et donc de créer un pointeur commençant à Bordage
. Le %s
du format permet d’afficher une chaîne de caractères, donc il affiche les caractères commençant à l’index 9 jusqu’à rencontrer \0
.Écrire une fonction string_length
renvoyant la longueur d’une chaîne de caractères passée en argument sous la forme de pointeur.
Interdiction bien sûr d’utiliser strlen
de string.h
.
Utiliser cette fonction pour afficher la longueur d’une chaîne saisie par un utilisateur, pouvant aller jusqu’à 10000 caractères.
In [ ]:
// Exercice 24
#include "stdio.h"
unsigned int string_length(char* data) {
unsigned int i = 0;
while (data[i] != '\0') {
i++;
}
return i;
}
int main() {
char data[10000];
scanf("%s", data);
printf("%i\n", string_length(data));
}
In [13]:
#include "stdio.h"
int main() {
char* name = "Bertrand Bordage";
printf("%s", name);
}
À partir de maintenant, on privilégiera char*
pour les chaînes de caractères.
In [14]:
#include "stdio.h"
#include "stdlib.h"
int* get_range(int n) {
int* numbers = (int*)malloc(n * sizeof(int));
while (n > 0) {
n--;
numbers[n] = n;
}
return numbers;
}
int main() {
int* range = get_range(6);
printf("%i, %i", range[0], range[5]);
free(range);
}
Dans l’exemple précédent :
malloc
et free
, permettant de réserver et libérer de la RAM.stdlib.h
contient les fonctions malloc
et free
utilisées ensuite.malloc
permet de réserver un emplacement mémoire, ici de la taille de n
int
.sizeof
permet d’obtenir le nombre d’octets d’un type, ici int
, donc cela vaut 4.malloc
car malloc
renvoie toujours le type void*
.free(range)
libère l’espace mémoire réservé dès qu’on n’en a plus besoin.Attention ! Tout malloc
doit toujours avoir un free
qui correspond, sans quoi le programme risque des fuites de mémoire, et ainsi utiliser de plus en plus de RAM inutilement.
Autre fonction utile : realloc
, qui permet d’agrandir un espace mémoire déjà réservé.
In [ ]:
// Exercice 25
#include "stdio.h"
#include "stdlib.h"
unsigned int string_length(char* data) {
unsigned int i = 0;
while (data[i] != '\0') {
i++;
}
return i;
}
char is_end_of_input(char* buffer) {
for (int i = 0; i < 10; i++) {
if (buffer[i] == '\0') {
return 1;
}
}
return 0;
}
int main() {
unsigned int size = 10;
char* data = (char*)malloc(size * sizeof(char));
char* current_buffer = data;
while (1) {
scanf("%10[^\n]", current_buffer);
if (is_end_of_input(current_buffer)) {
break;
}
size += 10;
data = (char*)realloc(data, size * sizeof(char));
current_buffer = data + size - 10;
}
printf("%i\n", string_length(data));
free(data);
}
Dernier avantage des pointeurs : ils peuvent être renvoyés par une fonction, ce qui est impossible pour un tableau.
Tout ceci dit, on peut se demander quel est l’intérêt des tableaux, quand les pointeurs sont si puissants.
Et effectivement, dans la plupart des cas, on utilise des pointeurs et non des tableaux, car il est finalement rare d’avoir des listes de données de taille fixe.
Mais il existe des cas où il est utile de donner une taille fixe à un tableau. Comme vu précédemment, cela convient par exemple pour des gobans, échiquiers, etc, dont la taille est toujours fixe.
Comme nous le verrons, cela peut également être utile pour les structures.
In [ ]:
typedef unsigned long int uint128;
typedef char chessboard[8][8];
int main() {
uint128 i = 68714234687197654;
chessboard board;
}
Ici, uint128
n’est finalement qu’un raccourci d’un type déjà existant, ce qui est peu utile.
Le second cas, est nettement plus utile. Lors d’un programme d’échecs, on n’a pas à se préoccuper du nombre de cases dans un échiquier, ou du type de chaque case. On dit juste qu’on créé un échiquier, tout simplement.
Le cas précédent est déjà utile, mais les structures sont un type composé encore plus intéressant.
stdio
contient également de quoi traiter des fichiers. Malheureusement, comme c’est plus proche de ce qu’il se passe vraiment dans la machine, c’est assez compliqué.
FILE
: type d’un fichier, utilisé avec un pointeurfopen
: fonction permettant d’ouvrir le fichier en choisissant son mode (lecture, écriture, etc)fwrite
: fonction permettant d’écrire un groupe d’octets dans le fichierfread
: fonction permettant de lire un groupe d’octets dans le fichierfeof
: fonction permettant de savoir si on est à la fin du fichierfclose
: fonction permettant de fermer et donc enregistrer le fichier
In [ ]:
#include "stdio.h"
#include "stdlib.h"
int main() {
FILE* file = fopen("example.txt", "w");
char content[] = "Lorem ipsum\n";
fwrite(content, sizeof(char), sizeof(content) - 1, file);
fclose(file);
}
In [ ]:
#include "stdio.h"
#include "stdlib.h"
int main() {
FILE* file = fopen("example.txt", "r");
if (file == NULL) {
return 1;
}
long index = 0;
long size = 0;
char* content = NULL;
while (!feof(file)) {
index = size;
size += BUFSIZ;
content = (char*)realloc(content, size * sizeof(char));
fread(&content[index], sizeof(char), BUFSIZ, file);
}
printf("%s", content);
free(content);
fclose(file);
}
In [15]:
#include "stdio.h"
#include "string.h"
typedef struct {
char title[100];
char author[60];
int pages;
} Book;
void show_book(Book book) {
printf("Book “%s” by %s contains %i pages\n",
book.title, book.author, book.pages);
}
int main() {
Book book = {"Les Piliers de la Terre", "Ken Follet", pages: 1050};
show_book(book);
book.pages = 1076;
strcpy(book.title, "Pillars of the Earth");
show_book(book);
}
Jusqu’à présent, on a créé uniquement des programmes exécutables directement. Souvent, on réunit des fonctionnalités à part dans un même module qui pourra être utilisable dans plusieurs programmes.
À chaque utilisation de module, on faisait un #include "module.h"
. Jusqu’à présent on n’a défini que des fichiers .c
, qui contiennent le fonctionnement du code. Les fichiers .h
contiennent les définitions de types et les signatures des fonctions, c’est-à-dire une version de la fonction spécifiant uniquement son nom, son type de retour et ses arguments.
In [ ]:
#include "math.h"
typedef struct {
float x;
float y;
} Point;
typedef struct {
Point a;
Point b;
Point c;
} Triangle;
float distance(Point a, Point b) {
float dx = (b.x - a.x);
float dy = (b.y - a.y);
return sqrt(dx * dx + dy * dy);
}
float perimeter(Triangle t) {
return distance(t.a, t.b) + distance(t.b, t.c) + distance(t.a, t.c);
}
In [ ]:
typedef struct {
float x;
float y;
} Point;
typedef struct {
Point a;
Point b;
Point c;
} Triangle;
float distance(Point a, Point b);
float perimeter(Triangle t);
In [ ]:
#include "stdio.h"
#include "triangle.h"
int main() {
Triangle t = {(Point){30, 5}, (Point){10, 16}, (Point){3, 8}};
printf("%f\n", perimeter(t));
}
Malheureusement, ce n’est pas de tout repos :
gcc -c -fPIC triangle.c -o triangle.o
gcc -shared triangle.o -o libtriangle.so
gcc example.c -o example -L. -ltriangle -lm
Puis on peut lancer le programme ainsi :
LD_LIBRARY_PATH=. ./example
(LD_LIBRARY_PATH
est utile ici car la librairie triangle
n’est pas installée globalement)Cette tâche laborieuse est souvent automatisée par Make ou autres outils.
Noter ici qu’on lie les librairies triangle
et m
à la troisième ligne de compilation.
C’est à cet endroit qu’on ajoute les éventuelles autres librairies à lier.
Avant l’étape de compilation, un préprocesseur entre en jeu et permet de modifier votre code avant qu’il soit compilé.
Les instructions données au préprocesseur sont appellées directives et commencent par un #
, sans ;
à la fin.
Oui, les #include
– dont la syntaxe était un peu à part – étaient bien des directives de préprocesseur.
#define
Elle permet de créer une variable globale qui sera remplacée partout juste avant la compilation. Ainsi, le préprocesseur va transformer ce code en la cellule suivante :
In [ ]:
#define SUDOKU_SIZE 8
char sudoku[SUDOKU_SIZE][SUDOKU_SIZE];
In [ ]:
char sudoku[8][8];
if
, elif
, else
, endif
On peut également définir du code en fonction d’un paramètre de préprocesseur.
C’est considéré comme une mauvaise pratique de s’en servir, mais souvent on n’a pas le choix. En effet, le nom des modules et leurs possibilités dépendent des systèmes d’exploitation. Ainsi, la plupart des conditions visibles dans des librairies en C permettent d’écrire de la compatibilité entre systèmes d’exploitation :
In [ ]:
#if defined __unix__ // For Linux and MacOS
#include "unistd.h"
#elif defined _WIN32 // For Windows (even with 64 bits…)
#include "windows.h"
#endif
Écrire un programme simulant un jeu d’échecs. À chaque coup y compris le premier, on affichera l’échiquier avec les pièces dedans et des coordonnées. On demandera tour à tour aux deux joueurs quel déplacement effectuer. La partie s’arrête quand il n’y a plus de pièces blanches ou noires dans l’échiquier. Pour commencer, on autorise tous les déplacements et on exclut les règles complexes comme les roques.
Idéalement, le programme sauvegardera automatiquement une partie au fur et à mesure qu’elle avance. La sauvegarde contiendra tous les états successifs du plateau tel qu’affichés dans le terminal.
De même, idéalement il faudrait structurer le programme de manière modulaire, de sorte d’éviter un énorme fichier contenant toute la logique.
Exemple d’entrée/sortie :
A B C D E F G H
┌───┬───┬───┬───┬───┬───┬───┬───┐
1 │ ♜ │ ♞ │ ♝ │ ♛ │ ♚ │ ♝ │ ♞ │ ♜ │
├───┼───┼───┼───┼───┼───┼───┼───┤
2 │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │
├───┼───┼───┼───┼───┼───┼───┼───┤
3 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
4 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
5 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
6 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
7 │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │
├───┼───┼───┼───┼───┼───┼───┼───┤
8 │ ♖ │ ♘ │ ♗ │ ♕ │ ♔ │ ♗ │ ♘ │ ♖ │
└───┴───┴───┴───┴───┴───┴───┴───┘
White player? B8 C6
A B C D E F G H
┌───┬───┬───┬───┬───┬───┬───┬───┐
1 │ ♜ │ ♞ │ ♝ │ ♛ │ ♚ │ ♝ │ ♞ │ ♜ │
├───┼───┼───┼───┼───┼───┼───┼───┤
2 │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │ ♟ │
├───┼───┼───┼───┼───┼───┼───┼───┤
3 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
4 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
5 │ │ │ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
6 │ │ │ ♘ │ │ │ │ │ │
├───┼───┼───┼───┼───┼───┼───┼───┤
7 │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │ ♙ │
├───┼───┼───┼───┼───┼───┼───┼───┤
8 │ ♖ │ │ ♗ │ ♕ │ ♔ │ ♗ │ ♘ │ ♖ │
└───┴───┴───┴───┴───┴───┴───┴───┘
Black player?
Twitter : @NoriPytCom - GitHub : @BertrandBordage