INF3105 —­­­­­ Structure de données et algorithmes
Automne 2017

Laboratoire 2 : Pointeurs, références, classes, constructeurs et destructeurs


Lectures préalables

Objectifs

  1. Pointeurs et références.
  2. Classes, constructeurs et destructeurs.
  3. Allocation automatique.
  4. Allocation dynamique.

Tâches


1. Les Pointeurs

  1. Créez un fichier lab2ex1.cpp, copiez et collez le code ci-dessous.
  2. #include <iostream>
    using namespace std;
    
    int main(){
        int a=1, b=2, c=3, d=4;
        int* pa=&a;
        int* pb=&b;
        int* pc=&c, *pd=&d;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "pa=" << pa << "\tpb=" << pb << "\tpc=" << pc << "\tpd=" << pd << endl;
        *pa = 4;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "pa=" << pa << "\tpb=" << pb << "\tpc=" << pc << "\tpd=" << pd << endl;
        pa = pb;
        *pa = 8;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "pa=" << pa << "\tpb=" << pb << "\tpc=" << pc << "\tpd=" << pd << endl;
        c = 10;
        d += *pd;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "pa=" << pa << "\tpb=" << pb << "\tpc=" << pc << "\tpd=" << pd << endl;
        return 0;
    }
    
  3. Ne le compilez pas tout de suite!
  4. Analysez le programme. Prédisez ce qu'il devrait faire.
  5. Compilez-le et exécutez-le.
  6. g++ lab2ex1.cpp -o lab2ex1
    ./lab2ex1
    
  7. Vérifiez l'exactitude de votre prédiction.

2. Les Références

  1. Créez un fichier lab2ex2.cpp, copiez et collez le code ci-dessous.
  2. #include <iostream>
    using namespace std;
    
    int main(){
        int a=1, b=2, c=3, d=4;
        int& ra=a;
        int& rb=&b;
        int* pc=&c, *pd=&d;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "ra=" << ra << "\trb=" << rb << "\tpc=" << pc << "\tpd=" << pd << endl;
        ra = 4;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "ra=" << ra << "\trb=" << rb << "\tpc=" << pc << "\tpd=" << pd << endl;
        ra = rb;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "ra=" << ra << "\trb=" << rb << "\tpc=" << pc << "\tpd=" << pd << endl;
        c = 10;
        d += *pd;
        cout << "a=" << a << "\tb=" << b << "\tc=" << c << "\td=" << d << endl;
        cout << "ra=" << ra << "\trb=" << rb << "\tpc=" << pc << "\tpd=" << pd << endl;
        return 0;
    }
    
  3. Selon vous, est-ce que ce programme peut être compilé sans modification?
  4. Essayez de compiler.
  5. g++ lab2ex2.cpp -o lab2ex2
    
  6. Essayez de comprendre le message d'erreur.
  7. Rappel : une référence ressemble à un pointeur, mais on n'a pas besoin d'expliciter la demande de l'adresse mémoire avec le symbole &.
  8. Appliquez la correction minimale pour rendre ce programme compilable.
  9. Comme pour l'exercice 1, analysez le programme sans l'exécuter.
  10. Prédisez ce qu'il devrait produire.
  11. g++ lab2ex2.cpp -o lab2ex2
    ./lab2ex2
    
  12. Vérifiez l'exactitude de votre prédiction.

3. Types de passage de paramètres

Expérimentez l'exemple retrouvé à la section 2.10.1 des notes de cours.

// lab2ex3.cpp
#include <iostream>
using namespace std;
void test(int a, int* b, int* c, int& d, int*& e){
    a=11; // effet local
    b++; // change l’adresse locale de b
    *c=13; // change la valeur pointee par c
    d=14; // change la valeur referee par d
    e=c; // change la valeur du pointeur (adresse) pour celle de c.
}
int main(){
    int v1=1, v2=2, v3=3, v4=4, *p5=&v1;
    test(v1, &v2, &v3, v4, p5);
    cout<<v1<<"\t"<<v2<<"\t"<<v3<<"\t"<<v4<<"\t"<<*p5<<"\t"<<endl;
    return 0;
}

