# Cours 2 - Opérateurs, expressions et types

Ze veux de la doc !

Mon site préféré pour apprendre: learncpp.com https://learncpp.com

Mon préféré, CPP Reference (cppreference.com) quick, easy, rapide, facile, to the point, vers le point CPP Reference

Par exemple, le prof il a fait rien qu’à m’embêter avec la function now() de systrem_clock de chrono de std: https://en.cppreference.com/w/cpp/chrono/system_clock/now

Opérateurs arithmétiques

OpérateurOpérationExempleRésultat
+Addition7 + 310
-Soustraction7 - 34
*Multiplication7 * 321
/Division7 / 32 (entier !)
%Modulo (reste)7 % 31

Attention à la division entière ! 7 / 3 donne 2, et non 2.333.... Si vous voulez le résultat décimal, au moins un des opérandes doit être à virgule flottante : 7.0 / 3 donne 2.333...

Incrément et décrément : prefix vs postfix

int x {5};
int a = ++x;  // PREFIX:  x becomes 6, then a gets 6  (increment first)
int b = x++;  // POSTFIX: b gets 6, then x becomes 7  (use first, then increment)
 
// À éviter, il vaut mieux ceci, qui est plus clair
int x {5};
x++;
int a {x};
int b {x};
x++;
 
#Les 4 instructions font la même chose, i.e. ajouter 1 à x:
++x;
x++;
x += 1;
x = x + 1;

Opérateurs d'affectation composés

OpérateurÉquivalentExemple
+=x = x + nhealth += 10;
-=x = x - nhealth -= damage;
*=x = x * nscore *= multiplier;
/=x = x / nspeed /= 2;
%=x = x % nangle %= 360;

Priorité des opérateurs (simplifiée) De la priorité la plus haute à la plus basse :

PrioritéOpérateurs
1() (parenthèses)
2++ -- (préfixe), !
3* / %
4+ -
5< <= > >=
6== !=
7&&
8||
9= += -= etc.

Bonne pratique : En cas de doute, utilisez des parenthèses pour clarifier l’intention.

Exemple : conversion de distances

// Converting distances
#include <iostream>
#include <format>
 
int main()
{
    unsigned int yards {}, feet {}, inches {};
 
    // Convert a distance in yards, feet, and inches to inches
    std::cout << "Enter a distance as yards, feet, and inches ";
    std::cout << "with the three values separated by spaces: " << std::endl;
    std::cin >> yards >> feet >> inches;
 
    const unsigned feet_per_yard {3};
    const unsigned inches_per_foot {12};
 
    unsigned total_inches {};
    total_inches = inches + inches_per_foot * (yards * feet_per_yard + feet);
    std::cout << "This distance corresponds to " << total_inches << " inches." << std::endl;
 
    // Convert a distance in inches to yards, feet, and inches
    std::cout << "Enter a distance in inches: ";
    std::cin >> total_inches;
    feet   = total_inches / inches_per_foot;
    inches = total_inches % inches_per_foot;
    yards  = feet / feet_per_yard;
    feet   = feet % feet_per_yard;
    
    // We can also use the std:format module (similar to Python's f-string formatting)
    std::cout << std::format("This distance corresponds to {} yards {} feet {} inches.",
                 yards, feet, inches) << std::endl;
}

Points clés à retenir :

  • std::cin >> lit à partir de l’entrée clavier

  • La division entière / donne le quotient (partie entière)

  • Le modulo % donne le reste

  • Ces deux opérateurs combinés sont parfaits pour les conversions d’unités

☝🏼

Écrivez un code similaire, cette fois-ci pour convertir mètres, centimètres et millimètres

Solution

// Converting distances
#include <iostream>
#include <format>
 
int main()
{
    unsigned int meters {}, centimeters {}, millimeters {};
    // Convert a distance in meters, centimeters, and millimeters to millimeters
    std::cout << "Enter a distance as meters, centimeters, and millimeters ";
    std::cout << "with the three values separated by spaces: " << std::endl;
    std::cin >> meters >> centimeters >> millimeters;
    
    const unsigned cm_per_meter {100};
    const unsigned mm_per_cm {10};
    
    unsigned total_millimeters {};
    
    total_millimeters = millimeters + mm_per_cm * (meters * cm_per_meter + centimeters);
    std::cout << "This distance corresponds to " << total_millimeters << " millimeters." << std::endl;
    
    // Convert a distance in millimeters to meters, centimeters, and millimeters
    std::cout << "Enter a distance in millimeters: ";
    std::cin >> total_millimeters;
   
    centimeters  = total_millimeters / mm_per_cm;
    millimeters  = total_millimeters % mm_per_cm;
    meters       = centimeters / cm_per_meter;
    centimeters  = centimeters % cm_per_meter;
 
    // We can also use the std:format module (similar to Python's f-string formatting)
    std::cout << std::format("This distance corresponds to {} meters {} centimeters {} millimeters.",
                 meters, centimeters, millimeters) << std::endl;
}

