Resolução do Exame de 2 ª época

Programação Orientada para Objectos

IGE e ETI

2º semestre de 1999/2000

ISCTE


A azul (e identadas 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.

Nas alíneas em que apenas uma resposta está correcta (se existirem estão assinaladas no texto), responder com mais ou menos do que um V anula a cotação.  A resposta correcta corresponde à cotação completa.  Qualquer outra resposta corresponde a zero 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>
#include <cassert>

using namespace std;

class Vector {
  public:
    typedef int Valor;

    Vector(int tamanho);
    virtual ~Vector() { ... }

    int tamanho() const { return tamanho_; }

    virtual Valor const& operator [] (int i) const = 0;
    virtual Valor& operator [] (int i) = 0;

  protected:
    virtual Valor& acede(int i) { return dados[i]; }

  private:
    int tamanho_;
    Valor* dados;
};

inline Vector::Valor const& Vector::operator [] (int i) const {
    return dados[i];
}

Vector::Vector(int tamanho) : tamanho_(tamanho), dados(new Valor[tamanho]) {
    assert(tamanho >= 0);
    for(int i = 0; i != tamanho; ++i)
        dados[i] = Valor();
}

class VectorVerificado : public Vector {
public:
    VectorVerificado(int tamanho) : Vector(tamanho) {}
    virtual Valor const& operator [] (int i) const {
        assert(0 <= i && i < tamanho());
        return Vector::operator [] (i);
    }
    virtual Valor& operator [] (int i) {
        assert(0 <= i && i < tamanho());
        return acede(i);
    }
};

int main() {
    ...
}

Questão 1.1
Quais das seguintes instruções estão correctas se colocadas no corpo do destrutor da classe Vector?

 F  free(dados);

Esta versão só seria válida se a matriz dinâmica tivesse sido reservada com o velho malloc() do C.
 F  delete[tamanho] dados;
Não é permitido indicar a dimensão da matriz no operador delete[].  A razão é simples: o sistema sabe essa dimensão (é usado um pequeno truque para guardar essa dimensão um pouco antes do início da matriz, num local invisível ao utilizador).
 F  delete dados;
Como é uma matriz dinâmica (construída com o operador new[]), é necessário usar o operador delete[] para a destruir.
 F  delete dados[];
É uma boa tentativa, mas a versão correcta sintacticamente é delete[] dados.
[2 valores]
Questão 1.2
Sendo a função main()
VectorVerificado* vv = new VectorVerificado(3);
Vector* v = vv;
vv[0] = 1;
vv[1] = 2;
vv[2] = 3;
cout << v[3];
delete v;
qual o resultado produzido pelo programa quando executado (apenas uma das respostas está correcta)?

 F Com sorte o programa aborta e com azar escreve lixo no ecrã.
 V O programa aborta por uma das asserções falhar.
 F O programa escreve 3 no ecrã.

Como a função operator [] é virtual, é usada ligação dinâmica, ou seja, a versão da função executada é da classe VectorVerificado, cuja asserção falhará.
[0,5 valores]
Questão 1.3
Quais das seguintes sequências de instruções estão correctas dentro da função main()?

 F  Vector* v; v = new Vector(4);

A classe Vector é abstracta (tem duas funções puramente virtuais), pelo que não pode ser instanciada.
 F  Vector* v; v = new VectorVerificado(3); cout << v->acede(1);
As duas primeiras instruções estão correctas.  A terceira não está, pois tenta invocar uma função membro protegida.  Aos membros protegidos de uma classe só podem aceder os membros da própria classe ou de classes derivadas.
[1 valor]
Questão 1.4
Seja a seguinte classe:
class A {
  public:
    A(int v) : valor(v) {}
    friend ostream& operator << (ostream&, A const&);
  private:
    int valor = 1;
};

inline ostream& operator << (ostream& saída, A const& a) {
    return saída << a.valor;
}

Suponha que a classe Vector começa com:
class Vector {
  public:
    typedef A Valor;
Que sucede quando se tenta compilar e executar o seguinte programa?
int main() {
    VectorVerificado v(3);
    cout << v[0] << endl;
}
 F    Surge no ecrã 1.
 F    O programa não pode ser compilado com sucesso, pois a linha int valor = 1; está errada, embora não haja mais erros no programa.
 V    O programa não pode ser compilado com sucesso, pois não só a linha int valor = 1; e dados[i] = Valor(); está errada, como falta um construtor por omissão na classe A.
O primeiro erro deve-se à obrigação de a inicialização das variáveis membro de instância ter de ser feita forçosamente nos construtores da classe.  O segundo deve-se a que a classe A não tem construtor por omissão, necessário não só para construir cada um dos seus elementos, como porque o construtor por omissão é invocado explicitamente do lado direito da atribuição controlada pelo ciclo no corpo do construtor.
[1,5 valores]
Questão 2
Questão 2.1
Defina uma classe Pessoa que represente pessoas e respectivas relações familiares biológicas.  Para cada pessoa deve ser possível saber o nome, o sexo, os pais (pai e mãe) e os filhos.  A classe Pessoa deve usar ponteiros para a mesma classe (Pessoa) para guardar os familiares mais chegados (pai, mãe e filhos).  Para representar os filhos use listas de ponteiros para Pessoa.  Pode optar por ListaPonteiroPessoa, Lista<Pessoa*> ou por list<Pessoa*> (escolha a versão que conhecer melhor).  Ignore problemas relativos à ordem de declaração das classes no caso de usar ListaPonteiroPessoa.

A classe deve ser preparada para poder ser derivada sem problemas.

A classe deve fornecer funções de inspecção para o nome, sexo, pai, mãe e filhos da pessoa.

Deve também fornecer um procedimento membro void mostra() const que escreve no ecrã o nome e sexo da pessoa.

Finalmente, deve fornecer as seguintes funções de verificação de grau de parentesco:

bool éDescendenteDe(Pessoa const* ascendente) const;
bool éAscendenteDe(Pessoa const* descendente) const;
bool éParenteDe(Pessoa const* pessoa) const;
Deve possuir um construtor que serve não apenas para inicializar o nome e sexo de uma nova pessoa, mas também para indicar quem são os seus pais (afinal, construir uma pessoa é concebê-la).  Os parâmetros usados para indicar os pais da nova pessoa devem ter valor por omissão 0.  A utilização de 0 no valor dos ponteiros para um qualquer dos pais significa que ele não é conhecido ou relevante para o problema em causa.

Assuma definido o tipo enumerado Sexo:

enum Sexo {masculino, feminino};
Tenha o cuidado de indicar claramente os métodos que não alteram a instância implícita.

Nesta alínea não é necessário definir nenhum dos métodos (funções ou procedimentos membro) da classe.

[1 valor]

Questão 2.2
Defina o construtor da classe Pessoa.

O construtor serve não apenas para inicializar o nome e sexo de uma nova pessoa, mas também para indicar quem são os seus pais.  Os parâmetros usados para indicar os pais da nova pessoa devem ter valor por omissão 0.  A utilização de 0 no valor dos ponteiros para um qualquer dos pais significa que ele não é conhecido ou relevante para o problema em causa.  Por cada pai especificado (não 0), deve ser verificado o respectivo sexo.  O programa aborta se os sexos estiverem errados.

É muito importante que o construtor da nova pessoa registe essa mesma pessoa como filha de cada um dos pais especificados!

[1 valor]

Questão 2.3
Defina a função bool éDescendenteDe(Pessoa const* ascendente) const.  Note que uma pessoa é descendente de outra se essa outra pessoa for algum dos seus pais, ou se algum dos seus pais for descendente dessa outra pessoa (use recursividade para resolver o problema!).

Defina também a função bool éAscendenteDe(Pessoa const* descendente) const.  Recorde-se que A é ascendente de B se B for descendente de A.  Recorra a essa informação para escrever esta função à custa de uma simples chamada da função anterior.

Tenha cuidado porque as pessoas em causa podem não ter algum (ou nenhum) dos pais especificados.  Nesse caso considere que a pessoa foi concebida por partenogénese ou "geração espontânea" :-)

[2 valores]

Questão 2.4
Seja a classe Coisa a baixo usada para representar o conceito de coisa...
class Coisa {
public:
    Coisa(std::string const& nome)
        : nome_(nome) {
    }
    virtual ~Coisa() {}
    std::string const& nome() const;
        return nome_;
    }
    virtual void mostra() const {
        std::cout << nome() << std::endl;
    }
private:
    std::string nome_;
};
Defina totalmente uma classe Prenda para representar prendas.  Uma prenda é uma coisa.  Além do nome, as prendas têm um valor pecuniário e um valor simbólico (em unidade indefinidas, mas representadas por double).  Além disso, as prendas têm associada uma pessoa que as ofereceu (ponteiro para a classe Pessoa).  A classe, para além do construtor (que inicializa o nome, dador e valores de uma nova prenda), deve possuir inspectores para os dois valores e um procedimento mostra() que, para além de mostrar o nome da prenda, indique os seus dois valores da prenda e o nome da pessoa que a deu.