4. Classes, constructeurs et destructeurs

  1. Créez un fichier point.h.
  2. #include <iostream>
    class Point {
      public:
        Point(double x=0, double y=0);
        Point(const Point&);
        ~Point();
    
        double distance(const Point&) const;
    
      private:
        double x;
        double y;
    
        friend std::ostream& operator << (std::ostream&, const Point&);
        friend std::istream& operator >> (std::istream&, Point&);
    };
    
  3. Créez un fichier point.cpp.
  4. #include <assert.h>
    #include "point.h"
    
    Point::Point(double _x, double _y) 
      : x(_x), y(_y){
    }
    Point::Point(const Point& point)
      : x(point.x), y(point.y){
    }
    Point::~Point(){
    }
    double Point::distance(const Point& point) const {
      // TODO
      return 0;
    }
    std::ostream& operator << (std::ostream& os, const Point& point) {
      os << "(" << point.x << "," << point.y << ")";
      return os;
    }
    std::istream& operator >> (std::istream& is, Point& point) {
      char po, vir, pf;
      is >> po;
      if(is){
        is >> point.x >> vir >> point.y >> pf;
        assert(po=='(');    assert(vir==',');    assert(pf==')');
      }
      return is;
    }
    
  5. Créez un fichier lab2ex4a.cpp.
  6. #include "point.h"
    using namespace std;
    int main(){
        Point a;
        Point b(3,4);
        cout << a.distance(b) << endl;
        return 0;
    }
    
  7. Compilez et exécutez.
  8. g++ -o lab2ex4a lab2ex4a.cpp point.cpp
    ./lab2ex4a
    
  9. Complétez la fonction Point::distance dans point.cpp. Vous aurez besoin de sqrt dans math.h ou de std::sqrt dans cmath.
  10. Compilez et exécutez.
  11. g++ -o lab2ex4a lab2ex4a.cpp point.cpp
    ./lab2ex4a
    
  12. Créez un fichier lab2ex4b.cpp.
  13. #include "point.h"
    using namespace std;
    void test(Point p1, Point& p2, const Point& p3, Point* p4){
       cout << p1 << endl;
       p1 = p2;
       *p4 = p3;
       p4 = &p1;
    }
    int main(){
        Point a, b(3,4), c(0,5);
        Point*d = new Point(5,0);
        test(a,b,c,d);
        cout << a << '\t' << b << '\t' << c << '\t' << *d << endl;
        return 0;
    }
    
  14. Analysez le programme et essayez de comprendre ce qu'il fait.
  15. Compilez et exécutez.
  16. g++ -o lab2ex4b lab2ex4b.cpp point.cpp
    ./lab2ex4b
    
  17. Le programme lab2ex4b.cpp libère-t-il la mémoire correctement?
  18. Que faut-il ajouter pour libérer la mémoire?
  19. Corrigez, compilez et exécutez.
  20. g++ -o lab2ex4b lab2ex4b.cpp point.cpp
    ./lab2ex4b
    
  21. Dans quel ordre sont appelés les constructeurs et destructeurs?
  22. Pour vous aider, modifiez les constructeurs et destructeurs dans point.cpp :
    Point::Point(double _x, double _y) 
      : x(_x), y(_y){
      std::cerr << "Point::Point(double, double)" << std::endl;
    }
    Point::Point(const Point& point)
      : x(point.x), y(point.y){
      std::cerr << "Point::Point(const Point&)" << std::endl;
    }
    Point::~Point(){
      std::cerr << "Point::~Point()" << std::endl;
    }
    
  23. Recompilez et réexécutez.
  24. g++ -o lab2ex4b lab2ex4b.cpp point.cpp
    ./lab2ex4b
    

5. Copie de pointeur et gestion de mémoire

    Créez le programme lab2ex5.cpp à partir du code ci-dessous.

    #include "point.h"
    
    class Rectangle{
      public:
        Rectangle();
        Rectangle(const Point& p1, const Point& p2);
      private:
        Point point1, point2;
    };
    
    Rectangle::Rectangle()
    {
    }
    
    Rectangle::Rectangle(const Point& p1, const Point& p2)
     : point1(p1)
    {
        point2 = p2;
    }
    
    int main(){
        Rectangle* r1 = new Rectangle();
        Rectangle* r2 = new Rectangle(Point(0,0), Point(10,10));
        Rectangle* r3 = new Rectangle();
        *r3 = *r2;
        r1 = r3;
        delete r1;
        delete r2;
        delete r3;
    }
    
  1. Ce programme est-il compilable? Sinon, comment doit-on le modifier?
  2. Que fait ce programme?
  3. Le deuxième constructeur Rectangle::Rectangle(const Point& p1, const Point& p2) initialise p1 et p2 de façon différente. Dans vos propres mots, expliquez la différence.
  4. Compilez et exécutez.
  5. g++ -o lab2ex5 lab2ex5.cpp point.cpp
    ./lab2ex5
    
  6. Ce programme termine-t-il anormalement? Pourquoi?
  7. Le programme libère-t-il la mémoire correctement ?
  8. Combien d'octets ne sont pas libérés correctement de la mémoire à la fin du main() ?
  9. Corrigez le programme.
  10. Recompilez et réexécutez.
  11. g++ -o lab2ex5 lab2ex5.cpp point.cpp
    ./lab2ex5
    

6. Question d'examen antérieur

Cet exercice est inspiré de la question 2 de l'examen de mi-session de la session d'hiver 2013.

Soit le programme lab2ex6.cpp suivant.

#include <iostream>

class Allo{
  public:
    Allo(int x_=0) 
     : x(x_)
    {
      std::cout << "A" << x << " ";
    }
    Allo(const Allo& autre) 
     : x(autre.x)
    {
      std::cout << "B" << x << " ";
    }
    ~Allo() { 
      std::cout << "C" << x << " ";
    }
    
    int x;
};


void f1(Allo a1, Allo& a2, Allo* a3){
    a1.x++;
    a2.x--;
    a3+=1;
    (a3->x)+=2000;
}

int main(){
    Allo tab[3];
    Allo a(20);
    Allo* b = new Allo(5);
    Allo* c = tab + 1;
    f1(a, b, c);
    std::cout << std::endl << "-------" << std::endl;
    Allo* t = new Allo[4];
    t[2] = Allo(9);
    std::cout << std::endl;
}

Avant de compiler et d'exécuter le programme ci-haut, essayez de répondre aux questions suivantes.

  1. Le programme ci-haut ne peut être compilé correctement en raison d’une erreur. Quelle est la correction minimale nécessaire pour compiler le programme?
  2. Une fois corrigé, est-ce que le programme s’exécute correctement (sans terminaison anormale) ? Sinon, indiquez l’erreur et la correction minimale pour éviter une terminaison anormale.
  3. Dessinez l’état de la pile d’exécution et du tas (heap) immédiatement avant le retour de la fonction f1.
  4. Qu’affiche le programme ?
  5. Le programme libère-t-il la mémoire correctement ?
  6. Combien d'octets ne sont pas libérés correctement de la mémoire à la fin du main() ?
  7. Comment corrigeriez-vous le programme?

/* Fin du Lab2 */
© Éric Beaudry