Aller au contenu

EX01 - Programmation C/C++ – avec solutions

Exercice 1 : Opérations binaires

Pour cet exercice, vous pouvez installer un environnement de développement C/C++ sur votre machine, ou simplement utiliser un compilateur dans le “cloud” tel que sur : tutorialspoint.com.

Vous pouvez aussi utilise Visual Studio Code avec le “devcontainer” fourni dans le dépôt ex01-starter. Pour cette option vous aurez besoin des logiciels suivants sur votre machine :

CountBits

Écrivez une fonction en C++ qui compte le nombre de bits à “1” dans un entier positif La signature de cette méthode est :

int CountBits(uint32_t n);

Testez votre fonction avec un programme principal.

Solution

Première solution : une boucle sur les bits de l’entier :

int CountBits(uint32_t n)
{
    int result = 0;
    for (int i = 0; i < (int)sizeof(n)*8; i++)
    {
        if ((n & (1 << i)) != 0)
            result++;
    }
    return result;
}

Deuxième solution, indépendente de la taille de l’entier :

int CountBits(uint32_t n)
{
    int result = 0;
    while (n != 0) {
        if (n % 2 == 1) {
            result++;
        }
        n = n / 2;
    }
    return result;
}

Troisième solution (astucieuse et rapide) :

int CountBits(uint32_t n)
{
    uint32_t t = n;
    t = (t & 0x55555555) + ((t & 0xaaaaaaaa) >> 1);
    t = (t & 0x33333333) + ((t & 0xcccccccc) >> 2);
    t = (t & 0x0f0f0f0f) + ((t & 0xf0f0f0f0) >> 4);
    t = (t & 0x00ff00ff) + ((t & 0xff00ff00) >> 8);
    t = (t & 0x0000ffff) + ((t & 0xffff0000) >> 16);
    return (int)t;
}

MostSignificantBit

Écrivez une fonction en C++ qui détermine le bit le plus signifiant dans un entier positif La signature de cette méthode est :

int MostSignificantBit(uint32_t n);

Testez votre fonction avec un programme principal.

Solution

Première solution :

int MostSignificantBit(uint32_t n)
{
    int result = 0;
    while (n != 0) {
        n = n >> 1;
        result++;
    }
    return result;
}

Deuxième solution (astucieuse et aussi rapide) :

int MostSignificantBit(uint32_t n)
{
    uint32_t t = n;
    t |= (t >> 1);
    t |= (t >> 2);
    t |= (t >> 4);
    t |= (t >> 8);
    t |= (t >> 16);
    return CountBits(t);
}

ReverseBits

Écrivez une fonction en C++ qui renverse les bits d’un entier positif. Le bit 31 (poids fort) est échangé avec le bit 0, le bit 30 est échangé avec le bit 1, etc.

La signature de cette méthode est :

uint32_t ReverseBits(uint32_t n);

Testez votre fonction avec un programme principal.

Solution

Première solution :

uint32_t ReverseBits(uint32_t n)
{
    uint32_t result = 0;
    for (int i = 0; i < (int)sizeof(n)*8; i++)
    {
        result = (result << 1) + (n % 2);
        n /= 2;
    }
    return result;
}

Deuxième solution (astucieuse et aussi rapide) :

uint32_t ReverseBits(uint32_t n)
{
    uint32_t t = n;
    t = ((t >>  1) & 0x55555555) | ((t & 0x55555555) <<  1);
    t = ((t >>  2) & 0x33333333) | ((t & 0x33333333) <<  2);
    t = ((t >>  4) & 0x0f0f0f0f) | ((t & 0x0f0f0f0f) <<  4);
    t = ((t >>  8) & 0x00ff00ff) | ((t & 0x00ff00ff) <<  8);
    t = ((t >> 16) & 0x0000ffff) | ((t & 0x0000ffff) << 16);
    return t;
}

Autres opérations binaires

Implémentez les fonctions ci-dessous conformément aux commentaires :

uint32_t SetBit(uint32_t n, int i);    // returns n with bit i set
uint32_t ClearBit(uint32_t n, int i);  // returns n with bit i cleared
uint32_t ToggleBit(uint32_t n, int i); // returns n with bit i inverted
bool TestBit(uint32_t n, int i);       // tests if bit i of n is set