Conversions de types

Casts explicites : static_cast<> Quand vous voulez convertir intentionnellement, utilisez static_cast<> :

int total {7};
int count {3};
double average = static_cast<double>(total) / count;  // 2.333... (correct!)
// Without cast: total / count = 2 (integer division!)

Ok, we know about the what of static_cast but not the why; meaning, why not just do this instead?

double average = (double)total / count;  // C-style cast

Both produce the same result. The reason static_cast is preferred in modern C++ comes down to a few things:

  1. It’s explicit about intent. The angle bracket syntax is verbose on purpose — it makes the cast impossible to miss during a code review. A C-style cast (double) can visually blend into surrounding code.
  2. It’s safer. static_cast only performs conversions the compiler considers “reasonable” — like numeric type promotions. The C-style cast is a blunt instrument that will also attempt reinterpret_cast and const_cast behavior, meaning it can do things you didn’t intend without warning.
  3. It’s searchable. If a bug is traced back to a cast, you can grep for static_cast in a large codebase. Good luck searching for (double) without tons of false positives.
  4. It fails loudly when appropriate. If the conversion isn’t valid, static_cast will produce a compile error. The C-style cast will often find some interpretation and proceed silently.

Pourquoi les casts de style C sont dangereux

double pi {3.14159};
int truncated = (int)pi;          // C-style cast — compiles silently, loses data
int better = static_cast<int>(pi); // C++ cast — intent is clear, same result

**static_cast<>** est préférable car :

  • Il est recherchable dans le code (grep pour static_cast)
  • Il n’autorise que les conversions “logiques”
  • Il montre clairement l’intention — vous vouliez convertir

Exemple : calculateur de bassin (tout combiner)

// Sizing a pond for happy fish
import std;
 
int main()
{
  // 0.2 square meters pond surface for every 15 cm of fish
  const double fish_factor { 0.2 / 0.15 };  // Area per unit length of fish (m² per m)
 
  double fish_count {};            // Number of fish
  double fish_length {};           // Average length of fish (cm)
 
  std::print("Enter the number of fish you want to keep: ");
  std::cin >> fish_count;
  std::print("Enter the average fish length in centimeters: ");
  std::cin >> fish_length;
  fish_length /= 100.0;  // Convert cm to meters
  std::println("");
 
  // Calculate the required surface area
  const double pond_area {fish_count * fish_length * fish_factor};
 
  // Calculate the pond diameter from the area
  const double pond_diameter {2.0 * std::sqrt(pond_area / std::numbers::pi)};
 
  // Convert to meters and centimeters
  const double pond_diameter_meters { std::floor(pond_diameter) };
  const double pond_diameter_cm {
      std::round((pond_diameter - pond_diameter_meters) * 100.0) };
 
  std::println("Pond diameter required for {} fish is {} m {} cm.",
               fish_count, pond_diameter_meters, pond_diameter_cm);
}

Nouveaux concepts présentés :

  • std::sqrt() — fonction racine carrée
  • std::numbers::pi — constante C++20 pour pi
  • std::floor() — arrondit vers le bas
  • std::round() — arrondit au plus proche
  • /= affectation composée

Exercices

Exercice 1 : Énigmes de priorité, prédisez la valeur de chaque expression, puis vérifiez :

int a = 2 + 3 * 4;        // ?
int b = (2 + 3) * 4;      // ?
int c = 10 / 3;           // ?
int d = 10 % 3;           // ?
double e = 10 / 3;        // ?
double f = 10.0 / 3;      // ?

Exercice 2 : Construire un convertisseur de température, écrivez un programme qui :

  1. Demande à l’utilisateur une température en Celsius
  2. Convertit en Fahrenheit : F = C * 9/5 + 32
  3. Convertit en Kelvin : K = C + 273.15
  4. Affiche les trois valeurs formatées à 1 décimale

Indice : Utilisez std::println("Temperature: {:.1f}", value) pour 1 décimale.