Tenha o cuidado de indicar claramente os métodos que não alteram a instância implícita.

[1 valor]

Questão 2.5
Defina as classes Festa, Participante, Anfitrião e Convidado.

A classe  Festa representa um jantar-festa.  Cada festa tem um conjunto de participantes, que podem ser divididos em anfitriões e convidados.  Cada festa tem também um nome (e.g., "Anos do Manel"), um número de convidados objectivo (e.g., 20) e um custo (em unidade indefinidas, mas representadas por double).  Quando uma festa é construída, ainda não tem quaisquer anfitriões ou convidados.

Deve ser possível realizar as seguintes operações com uma festa:

  1. Saber o nome da festa.
  2. Saber se já chegaram todos os convidados.
  3. Saber se os anfitriões na festa estão já todos sentados.
  4. Saber o valor da festa (i.e., a soma dos valores pecuniários das prendas dos convidados subtraída do custo da festa).
  5. Introduzir um novo anfitrião (ponteiro para Anfitrião) na festa.
  6. Receber um convidado (ponteiro para Convidado) na festa.  Um convidado só é recebido se o número de convidados na festa for inferior ao objectivo e se trouxer prenda.
  7. Mandar sentar todos os participantes na festa.
Ao terminar a festa (ou seja, ao destruir uma variável da classe Festa) os presentes entregues pelos convidados devem ser distribuídos pelos anfitriões.