Testez vos fonctions avec un programme principal.

Solution
uint32_t SetBit(uint32_t n, int i) // returns n with bit i set
{
    return n | (1 << i);
}

uint32_t ClearBit(uint32_t n, int i) // returns n with bit i cleared
{
    return n & ~(1 << i);
}

uint32_t ToggleBit(uint32_t n, int i) // returns n with bit i inverted
{
    return n ^ (1 << i);
}

bool TestBit(uint32_t n, int i) // tests if bit i of n is set
{
    return (n & (1 << i)) != 0;
}

Exercice 2 : La fonction mystère

Implémentez l’algorithme suivant dans une fonction C++:

  1. soit \(n\) un nombre tel que \(n \leq 9999\) et qui ne soit pas composé de 4 fois le même chiffre (c’est-à-dire pas 0000, 1111, 2222, …)
  2. calculer \(max\) comme le plus grand nombre de 4 digits qu’on peut obtenir avec les digits de \(n\)
  3. calculer \(min\) comme le plus petit nombre de 4 digits qu’on peut obtenir avec les digits de \(n\)
  4. calculer \(d = max - min\)
  5. si \(d = n\) retourner \(d\) et l’algorithme est terminé
  6. sinon, assigner \(d\) à \(n\) et reprendre le point 2.

Pour cet exercice, nous vous recommandons d’utiliser des vecteurs et la fonction “std::sort” proposée par la bibliothèque “algorithm”. Votre programme pourrait importer les bibliothèques suivantes :

#include <algorithm> // std::sort
#include <vector> // std::vector

Voici quelques opérations que vous pouvez faire avec un vecteur d’entiers :

std::vector<int> a;
a.insert(a.begin(), x); // insert x at the beginning of the vector
a.push_back(x); // append x at the end of the vector
std::sort(a.begin(), a.end()); // sort the vector a (ascending)
std::sort(a.begin(), a.end(), std::greater<int>()); // sort descending

Info

Si vous voulez savoir pourquoi votre méthode retourne toujours 6174, lisez l’article suivant sur Wikipedia :

https://en.wikipedia.org/wiki/6174_(number)

Solution 1 (avec des "vectors")
#include <iostream>  // std::cout
#include <algorithm> // std::sort
#include <vector>    // std::vector

constexpr int kNumberOfDigits = 4;

std::vector<int> IntegerToVector(int n)
{
    std::vector<int> res;

    while (n != 0) {
        res.insert(res.begin(), n % 10);
        n = n / 10;
    }
    // complete vector with zeros
    while (res.size() < kNumberOfDigits) {
        res.insert(res.begin(), 0);
    }
    return res;
}

int VectorToInteger(std::vector<int> a)
{
    int res = 0;
    for (auto i : a) {
        res = res * 10 + i;
    }
    return res;
}

int Step(int x)
{
    auto r = IntegerToVector(x);
    std::sort(r.begin(), r.end(), std::greater<int>());
    int max = VectorToInteger(r);
    std::sort(r.begin(), r.end());
    int min = VectorToInteger(r);
    return max - min;
}

int Mysterious(int n)
{
    while (true) {
        int x = Step(n);
        if (x == n) {
            return n;
        }
        n = x;
    }
}

int main()
{
    std::cout << Mysterious(4793) << std::endl;
}
Solution 2 (avec des "strings")
#include <cstdio>
#include <string>
#include <algorithm>

using namespace std;

int main(int argc, char* argv[])
{
    const int NB_CHAR = 4;

    // check for argument 
    if (argc != 2) return 0;

    // check for valid number within argument
    string val = argv[1];
    if (val.length() != NB_CHAR) return 0;

    // check for valid input
    char c = val[0];
    bool ok = false;
    for (auto v : val) {
        if (v != c) ok = true;
    }
    if (!ok) return 0;

    // compute 6174 (number)
    int n=0;
    int delta=0;
    do {
        n = stoi(val);

        sort (val.begin(), val.end(), std::greater<char>());
        int max = stoi(val);

        sort (val.begin(), val.end());
        int min = stoi(val);

        delta = max - min;

        val = to_string(delta);

        printf("n:%4d, max:%4d, min:%4d, d:%4d\n", n, max, min, delta);
    } while (delta != n);

    return 0;
}