Exercice 3 : Calculatrice mixte, construisez une calculatrice qui :

  1. Lit deux nombres (autorisez les entrées décimales)
  2. Effectue +, -, *, / sur ces nombres
  3. Affiche les résultats pour toutes les opérations
  4. Gère correctement la division (pas de troncature entière)

Solutions

Solution de l'exercice 1 : Énigmes de priorité

int a = 2 + 3 * 4;        // 14  (multiplication d'abord: 3*4=12, puis 2+12=14)
int b = (2 + 3) * 4;      // 20  (parenthèses d'abord: 5*4=20)
int c = 10 / 3;           // 3   (division entière, tronquée)
int d = 10 % 3;           // 1   (reste de la division: 10 - 3*3 = 1)
double e = 10 / 3;        // 3.0 (division entière d'abord → 3, puis converti en double)
double f = 10.0 / 3;      // 3.333... (division flottante car 10.0 est un double)

Solution de l'exercice 2 : Convertisseur de température

import std;
 
int main()
{
  double celsius {};
  std::print("Entrez une température en Celsius : ");
  std::cin >> celsius;
 
  const double fahrenheit {celsius * 9.0 / 5.0 + 32.0};
  const double kelvin {celsius + 273.15};
 
  std::println("Celsius :    {:.1f} °C", celsius);
  std::println("Fahrenheit : {:.1f} °F", fahrenheit);
  std::println("Kelvin :     {:.1f} K", kelvin);
}

Solution de l'exercice 3 : Calculatrice mixte

import std;
 
int main()
{
  double a {}, b {};
 
  std::print("Entrez le premier nombre : ");
  std::cin >> a;
  std::print("Entrez le deuxième nombre : ");
  std::cin >> b;
 
  std::println("{} + {} = {}", a, b, a + b);
  std::println("{} - {} = {}", a, b, a - b);
  std::println("{} * {} = {}", a, b, a * b);
 
  if (b != 0.0)
    std::println("{} / {} = {}", a, b, a / b);
  else
    std::println("Division par zéro impossible !");
}

# ID Software, Quake II et Q_rsqrt : Ouate de Phoque ?

L’inverse de la racine carré d’un nombre, c’est l’une des opérations les plus fondamentales de toute la 3D temps réel.

La normalisation de vecteur

Normaliser un vecteur, c’est le ramener à une longueur entre 0.0 et 1.0 (un vecteur unitaire) tout en conservant sa direction.

La formule est :

v_normalisé = v / |v|

où |v| est la longueur du vecteur :

|v| = sqrt(x² + y² + z²)

Donc normaliser, c’est diviser chaque composante par cette longueur :

v_normalisé = v * (1 / sqrt(x² + y² + z²))

C’est exactement 1 / sqrt(value). L’inverse de la racine carrée n’est pas un truc de mathématicien — c’est la normalisation, exprimée de façon optimale.

Pourquoi a-t-on besoin de vecteurs unitaires partout ?

L’éclairage (Phong, Lambert, PBR)

C’est le cas le plus critique. Pour calculer si une surface est éclairée, on fait le produit scalaire (dot product) entre la normale de surface et la direction de la lumière :

intensité = dot(N, L) = N.xL.x + N.yL.y + N.z*L.z

Ce calcul ne donne un résultat correct que si N et L sont des vecteurs unitaires. Si la normale a une longueur de 2.5 au lieu de 1.0, ta surface sera 2.5× trop lumineuse. Dans Quake III, chaque pixel, chaque frame, pour chaque surface — on normalisait des vecteurs en permanence. C’est pour ça que la performance de 1/sqrt() importait autant.

Les normal maps
Chaque texel d’une normal map encode une normale. Au moment du rendu, le GPU doit normaliser ces normales après interpolation entre les vertices (l’interpolation brise la longueur unitaire). Des millions de normalisations par frame.

Les réflexions et réfractions

R = reflect(I, N) // nécessite N unitaire

Utilisé dans les shaders PBR, les ray tracers, les cubemaps.

L’animation et les quaternions Les quaternions de rotation doivent rester normalisés pour représenter des rotations pures. Après des interpolations (SLERP, blending de poses), on renormalise pour éviter la dérive.

La physique — détection de collisions Les normales de contact, les axes de séparation (SAT), les plans de collision — tous nécessitent des vecteurs unitaires pour que les calculs de distance et de projection soient corrects.

Le fait que Quake III faisait tourner des niveaux entiers avec des centaines d’entités éclairées en temps réel en 1999 sur du matériel 200 MHz, c’est en grande partie grâce à cette optimisation. Chaque personnage, chaque mur éclairé dynamiquement — des milliers de normalisations par frame, accélérées par ce hack de 6 lignes.