A classe Festa deve guardar as listas de:

  1. Participantes.
  2. Anfitriões (também estão na lista de participantes).
  3. Convidados (também estão na lista de participantes).
  4. Prendas trazidas e entregues pelos convidados, que serão depois distribuídas pelos anfitriões.
Use ListaPonteiroXXX, Lista<XXX*> ou por list<XXX*> onde XXX é a classe dos objectos cujos ponteiros quer guardar na lista (escolha a versão que conhecer melhor).

A classe abstracta Participante representa um participante na festa.   Um participante é uma pessoa.  Além disso, cada participante deve saber a festa em que participa (ponteiro para a festa) e se está ou não sentado à mesa.  Deve ser possível realizar as seguintes operações com um participante:

  1. Saber a festa em que participa (ponteiro para a festa).
  2. Saber se está já sentado à mesa.
  3. Ordenar-lhe que se sente.  Esta operação tem de ser concretizada em classes derivadas.
Um anfitrião é um tipo específico de participante na festa.  Os anfitriões, para além daquilo que qualquer participante guarda, guardam a lista dos presentes que lhe forem atribuídos no final da festa.  Quando se ordena a um anfitrião que se sente, este só o faz se todos os convidados da festa já tiverem chegado.  Esta classe deve ter um procedimento para guardar uma prenda (ponteiro para) passada como argumento.

Quando se constrói um anfitrião, este introduz-se imediatamente (e automaticamente) na festa, invocando no seu construtor o método da festa que introduz um anfitrião.

Um convidado é um tipo específico de participante na festa.  Os convidados, para além daquilo que qualquer participante guarda, guardam um ponteiro para uma prenda a entregar na festa.  Essa prenda é construída (comprada...) quando o convidado é construído.  Quando se ordena a um convidado que se sente, este só o faz se todos os anfitriãos da festa estiverem já sentados.  Deve ser possível realizar as seguintes operações adicionais com um convidado:

  1. Saber se ainda tem prenda para oferecer.
  2. Ordenar-lhe que entregue a prenda a oferecer (a função devolve a prenda e coloca o ponteiro com 0 para assinalar que já não tem prenda para oferecer).
Quando se constrói um convidado, este entra imediatamente (e automaticamente) na festa em que vai participar, invocando no seu construtor o método da festa que recebe um convidado.

Tenha o cuidado de indicar claramente os métodos que não alteram a instância implícita em todas as classes.

Nesta alínea não é necessário definir nenhum dos métodos (funções ou procedimentos membro) da classe.