Exercice 3 : Affichage 7-segments

Écrivez un programme en C++ qui simule un affichage à sept segments.

L’interface publique de la classe Segments est donnée :

class Segments {
public:
    Segments(int n = 1);
    void Set(int v);
    void Draw(int w = 3, int h = 2, int dx = 1);
private:
    // TODO
};

L’argument du constructeur indique le nombre de “digits” qui compose l’affichage. La méthode Set définit le nombre à afficher. La méthode Draw simule l’affichage avec des segments horizontaux de w caractères, des segments verticaux de h caractères et un espace de dx entre deux segments.

Testez avec le programme principal suivant :

int main()
{
    Segments s(4) ;
    s.Set(639);
    s.Draw();
}

Le résultat devrait ressembler à ça :

 ---   ---   ---   --- 
|   | |         | |   |
|   | |         | |   |
       ---   ---   --- 
|   | |   |     |     |
|   | |   |     |     |
 ---   ---   ---   --- 

Pour l’affichage, vous pouvez utiliser la classe Canvas que nous vous donnons ci-dessous, mais vous pouvez aussi faire autrement si vous voulez :

#include <vector>
#include <stdint.h>

class Canvas {
public:
    Canvas (int w, int h);
    virtual ~Canvas() {}; 
    void Clear (char c = '.');
    void HLine(int x, int y, int len, char c = '*');
    void VLine(int x, int y, int len, char c = '*');
    void Print();
private:
    std::vector<std::vector<char>> canvas_;
}; 

Canvas::Canvas (int w, int h) {
    canvas_.resize(h);
    for (int y = 0; y < h; y++) {
        canvas_[y].resize(w, ' ');
    }
}

void Canvas::Clear (char c) {
    for (int y = 0; y < canvas_.size(); y++) {
        for (int x = 0; x < canvas_[y].size(); x++) {
            canvas_[y][x] = c;
        }
    }
}

void Canvas::HLine(int x, int y, int len, char c) {
    for (int i = 0; i < len; i++) {
        canvas_[y][x+i] = c;
    }
}

void Canvas::VLine(int x, int y, int len, char c) {
    for (int i = 0; i < len; i++) {
        canvas_[y+i][x] = c;
    }
}

void Canvas::Print() {
    for (auto row : canvas_) {
        for (auto c : row) {
            printf("%c", c);
        }
        printf("\n");
    }
}
Solution
#include <iostream>
#include <vector>
#include <stdint.h>

typedef std::vector<std::vector<char> > canvas_t;

const uint8_t digits[] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111, // 9
};

class Canvas
{
public:
    Canvas(int w, int h);
    void Clear(char c = '.');
    void HLine(int x, int y, int len, char c = '*');
    void VLine(int x, int y, int len, char c = '*');
    void Print();

private:
    std::vector<std::vector<char> > canvas_;
};

Canvas::Canvas(int w, int h)
{
    canvas_.resize(h);
    for (int y = 0; y < h; y++)
    {
        canvas_[y].resize(w, ' ');
    }
}

void Canvas::Clear(char c)
{
    for (int y = 0; y < canvas_.size(); y++)
    {
        for (int x = 0; x < canvas_[y].size(); x++)
        {
            canvas_[y][x] = c;
        }
    }
}

void Canvas::HLine(int x, int y, int len, char c)
{
    for (int i = 0; i < len; i++)
    {
        canvas_[y][x + i] = c;
    }
}

void Canvas::VLine(int x, int y, int len, char c)
{
    for (int i = 0; i < len; i++)
    {
        canvas_[y + i][x] = c;
    }
}

void Canvas::Print()
{
    for (int y = 0; y < canvas_.size(); y++)
    {
        for (int x = 0; x < canvas_[y].size(); x++)
        {
            std::cout << canvas_[y][x];
        }
        std::cout << std::endl;
    }
}

class Segments
{
public:
    Segments(int n = 1);
    void Set(int v);
    void Draw(int w = 3, int h = 2, int dx = 1);

private:
    void DrawDigit(Canvas &canvas, int seg, int x, int y, int h, int w);
    std::vector<uint8_t> seg_;
};

Segments::Segments(int n)
{
    seg_.resize(n);
};

