Recibo do exame de 2ª época de Programação Orientada para Objectos (IGE e ETI), 2º semestre de 1999/2000, ISCTE:
Nome do aluno: _______________________________________________
Número do aluno:  ______________________
Assinatura do docente: ________________________________________

Identificação

Nome do aluno: _______________________________________________

Número do aluno:  ______________________

Turma: ____________

Exame de 2 ª época

Programação Orientada para Objectos

IGE e ETI

2º semestre de 1999/2000

ISCTE


Notas:

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?

__ free(dados);
__ delete[tamanho] dados;
__ delete dados;
__ 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)?

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

[0,5 valores]
 
 
 
 
 

Questão 1.3
Quais das seguintes sequências de instruções estão correctas?

__ Vector* v; v = new Vector(4);
__ Vector* v; v = new VectorVerificado(3); cout << v->acede(1);

[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;
}
__ Surge no ecrã 1.
__ 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.
__ 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.

[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]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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!

[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) {
    // Complete aqui:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

}

bool Vector::le() {
    // Complete aqui:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

}

void Vector::leInteractivo() {
    // Complete aqui:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

}

[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?

[1 valor]