[3 valores]

Questão 2.6
Defina as todas as operações que ordenam aos participantes que se sentem (void senta()).  Estas operações são declaradas nas três classes: Participante, Anfitrião e Convidado.  Não precisa de definir a operação para a classe Participante se ela tiver sido definida como puramente virtual (ou abstracta), mas pode defini-la mesmo nesse caso se isso lhe for útil (e.g., para lhe permitir alterar uma varáviel membro privada de uma classe base, por exemplo...).

[1,5 valores]

Apresenta-se a solução completa com todos os métodos definidos e dividida em módulos físicos.  A negrito a parte correspondente às respostas ao enunciado:

coisa.H

#ifndef COISA_H
#define COISA_H

#include <string>
#include <iostream>

// Classe Coisa:
class Coisa {
public:
    Coisa(std::string const& nome);
    virtual ~Coisa() {}
    std::string const& nome() const;
    virtual void mostra() const;

private:
    std::string nome_;
};

inline Coisa::Coisa(std::string const& nome)
    : nome_(nome) {
}

inline std::string const& Coisa::nome() const {
    return nome_;
}

inline void Coisa::mostra() const {
    std::cout << nome() << std::endl;
}

#endif // COISA_H

prenda.H
#ifndef PRENDA_H
#define PRENDA_H

#include <string>
#include <iostream>

#include "coisa.H"

// Classe Prenda:
class Prenda : public Coisa {
public:
    Prenda(std::string const& nome, Pessoa const* dador,
           double valor_sentimental, double valor_pecuniario);
    double valorSentimental() const;
    double valorPecuniario() const;
    void mostra() const;

private:
    Pessoa const* dador;
    double valor_sentimental;
    double valor_pecuniario;
};

inline Prenda::Prenda(std::string const& nome, Pessoa const* dador,
                      double valor_sentimental,
                      double valor_pecuniario)
    : Coisa(nome), dador(dador),
      valor_sentimental(valor_sentimental),
      valor_pecuniario(valor_pecuniario) {
    assert(valor_sentimental >= 0 && valor_pecuniario >= 0);
}

inline double Prenda::valorSentimental() const {
    return valor_sentimental;
}

inline double Prenda::valorPecuniario() const {
    return valor_pecuniario;
}

inline void Prenda::mostra() const {
    Coisa::mostra();
    std::cout << "Valor sentimental: " << valorSentimental() << std::endl
              << "Valor pecuniario: " << valorPecuniario() << std::endl
              << "Oferecida por: " << dador->nome() << std::endl;
}

#endif // PRENDA

pessoa.H
#ifndef PESSOA_H
#define PESSOA_H

#include <iostream>
#include <list>
#include <string>

enum Sexo {feminino, masculino};

class Pessoa {
public:
    Pessoa(std::string const& nome, Sexo sexo,
           Pessoa* pai = 0, Pessoa* mae = 0);
    virtual ~Pessoa() {}

    std::string const& nome() const;
    Sexo sexo() const;
    Pessoa const* pai() const;
    Pessoa const* mae() const;
    std::list<Pessoa const*> const& filhos() const;

    virtual void mostra() const;

    void mostraDescendencia() const;
    void mostraAscendencia() const;

    bool eDescendenteDe(Pessoa const* ascendente) const;
    bool eAscendenteDe(Pessoa const* descendente) const;
    bool eParenteDe(Pessoa const* outra) const;

private:
    std::string nome_;
    Sexo sexo_;
    Pessoa const* const pai_;
    Pessoa const* const mae_;
    std::list<Pessoa const*> filhos_;
};

inline Pessoa::Pessoa(std::string const& nome, Sexo sexo,
                      Pessoa* pai, Pessoa* mae)
    : nome_(nome), sexo_(sexo), pai_(pai), mae_(mae) {
    assert((pai == 0 || pai_->sexo() == masculino) &&
           (mae == 0 || mae_->sexo() == feminino));
    if(pai != 0)
        pai->filhos_.push_back(this);
    if(mae != 0)
        mae->filhos_.push_back(this);
}

inline std::string const& Pessoa::nome() const {
    return nome_;
}

inline Sexo Pessoa::sexo() const {
    return sexo_;
}

inline Pessoa const* Pessoa::pai() const {
    return pai_;
}

inline Pessoa const* Pessoa::mae() const {
    return mae_;
}

