Resolução da Frequência

Programação Orientada para Objectos

IGE e ETI

2º semestre de 1999/2000

ISCTE


A azul (e indentadas uma vez) as respostas às questões.

Questão 1
Assinale com V (Verdadeiro) as expressões que estão correctas e com F (Falso) as que estão incorrectas.

Deve preencher todos os espaços indicados por um sublinhado (      ) com V ou F.  Qualquer espaço não preenchido será considerado como uma resposta errada.

As alíneas podem ter zero ou mais respostas correctas.  Cada resposta correctamente assinalada vale 0,5 valores.

Em todos os casos em que não é explicitamente referida a localização de uma instrução, considere que esta é dada na função main() do programa seguinte:

#include <iostream>

using namespace std;

class A {
  public:
    A(int a)
        : a(a) {
    }
    virtual void g() const = 0;

  private:
    int a;
};

class B : public A {
  public:
    int bx;
    B(int, int);  // Não há construtor por omissão!
    void g() const {
        std::cout << "Execução de B::g(). ";
    }

  private:
    void f() const {
        std::cout << "Execução de B::f(). ";
    }
};

class C : public B {
  public:
    C(int);
    void h();

  private:
    int c;
};

...

int main()
{
    ...
}

Questão 1.1
Quais das seguintes instruções estão correctas?

   F   A a(2);

A classe A é abstracta (tem uma operação abstracta [ou método puramente virtual] ), por isso não pode ser instanciada.
  F   B b(2);
A classe B tem um único construtor que requere dois argumentos inteiros.
  V   B* b = new B(2, 3);

[1,5 valores]

Questão 1.2
Quais das seguintes definições de métodos da classe C estão correctas?

  F    C::C(int aa) { B(1, 2); }

A classe C deriva de B.  Quando se constrói uma instância de C a sua parte herdada de B tem de ser inicializada através do construtor de B.  Como B não tem construtor por omissão, é obrigatório invocar um construtor de B na lista de inicializadores do construtor de C.  Note-se que a invocação do construtor de B no corpo do construtor de C não tem qualquer efeito, pois limita-se a criar uma instância temporária de B que é destruída logo após ter sido construída!
  V    void C::h() {g(); std::cout<< "Executei g()";}
  F    void C::h() {f(); std::cout<< "Executei B::f()";}
O método f() é privado de B.  Não pode ser invocado nem mesmo pelas classe derivadas de B.
[1,5 valores]
Questão 1.3
 Dado o seguinte código:
int i = 1;
double d = 2.0;
quais das seguintes instruções estão correctas?

  F    int** pi = &i;

Não se pode inicializar um ponteiro para um ponteiro para um int com o endereço de um int.  Só mesmo com o endereço de um ponteiro para um int.  No entanto a expressão &&i não faz qualquer sentido!  Porquê?
  F    int& ri = &i;
Não se pode inicializar uma referência para um int com algo que não seja um int(&i é o endereço de um int).
  F    int* pi = &d;
Não se pode inicializar um ponteiro para int com o endereço de um double!
  F    int* pi = 2;
Não se pode inicializar um ponteiro para int com inteiro!
[2 valores]
Questão 2
Considere a seguinte classe :
class GestorDeAlojamentos {
  public:
    GestorDeAlojamento();
    bool ocupa(int código, int número_de_dias, int número_de_pessoas);
    void liberta(int código);
    void mostraLivres() const;
    void mostra(int código) const;
    int conta(int código) const; // em $.

  private:
    list<Alojamento*> alojamentos;
    // ou ListaPonteiroAlojamento alojamentos;, se estiver mais à vontade com estas listas,
    // ou mesmo Lista<Alojamento*> alojamentos;.
};