Dans Blender et Unreal aujourd’hui, c’est le GPU qui fait ces millions de normalisations en parallèle, mais le principe est identique — et les shaders GLSL/HLSL ont même une fonction intégrée normalize() qui fait exactement v * inversesqrt(dot(v,v)).

Quake’s Q_rsqrt (avec son WTF et son UB), une version équivalente moderne et un appel à la fonction sqrt standard:

// Benchmark: Original Q_rsqrt (UB) vs Modern bit_cast version
// Requires C++20 with standard library modules support.
 
import std;
 
// ─────────────────────────────────────────────
//  Original Quake III version (undefined behavior)
//  NOTE: uses ‘long’ which was 32-bit on the original
//  target (MSVC/x86). On 64-bit Linux, long is 64-bit,
//  which BREAKS the bit trick — demonstrating real UB!
//  We preserve the original code exactly as written.
// ─────────────────────────────────────────────
float Q_rsqrt_original(float number)
{
long i;
float x2, y;
const float threehalfs = 1.5F;
 
 
x2 = number * 0.5F;
y  = number;
i  = * ( long * ) &y;            // evil floating point bit level hacking
i  = 0x5f3759df - ( i >> 1 );   // what the fuck?
y  = * ( float * ) &i;
y  = y * ( threehalfs - ( x2 * y * y ) );  // 1st iteration of Newton's method
 
return y;
 
 
}
 
// ─────────────────────────────────────────────
//  Modern C++20 version (defined behavior)
// ─────────────────────────────────────────────
float Q_rsqrt_modern(float number)
{
const float x2 = number * 0.5F;
const float threehalfs = 1.5F;
 
 
std::uint32_t i = std::bit_cast<std::uint32_t>(number);
i = 0x5f3759df - ( i >> 1 );
float y = std::bit_cast<float>(i);
y = y * ( threehalfs - ( x2 * y * y ) );
 
return y;
 
 
}
 
// ─────────────────────────────────────────────
//  std::sqrt reference (for accuracy comparison)
// ─────────────────────────────────────────────
float rsqrt_reference(float number)
{
return 1.0F / std::sqrt(number);
}
 
// ─────────────────────────────────────────────
//  Benchmark helper
// ─────────────────────────────────────────────
template<typename Func>
double run_benchmark(Func func, const std::vector<float>& inputs, int iterations)
{
// Warm up
volatile float sink {};
for (auto v : inputs) sink = func(v);
 
 
auto start = std::chrono::high_resolution_clock::now();
 
for (int i = 0; i < iterations; ++i)
    for (auto v : inputs)
        sink = func(v);
 
auto end = std::chrono::high_resolution_clock::now();
 
std::chrono::duration<double, std::milli> elapsed = end - start;
return elapsed.count();
 
 
}
 
int main()
{
// Build a set of test values (avoid 0)
const int sample_count { 10000 };
const int iterations   { 1000 };
 
 
std::vector<float> inputs(sample_count);
for (int i = 0; i < sample_count; ++i)
    inputs[i] = 0.1F + static_cast<float>(i) * 0.01F;
 
std::cout << std::fixed << std::setprecision(6);
 
// ── Accuracy check ──────────────────────────────────────
std::cout << "\n=== Accuracy Check (first 5 values) ===\n";
std::cout << std::setw(10) << "Input"
          << std::setw(16) << "Reference"
          << std::setw(16) << "Original"
          << std::setw(16) << "Modern"
          << std::setw(14) << "Orig Error%"
          << std::setw(14) << "Mod Error%"
          << '\n';
std::cout << std::string(86, '-') << '\n';
 
for (int i = 0; i < 5; ++i)
{
    float v    = inputs[i];
    float ref  = rsqrt_reference(v);
    float orig = Q_rsqrt_original(v);
    float mod  = Q_rsqrt_modern(v);
 
    double err_orig = std::abs((orig - ref) / ref) * 100.0;
    double err_mod  = std::abs((mod  - ref) / ref) * 100.0;
 
    std::cout << std::setw(10) << v
              << std::setw(16) << ref
              << std::setw(16) << orig
              << std::setw(16) << mod
              << std::setw(13) << err_orig << "%"
              << std::setw(13) << err_mod  << "%"
              << '\n';
}
 
// ── Timing benchmark ────────────────────────────────────
std::cout << "\n=== Benchmark ("
          << sample_count << " values × "
          << iterations   << " iterations) ===\n";
 
double time_orig = run_benchmark(Q_rsqrt_original, inputs, iterations);
double time_mod  = run_benchmark(Q_rsqrt_modern,   inputs, iterations);
double time_ref  = run_benchmark(rsqrt_reference,  inputs, iterations);
 
std::cout << std::setprecision(3);
std::cout << std::setw(20) << "Original (UB):  " << time_orig << " ms\n";
std::cout << std::setw(20) << "Modern (C++20): " << time_mod  << " ms\n";
std::cout << std::setw(20) << "std::sqrt ref:  " << time_ref  << " ms\n";
 
double speedup = time_ref / std::min(time_orig, time_mod);
if (speedup >= 1.0)
    std::cout << "\nFast inverse sqrt is ~" << speedup << "x faster than 1/std::sqrt()\n";
else
    std::cout << "\n1/std::sqrt() is ~" << (1.0 / speedup) << "x faster than Fast inverse sqrt\n";
 
std::cout << "\nNote: with modern CPUs the gap is much smaller than in 1999!\n"
          << "      Hardware rsqrtss instruction often beats both.\n\n";
 
return 0;
 
}