inline std::list<Pessoa const*> const& Pessoa::filhos() const {
    return filhos_;
}

inline void Pessoa::mostra() const {
    cout << nome_ << " (" << (sexo_ == masculino? "homem" : "mulher")
         << ')' << endl;
}

#endif // PESSOA_H

pessoa.C
#include "pessoa.H"

using namespace std;

void Pessoa::mostraDescendencia() const {
    for(list<Pessoa*>::const_iterator i = filhos_.begin();
        i != filhos_.end(); ++i) {
        (*i)->mostra();
        (*i)->mostraDescendencia();
    }
}

void Pessoa::mostraAscendencia() const {
    if(pai_ != 0) {
        pai_->mostra();
        pai_->mostraAscendencia();
    }
    if(mae_ != 0) {
        mae_->mostra();
        mae_->mostraAscendencia();
    }
}

bool Pessoa::eDescendenteDe(Pessoa const* ascendente) const {
    if(pai_ != 0 &&
       (pai_ == ascendente || pai_->eDescendenteDe(ascendente)))
        return true;
    if(mae_ != 0 &&
       (mae_ == ascendente || mae_->eDescendenteDe(ascendente)))
        return true;
    return false;
}

bool Pessoa::eAscendenteDe(Pessoa const* descendente) const {
    return descendente->eDescenteDe(this)
}

bool Pessoa::eParenteDe(Pessoa const* outra) const {
    if(this == outra || this->eAscendenteDe(outra))
        return true;
    if(pai_ != 0 && pai_->eParenteDe(outra))
        return true;
    if(mae_ != 0 && mae_->eParenteDe(outra))
        return true;
    return false;
}

festa.H
#ifndef FESTA_H
#define FESTA_H

#include <string>
#include <list>

#include "pessoa.H"
#include "prenda.H"

// Classe Festa:
class Festa {
public:
    // A solução com classes embutidas não era importante.
    class Participante;
    class Anfitriao;
    class Convidado;

    Festa(std::string const& nome, int convidados, double custo);
    ~Festa();

    std::string nome() const;
    bool convidadosChegaram() const;
    bool anfitrioesSentados() const;
    double valor() const;
    void mostra() const;

    void novoAnfitriao(Anfitriao* anfitriao);

    void recebe(Convidado* convidado);

    void senta();

private:
    std::list<Participante*> participantes;
    std::list<Anfitriao*> anfitrioes;
    std::list<Convidado*> convidados;
    std::list<Prenda*> prendas;
    std::string nome_;
    int numero_de_convidados_a_chegar;
    double custo;
};

class Festa::Participante : public Pessoa {
public:
    Participante(Festa* festa, std::string const& nome, Sexo sexo,
                 Pessoa* pai = 0, Pessoa* mae = 0);
    virtual ~Participante() {}

    Festa const* festa() const;
    Festa* festa();
    bool sentado() const;

    virtual void senta() = 0;

private:
    Festa* festa_;
    bool sentado_;
};

class Festa::Anfitriao : public Festa::Participante {
public:
    Anfitriao(Festa* festa, std::string const& nome, Sexo sexo,
              Pessoa* pai = 0, Pessoa* mae = 0);
    ~Anfitriao();

    virtual void mostra() const;

    virtual void senta();
    void guarda(Prenda* prenda);

private:
    std::list<Prenda*> prendas;
};

class Festa::Convidado : public Festa::Participante {
public:
    Convidado(Festa* festa, std::string const& nome, Sexo sexo,
              Pessoa* pai = 0, Pessoa* mae = 0);
    ~Convidado();

    virtual void mostra() const;
    bool temPrenda() const;

    Prenda* oferece();
    virtual void senta();

private:
    Prenda* prenda;
};

inline Festa::Festa(std::string const& nome, int convidados, double custo)
    : nome_(nome), numero_de_convidados_a_chegar(convidados), custo(custo) {
    assert(convidados >= 0);
}

inline std::string Festa::nome() const {
    return nome_;
}

inline bool Festa::convidadosChegaram() const {
    return numero_de_convidados_a_chegar == 0;
}

inline void Festa::novoAnfitriao(Anfitriao* anfitriao) {
    assert(anfitriao != 0);
    anfitrioes.push_back(anfitriao);
    participantes.push_back(anfitriao);
}