Pretende-se implementar em C++ uma aplicação para apoio ao serviço de gestão de alojamentos num empreendimento turístico.  Entre outras características este sistema deve disponibilizar informação precisa sobre cada alojamento.  Essa informação deve conter: código do alojamento, categoria (primeira, segunda e terceira), indicação acerca do estado do alojamento (ocupado ou não) e, caso esteja ocupado, por quantos dias no total.

Existem dois tipos concretos de alojamento, o quarto e o apartamento:

Qualquer alojamento possui a operação bool podeOcupar(int número_de_pessoas) que  verifica se o alojamento se encontra livre e se o alojamento é adequado para o número de pessoas que o pretende ocupar.  Possui também uma operação ocupa(int número_de_dias) para fazer a ocupação do alojamento (esta operação aborta o programa se o alojamento já estiver ocupado).  Esta operação, de ocupação, encarrega-se de pedir ao utilizador aquilo que é específico de cada tipo concreto de alojamento (e.g., se se pretende serviço de bar, no caso de um quarto ou seviço de cozinha no caso de um apartamento).  Se um alojamento estiver ocupado também deve ser possível saber a sua conta através de uma operação int conta().

Devem-se poder invocar métodos que permitam libertar o alojamento, calcular a conta e mostrar no ecrã a informação sobre o alojamento.

Questão 2.1
Defina as classes Alojamento (abstracta) e Apartamento.

Não é necessário nesta alínea implementar qualquer um dos métodos das classes definidas.

Faz-se aqui a definição quase completa das classes.  Tal não era necessário!

enum Categoria {primeira = 1, segunda, terceira};

class Alojamento {
  public:
    Alojamento(int código, Categoria categoria);
    virtual ~Alojamento() {}

    // Inspectores:
    int código() const;
    Categoria categoria() const;
    bool ocupado() const;
    int dias() const;

    virtual void mostra() const;
    virtual int conta() const = 0;

    // Apenas verifica se o alojamento está livre.  Alojamentos concretos sobrepõem versões
    // especializadas para verificar o número de pessoas, por exemplo:
    virtual bool podeOcupar(int número_de_pessoas);

    // Marca alojamento como ocupado.  Alojamentos concretos sobrepõem versões
    // especializadas que indagam acerca de serviços particulares (e.g., bares ou cozinha).
    // É puramente virtual apenas para tornar a classe abstracta e forçar a classe derivadas a
    // fornecerem a sua própria especialização:
    virtual void ocupa(int número_de_dias) = 0;
    virtual void liberta();

  private:
    int const código_;
    Categoria const categoria_;
    bool ocupado_;
    int dias_;
};

inline Alojamento::Alojamento(int código, Categoria categoria)
    : código_(código), categoria_(categoria) {
}

inline int Alojamento::código() const {
    return código_;
}

inline Categoria Alojamento::categoria() const {
    return categoria_;
}

inline bool Alojamento::ocupado() const {
    return ocupado_;
}

inline int Alojamento::dias() const {
    return dias_;
}

void Alojamento::mostra() const {
    cout << "Código do alojamento: " << código() << endl;
    cout << categoria() << "ª categoria" << endl;
    cout << (ocupado? "Ocupado" : "Livre") << endl;
}

inline bool Alojamento::podeOcupar(int número_de_passoas) const {
    return !ocupado();
}

inline void Alojamento::ocupa(int número_de_dias) {
    ocupado = true;
    dias_ = número_de_dias;
}

inline void Alojamento::liberta() {
    ocupado = false;
}

class Quarto : public Alojamento {
  public:
    Quarto(int código, Categoria categoria);

    // Inspectores:
    int lotação() const;
    bool serviçoDeBar() const;

    virtual void mostra() const;
    virtual int conta() const;
    virtual bool podeOcupar(int número_de_pessoas);

    virtual void ocupa(int número_de_dias);

  private:
    static int const lotação_ = 2;
    bool serviço_de_bar;
};

inline Quarto::Quarto(int código, Categoria categoria)
    : Alojamento(código, categoria) {
}