void Segments::Set(int v)
{
    for (int i = seg_.size() - 1; i >= 0; i--)
    {
        this->seg_[i] = digits[v % 10];
        v = v / 10;
    }
}

void Segments::DrawDigit(Canvas &canvas, int seg, int x, int y, int w, int h)
{
    if (seg & 0x01) canvas.HLine(x + 1,     y + 0,         w, '-');
    if (seg & 0x02) canvas.VLine(x + w + 1, y + 1,         h, '|');
    if (seg & 0x04) canvas.VLine(x + w + 1, y + h + 2,     h, '|');
    if (seg & 0x08) canvas.HLine(x + 1,     y + 2 * h + 2, w, '-');
    if (seg & 0x10) canvas.VLine(x + 0,     y + h + 2,     h, '|');
    if (seg & 0x20) canvas.VLine(x + 0,     y + 1,         h, '|');
    if (seg & 0x40) canvas.HLine(x + 1,     y + h + 1,     w, '-');
}

void Segments::Draw(int w, int h, int dx)
{
    Canvas canvas((2 + w) * seg_.size() + dx * (seg_.size() - 1), 3 + 2 * h);
    canvas.Clear(' ');
    for (int i = 0; i < seg_.size(); i++)
    {
        DrawDigit(canvas, seg_[i], (w + 2 + dx) * i, 0, w, h);
    }
    canvas.Print();
}

int main()
{
    Segments s(4);
    s.Set(639);
    s.Draw();
}

Exercice 4 : CPU à accumulateur

Un CPU basé sur un accumulateur est un type d’architecture de processeur qui utilise un registre spécial, appelé accumulateur, pour la plupart des opérations arithmétiques et logiques. Dans cette conception, l’un des opérandes pour ces opérations est toujours l’accumulateur, et le résultat est stocké de nouveau dans l’accumulateur. Cette approche simplifie la conception du CPU en réduisant le nombre de registres et de chemins de données, ce qui en a fait un choix populaire pour les premiers ordinateurs et certains processeurs modernes à faible consommation ou spécialisés.

Schéma d’un CPU à accumulateur

En vous basant sur la description ci-dessus, écrivez une classe C/C++ appelée AccumulatorCpu représentant un CPU 32-bit. La classe doit avoir les éléments suivants :

  • Une variable membre représentant l’accumulateur occupant 32 bits.
  • Des méthodes pour les opérations suivantes :
    • load (pour charger une valeur dans l’accumulateur),
    • add et subtract (pour effectuer ces opérations avec l’accumulateur et un autre opérande), et
    • store (pour stocker la valeur de l’accumulateur dans une variable donnée).
  • Une méthode display pour afficher la valeur actuelle de l’accumulateur.

Assurez-vous que votre classe encapsule correctement l’accumulateur et fournit des méthodes d’accès appropriées. Vous pouvez également inclure une fonction principale simple pour démontrer l’utilisation de votre classe AccumulatorCpu.

Exemple d’utilisation :

#include <stdio.h>
#include <stdint.h>

int main() {
    AccumulatorCpu acccpu;
    printf("\n=============================== \n");
    printf("Accumulator Computation results \n");
    printf("=============================== \n");
    acccpu.load(128);
    acccpu.add(18);
    acccpu.subtract(0x18);
    acccpu.display();

    uint32_t result;
    acccpu.store(result);
    printf("Result of the computation is : %d\n", result);

    return 0;
}
Dans cet exemple, la classe AccumulatorCpu est utilisée pour effectuer des opérations arithmétiques de base et stocker les résultats. Le résultat obtenu est le suivant :
=============================== 
Accumulator Computation results
=============================== 
Accumulator value: 0x007a
Result of the computation is : 122

Solution
class AccumulatorCPU {

public:
    AccumulatorCPU() : accumulator(0) {}

    void load(uint32_t value) {
        accumulator = value;
    }

    void add(uint32_t value) {
        accumulator += value;
    }

    void subtract(uint32_t value) {
        accumulator -= value;
    }

    void store(uint32_t &target) const {
        target = accumulator;
    }

    void display() const {
        printf("Accumulator value: 0x%04x\n", accumulator);
    }
private:
    uint32_t accumulator;
};