inline Festa::Participante::Participante(Festa* festa,
                                         std::string const& nome, Sexo sexo,
                                         Pessoa* pai, Pessoa* mae)
    : Pessoa(nome, sexo, pai, mae), festa_(festa), sentado_(false) {
    assert(festa != 0);
}

inline Festa const* Festa::Participante::festa() const {
    return festa_;
}

inline Festa* Festa::Participante::festa() {
    return festa_;
}

inline bool Festa::Participante::sentado() const {
    return sentado_;
}

inline void Festa::Participante::senta() {
    sentado_ = true;
}

inline Festa::Anfitriao::Anfitriao(Festa* festa,
                                   std::string const& nome, Sexo sexo,
                                   Pessoa* pai, Pessoa* mae)
    : Participante(festa, nome, sexo, pai, mae) {
    festa->novoAnfitriao(this);
}

inline void Festa::Anfitriao::guarda(Prenda* prenda) {
    assert(prenda != 0);
    prendas.push_back(prenda);
}

inline Festa::Convidado::Convidado(Festa* festa,
                                   std::string const& nome, Sexo sexo,
                                   Pessoa* pai, Pessoa* mae)
    : Participante(festa, nome, sexo, pai, mae), prenda(prenda) {
    prenda = new Prenda("Treta", this, rand() % 100, rand() % 100);
    festa->recebe(this);
}

inline Festa::Convidado::~Convidado() {
    delete prenda;
}

inline bool Festa::Convidado::temPrenda() const {
    return prenda != 0;
}

inline Prenda* Festa::Convidado::oferece() {
    Prenda* p = prenda;
    prenda = 0;
    return p;
}

#endif // FESTA

festa.C
#include "festa.H"

#include <iostream>

using namespace std;

Festa::~Festa() {
    if(anfitrioes.empty())
        for(list<Prenda*>::iterator i = prendas.begin();
            i != prendas.end(); ++i)
            delete *i;
    else {
        list<Anfitriao*>::iterator ia = anfitrioes.begin();
        for(list<Prenda*>::iterator ip = prendas.begin();
            ip != prendas.end(); ++ip) {
            (*ia)->guarda(*ip);
            if(++ia == anfitrioes.end())
                ia = anfitrioes.begin();
        }
    }
}

bool Festa::anfitrioesSentados() const {
    for(list<Anfitriao*>::const_iterator i = anfitrioes.begin();
        i != anfitrioes.end(); ++i)
        if(!(*i)->sentado())
            return false;
    return true;
}

double Festa::valor() const {
    double proveito = 0.0;
    for(list<Prenda*>::const_iterator i = prendas.begin();
        i != prendas.end(); ++i)
        proveito += (*i)->valorPecuniario();
    return proveito - custo;
}

void Festa::recebe(Convidado* convidado) {
    assert(convidado != 0);
    if(numero_de_convidados_a_chegar != 0 && convidado->temPrenda()) {
        prendas.push_back(convidado->oferece());
        convidados.push_back(convidado);
        participantes.push_back(convidado);
        --numero_de_convidados_a_chegar;
    }
}

void Festa::senta() {
    if(convidadosChegaram()) {
        for(list<Anfitriao*>::iterator i = anfitrioes.begin();
            i != anfitrioes.end(); ++i)
            (*i)->senta();
        for(list<Convidado*>::iterator i = convidados.begin();
            i != convidados.end(); ++i)
            (*i)->senta();
    }
}

void Festa::mostra() const {
    cout << "Festa: " << nome() << endl
         << "Anfitriões: " << endl;
    for(list<Anfitriao*>::const_iterator i = anfitrioes.begin();
        i != anfitrioes.end(); ++i)
        (*i)->mostra();
    cout << "Convidados: " << endl;
    for(list<Convidado*>::const_iterator i = convidados.begin();
        i != convidados.end(); ++i)
        (*i)->mostra();
    if(numero_de_convidados_a_chegar != 0)
        cout << "Ainda faltam " << numero_de_convidados_a_chegar
             << " convidados." << endl;
    cout << "Prendas: " << endl;
    for(list<Prenda*>::const_iterator i = prendas.begin();
        i != prendas.end(); ++i)
        (*i)->mostra();
    cout << "Valor: " << valor() << endl;
}

Festa::Anfitriao::~Anfitriao() {
    for(list<Prenda*>::iterator i = prendas.begin();
        i != prendas.end(); ++i)
        delete *i;
}