void Quarto::mostra() const {
    cout << "Tipo de alojamento: Quarto" << endl;
    Alojamento::mostra();
    cout << "Utiliza bar: " << (serviçoDeBar() ? "Sim" : "Não" << endl;
}

int Quarto::conta() const {
    ...
}

inline bool Quarto::podeOcupar(int número_de_pessoas) {
    return Alojamento::podeOcupar() && número_de_pessoas <= lotação();
}

void Quarto::ocupa(int número_de_dias) {
    Alojamento::ocupa(número_de_dias);
    cout << "Pretence serviço de bar? ";
    char resposta;
    cin >> resposta;
    serviço_de_bar = resposta == 's' || resposta == 'S';
}

class Apartamento : public Alojamento {
  public:
    Apartamento(int código, Categoria categoria);

    // Inspectores:
    int lotação() const;
    bool serviçoDeCozinha() const;

    virtual void mostra() const;
    virtual int conta() const;
    virtual bool podeOcupar(int número_de_pessoas);

    virtual void ocupa(int número_de_dias);

  private:
    static int const lotação_ = 5;
    bool serviço_de_cozinha;
};

inline Apartamento::Apartamento(int código, Categoria categoria)
    : Alojamento(código, categoria) {
}

int Quarto::conta() const {
    ...
}

void Apartamento::mostra() const {
    cout << "Tipo de alojamento: Apartamento" << endl;
    Alojamento::mostra();
    cout << "Utiliza cozinha: " << (serviçoDeCozinha() ? "Sim" : "Não"
         << endl;
}

inline bool Apartamento::podeOcupar(int número_de_pessoas) {
    return Alojamento::podeOcupar() && número_de_pessoas <= lotação();
}

void Apartamento::ocupa(int número_de_dias) {
    Alojamento::ocupa(número_de_dias);
    cout << "Pretence serviço de cozinha? ";
    char resposta;
    cin >> resposta;
    serviço_de_cozinha = resposta == 's' || resposta == 'S';
}

[5 valores]
Questão 2.2
Assuma que a invocação do método void Alojamento::mostra() const faz aparecer no ecrã informação com o seguinte aspecto:
Código do alojamento: 4
1ª categoria
Ocupado
Implemente o método void Apartamento::mostra() const, que mostre no ecrã toda a informação relativa a este apartamento no seguinte formato:
Tipo de alojamento: Apartamento
Código do alojamento: 4
1ª categoria
Ocupado
Utiliza cozinha: Não
Ver resolução completa acima.
[2 valores]
Questão 2.3
Implemente o método  bool GestorDeAlojamentos::ocupa(int código, int número_de_dias, int número_de_pessoas).  Este método deve devolver false caso o código não exista, alojamento com o código indicado esteja ocupado ou o número de pessoas exceda a capacidade do alojamento indicado.  Este método deve devolver true caso seja possível ocupar o alojamento.  Nesse caso deve também proceder à sua ocupação.  Não deve abortar o programa em nenhuma circunstância.

Assuma que os restantes métodos das classes Alojamento , Quarto e Apartamento já estão definidos.

bool GestorDeAlojamentos::ocupa(int código, int número_de_dias,
                                int número_de_pessoas)
{
    list<Alojamento*>::iterador i = alojamentos.begin();
    while(i != alojamentos.end() && (*i)->código() != código)
        ++i;
    if(i == alojamentos.end() || !(*i)->podeOcupar(número_de_passoas)) {
        return false;
    } else {
        (*i)->ocupa(número_de_dias);
        return true;
    }
}
[3 valores]
Questão 3
As matrizes clássicas do C++ têm algumas características antipáticas.  Seria desejável verificar a validade dos índices usados no acesso aos seus elementos.  Pretende-se que a classe abaixo (Matriz) substitua com vantagem as matrizes clássicas do C++.  Ao contrário do que se passa com estas, a nova classe deve lançar uma excepção sempre que for tentada uma indexação inválida.  Defina uma classe para representar este tipo de excepção e complete o operador [] da classe:
class Matriz {
  public:
    typedef Elemento double;
    typedef int;

    Matriz(int tamanho);
    ...
    int tamanho();
    ...
    Elemento const& operator [] (int i) const;
    ...

    // Defina aqui a classe para representar excepções de indexação inválida:
    classe ÍndiceInválido {
    };

  private:
    Elemento* elementos; // matriz dinâmica...
    int número_de_elementos;
};

// Os índices válidos da matriz situam-se entre 0 e tamanho() - 1.
inline Matriz::Elemento const& Matriz::operator [] (int i) const {
    if(i < 0 || i >= tamanho())
        throw ÍndiceInválido();
    return elementos[i];
}

[3 valores]
Questão 4
Dada a classe
class A {
  public:
    A(int x, int y)
        : a(new int(x)), b(y) {
    }
    ~A() {
        delete a;
    }
    ...
  private:
    int* a;
    int b;
};
indique qual a sequência de acontecimentos quando são executadas as seguintes instruções:
A* aa = new A(4, 5); // linha 1
...                  // linha 2
delete aa;           // linha 3
Indique claramente a ordem dos acontecimentos.  Distinga entre reserva de memória e construção (inicialização) das variáveis.
Descrição exaustiva:

Na linha 1 é construída uma nova variável normal (não-dinâmica) aa do tipo ponteiro para A que é inicializada com o endereço de uma nova variável dinâmica da classe A.  A variável dinâmica é construída pelo operador new, que se encarrega de reservar memória sufiente para essa variável e, em seguida, de invocar o seu construtor para que ela fique inicializada apropriadamente.  O construtor invocado recebe como argumentos os valores 4 e 5, que são usados para inicializar os parâmetros respectivos x e y.  Depois as variáveis membro a e b são inicializadas, por esta ordem.  A variável a é inicializada com o endereço de uma nova variável dinâmica do tipo int.  A construção desta variável dinâmica consiste na reserva de memória e posterior iniciali zação com 4.  A variável b é inicializada com 5.

Note-se que o endereço das variáveis dinâmicas só é colocado nos respectivos ponteiros após terem sido construídas.

Na linha 3 a variável dinâmica apontada por aa é destruída.  Isso implica invocar o destrutor da classe.  Este começa por destruir a variável dinâmica apontada pela variável membro a (que por sua vez consiste apenas na libertação da respectiva memória, pois variáveis de tipos básicos não têm destrutores).  Despois seriam destruídas as variáveis membro b e a, por esta ordem.  Mas como a é int (um tipo básico) e b um ponteiro, não têm destrutores, pelo que não acontece nada.

Lista exaustiva de acontecimentos:

Linha 1:

  1. Reserva de memória para aa.
  2. Construção de nova variável dinâmica da classe A:
    1. Reserva de memória suficiente para uma instância de A.
    2. Invocação do construtor de A:
      1. Construção de a:
        1. Construção de uma variável dinâmica do tipo int.
          1. Reserva de memória para um int.
          2. Inicialização do int com 4.
        2. Inicialização de a com o endereço da variável dinâmica.
      2. Construção de b (inicialização com 5).
      3. Execução do corpo (vazio) do construtor.
  3. Inicialização de aa com o endereço da variável dinâmica.
Linha 3:
  1. Destruição de variável dinâmica da classe A apontada por aa:
    1. Invocação do destrutor da classe A:
      1. Execução do corpo do destrutor de A:
        1. Destruição da variável dinâmica apontada por a:
          1. Invocação do destrutor (vazio) dos int.
          2. Libertação da memória ocupada pela variável dinâmica.
      2. Invocação do destrutor (vazio) do ponteiro a.
      3. Invocação do destrutor (vazio) da variável b.
    2. Libertação da memória ocupada pela variável dinâmica.
[2 valores]