Travail personnel

Lecture

  • Lire Beginning C++23 Chapitre 2

Exercices

Exercice 1 : Énigmes de priorité
Prédisez la valeur de chaque expression, puis vérifiez :

int a = 2 + 3 * 4;        // ?
int b = (2 + 3) * 4;      // ?
int c = 10 / 3;           // ?
int d = 10 % 3;           // ?
double e = 10 / 3;        // ?
double f = 10.0 / 3;      // ?

Exercice 2 : Construire un convertisseur de température
Écrivez un programme qui :

  1. Demande à l’utilisateur une température en Celsius
  2. Convertit en Fahrenheit : F = C * 9/5 + 32
  3. Convertit en Kelvin : K = C + 273.15
  4. Affiche les trois valeurs formatées à 1 décimale

Indice : Utilisez std::cout << std::format("Temperature: {:.1f}", value) << std::endl; pour 1 décimale.

Exercice 3 : Calculatrice mixte
Construisez une calculatrice qui :

  1. Lit deux nombres (autorisez les entrées décimales)
  2. Effectue +, , , / sur ces nombres
  3. Affiche les résultats pour toutes les opérations
  4. Gère correctement la division (pas de troncature entière)

Solutions

Solution de l’exercice 1 : Énigmes de priorité

int a = 2 + 3 * 4;        // 14  (multiplication d'abord: 3*4=12, puis 2+12=14)
int b = (2 + 3) * 4;      // 20  (parenthèses d'abord: 5*4=20)
int c = 10 / 3;           // 3   (division entière, tronquée)
int d = 10 % 3;           // 1   (reste de la division: 10 - 3*3 = 1)
double e = 10 / 3;        // 3.0 (division entière d'abord → 3, puis converti en double)
double f = 10.0 / 3;      // 3.333... (division flottante car 10.0 est un double)

Solution de l’exercice 2 : Convertisseur de température

#include <iostream>
#include <format>
int main()
{
  double celsius {};
  std::cout << std::format("Entrez une température en Celsius : ");
  std::cin >> celsius;
 
  const double fahrenheit {celsius * 9.0 / 5.0 + 32.0};
  const double kelvin {celsius + 273.15};
 
  std::cout << std::format("Celsius :    {:.1f} °C", celsius) << std::endl;
  std::cout << std::format("Fahrenheit : {:.1f} °F", fahrenheit) << std::endl;
  std::cout << std::format("Kelvin :     {:.1f} K", kelvin) << std::endl;
}

Solution de l’exercice 3 : Calculatrice mixte

#include <iostream>
#include <format>
int main()
{
  double a {}, b {};
 
  std::cout << std::format("Entrez le premier nombre : ");
  std::cin >> a;
  std::cout << std::format("Entrez le deuxième nombre : ");
  std::cin >> b;
 
  std::cout << std::format("{} + {} = {}", a, b, a + b) << std::endl;
  std::cout << std::format("{} - {} = {}", a, b, a - b) << std::endl;
  std::cout << std::format("{} * {} = {}", a, b, a * b) << std::endl;
 
  if (b != 0.0)
    std::cout << std::format("{} / {} = {}", a, b, a / b) << std::endl;
  else
    std::cout << std::format("Division par zéro impossible !") << std::endl;
}