void Festa::Anfitriao::senta() {
    if(festa()->convidadosChegaram())
        Participante::senta();
    else
        cout << '[' << nome()
             << "] não me posso sentar porque faltam convidados."
             << endl;
}

void Festa::Anfitriao::mostra() const {
    Pessoa::mostra();
    cout << "Prendas:" << endl;
    for(list<Prenda*>::const_iterator i = prendas.begin();
        i != prendas.end(); ++i)
        (*i)->mostra();
}

void Festa::Convidado::senta() {
    if(festa()->anfitrioesSentados())
        Participante::senta();
    else
        cout << '[' << nome()
             << "] não me posso sentar porque os "
             << "anfitriões estão em pé."
             << endl;
}

void Festa::Convidado::mostra() const {
    Pessoa::mostra();
    if(prenda != 0) {
        cout << "Prenda a dar:" << endl;
        prenda->mostra();
    }
}

teste_festa.C
#include "festa.H"

using namespace std;

int main()
{
    list<Festa::Anfitriao*> anfitrioes;
    list<Festa::Convidado*> convidados;

    Festa* festa = new Festa("Anos do Zé", 5, 2000);

    convidados.push_back(new Festa::Convidado(festa, "Ana", feminino));
    convidados.push_back(new Festa::Convidado(festa, "Américo", masculino));
    anfitrioes.push_back(new Festa::Anfitriao(festa, "Zé", masculino));
    anfitrioes.push_back(new Festa::Anfitriao(festa, "Maria", feminino));
    festa->senta();
    anfitrioes.front()->senta();
    convidados.front()->senta();
    convidados.push_back(new Festa::Convidado(festa, "Anabela", feminino));
    convidados.push_back(new Festa::Convidado(festa, "António", masculino));
    convidados.push_back(new Festa::Convidado(festa, "Amaral", masculino));

    festa->mostra();
    festa->senta();

    if(festa->valor() > 0)
        cout << "Festa valeu a pena :-)" << endl;
    else
        cout << "Festa não valeu a pena :-(" << endl;

    delete festa;

    for(list<Festa::Anfitriao*>::const_iterator i = anfitrioes.begin();
        i != anfitrioes.end(); ++i)
        (*i)->mostra();

    for(list<Festa::Anfitriao*>::const_iterator i = anfitrioes.begin();
        i != anfitrioes.end(); ++i)
        delete *i;

    for(list<Festa::Convidado*>::const_iterator i = convidados.begin();
        i != convidados.end(); ++i)
        delete *i;
}

Questão 3
Considere a seguinte estrutura
struct Elo {
    typedef int Item;

    Item item;
    Elo* seguinte;
    Elo(Item const& item, Elo* seguinte = 0) : item(item), seguinte(seguinte) {
    }
};

usada para representar os elos de uma cadeia simplesmente ligada com a particularidade de ser circular, i.e., o elo seguinte do último elo da cadeia é o primeiro elo da cadeia.  A cadeia não tem guardas.

Defina um procedimento void inverte(Elo*& último) que inverta a ordem dos elos de uma cadeia simplesmente ligada, circular e sem guardas cujo último elo é apontado pelo ponteiro último.  Note que quando a cadeia está vazia o ponteiro para o último elemento tem o valor 0.  Note também que em geral o último elo da cadeia passa a ser o que antes era o primeiro.

Faça diagramas da cadeia em várias situações!  Só assim resolverá com sucesso esta alínea!

Apresenta-se uma solução completa incluindo programa de teste.  A negrito as partes pedidas no exercício:

#include <iostream>

using namespace std;

struct Elo {
    typedef int Item;

    Item item;
    Elo* seguinte;
    Elo(Item const& item, Elo* seguinte = 0) : item(item), seguinte(seguinte) {
    }
};

void poeInicio(Elo*& ultimo, Elo::Item const& item) {
    if(ultimo == 0) {
        ultimo = new Elo(item);
        ultimo->seguinte = ultimo;
    } else
        ultimo->seguinte = new Elo(item, ultimo->seguinte);
}

void inverte(Elo*& ultimo) {
    if(ultimo != 0) {
        Elo* anterior = ultimo;
        Elo* e = ultimo->seguinte;
        while(e != ultimo) {
            Elo* seguinte = e->seguinte;
            e->seguinte = anterior;
            anterior = e;
            e = seguinte;
        }
        ultimo = ultimo->seguinte;
        e->seguinte = anterior;
    }
}

void mostra(Elo const* ultimo) {
    cout << '(';
    if(ultimo != 0) {
        for(Elo const* e = ultimo->seguinte; e != ultimo; e = e->seguinte)
            cout << e->item << ", ";
        cout << ultimo->item;
    }
    cout << ')' << endl;
}

int main() {
    Elo* ultimo = 0;
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 1);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 2);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 3);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 4);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 5);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);

    poeInicio(ultimo, 6);
    mostra(ultimo);
    inverte(ultimo);
    mostra(ultimo);
    inverte(ultimo);
}

[2 valores]
Questão 4
Complete o seguinte código que define uma classe representando vectores em R3.  Recorde que, em caso de erro, um canal interpretado como um booleano (e.g., em if(cin)) tem valor falso.  Recorde também que todas as leituras de um canal após um erro falharão: para que possam ter sucesso é necessário limpar a condição de erro do canal invocando o método clear() (e.g., cin.clear();).  Tenha atenção aos comentários abaixo!
#include <iostream>

using namespace std;

// Procedimento que descarta todos os caracteres lidos do teclado até ao primeiro
// fim-de-linha encontrado:
void ignora() {
    char c;
    while(cin.get(c) && c != '\n')
        ;
}

class Vector {
public:
    Vector() : x_(0.0), y_(0.0), z_(0.0) {}
    Vector(double x, double y, double z) : x_(x), y_(y), z_(z) {}

    double x() const { return x_; }
    double y() const { return y_; }
    double z() const { return z_; }

    Vector& operator += (Vector const& v) {
        x_ += v.x_; y_ += v.y_; z_ += v.z_;
        return *this;
    }

    class ErroAoCarregar {};

    // Carrega as coordenadas do vector a partir do canal entrada.  Em caso de
    // erro lança a excepção ErroAoCarregar e mantém o valor original do vector
    // intacto!
    void carrega(istream& entrada);

    // Lê do teclado as coordenadas.  Se tudo correr bem devolve true.  Se a
    // leitura falhar devolve false e mantém o valor original do vector intacto!
    bool le();

    // Lê do teclado as coordenadas, depois de as pedir.  Em caso de erro
    // avisa o utilizador e pede para ele introduzir de novo ignorando toda a
    // linha.  Só termina depois de lidas com sucesso as três coordenadas.
    void leInteractivo();

private:
    double x_, y_, z_;
};

Vector operator + (Vector a, Vector const& b) {
    return a += b;
}

void Vector::carrega(istream& entrada) {
    double x, y;
    if(!(entrada >> x >> y >> z_))
        throw ErroAoCarregar();
    x_ = x;
    y_ = y;
}

bool Vector::le() {
    double x, y;
    if(!(cin >> x >> y >> z_))
        return false;
    x_ = x;
    y_ = y;
    return true;
}

void Vector::leInteractivo() {
    while(true) {
        cout << "Introduza três coordenadas: ";
        if(cin >> x_ >> y_ >> z_)
            return;
        cout << "Coordenadas inválidas!" << endl;
        // Limpar condição de erro:
        cin.clear();
        // Ignorar caracteres até ao fim da linha:
        ignora();
    }
}

[2 valores]
Questão 5
Diga o que distingue um método normal de um método virtual em termos semânticos (informais).  E o que é um método abstracto ou puramente virtual?
O que distingue um método normal de um nétodo virtual é que para um método normal não podem ser fornecidas especializações enquanto para um método virtual podem.  I.e., se numa classe derivada se redefinir o método, no primeiro caso oculta-se simplesmente o método de igual assinatura da classe base e no segundo sobrepõe-se-lhe uma especialização.  O método normal será invocado usando ligação estática enquanto o método virtual será invocado usando ligação dinâmica.  Ou seja, o método a invocar através de um ponteiro ou referência será no primeiro caso determinado pelo tipo do ponteiro ou referência e no segundo pelo tipo do objecto apontado ou referenciado.

Um método abstracto é um método virtual que não necessita ser definido.  Tem como consequência tornar a classe abstracta (i.e., que não se pode instanciar).  Outra consequência é obrigar as classe derivadas que se queiram concretas a fornecerem uma definição para esse método.

[1 valor]