Resolução da Frequência
Programação Orientada para Objectos
IGE e ETI
2º semestre de 2000/2001
ISCTE
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.
Em geral 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.
Considere as seguintes classes:
class B {
public:
B();
virtual ~B();
virtual void m1() = 0;
};
class DdeB : public B {
public:
DdeB();
virtual void m1();
virtual void m2(int p);
};
class DdeD: public DdeB {
public:
DdeD();
virtual void m2(int p);
};
1.1 Quais das seguintes instruções estão correctas?
V B* b = new
DdeB(); b->m1();
É possível criar uma nova instância de
DdeBporque (a) a classe tem uma construtor por omissão público e (b) a classe é concreta, pois fornece um método para a operação abstractavoid m1()herdada da classeB. Como a herança é pública, pode-se atribuir o endereço de um objecto da classe derivada a um ponteiro para a classe base. Finalmente, a operaçãovoid m1()é pública, pelo que pode ser invocada fora da classe.
F B* b = new B(); b->m1();
Não é possível criar uma nova instância de
Bporque essa classe é abstracta. Isto acontece porque declara uma operação abstracta:void m1().
F B* b = new DdeD(); b->m2(3);
[cotação: 1,5]Semelhante ao primeiro caso. É possível criar uma nova instância de
DdeDporque (a) a classe tem uma construtor por omissão público e (b) a classe é concreta, pois deriva de uma classe concreta,DdeB, e não declara qualquer operação abstracta. Como ambas as heranças são públicas, pode-se atribuir o endereço de um objecto da classe (duplamente) derivada a um ponteiro para a classe base. Finalmente, como não existe nenhuma operaçãovoid m2(int)declarada na classeB, é inválida a sua invocação através de uma ponteiro para essa classe (se o ponteiro fosse para a classeDdeBnão haveria qualquer problema).
1.2 Quais das seguintes definições estão correctas?
F void DdeD() { cout << "DdeD::DdeD()" <<
endl; }
Presume-se que esta definição é feita fora da classe
DdeD. O seu nome teria de ser precedido deDdeD, para deixar claro ser um método (construtor) da classeDdeD. Note-se que não se pode definir no mesmo âmbito uma rotina e uma classe com o mesmo nome! Finalmente, se a intenção for de facto definir um construtor para a classe, a palavra chavevoidestá a mais, pois por definição os construtores não devolvem nada e por isso não têm tipo de devolução (ou, quando muito, pode-se dizer que devolvem um objecto da classe em causa, pelo que o tipo de devolução seria redundante face ao nome do construtor...).
F DdeD() : DdeB(), B() {}
Na lista de inicializadores só se podem invocar directamente os construtores das classes de que derivam directamente. A razão é evidente: será esses construtores a invocar os construtores das classes mais acima na hierarquia.
Note-se, no entanto, que existe o conceito de classe base virtual em C++, que permite resolver alguns problemas que podem surgir com heranças múltiplas, e que obrigam à invocação directa do seu construtor em todas as listas de inicializadores de todas as classes derivadas (directas ou indirectas). Mas mesmo se fosse esse o caso a ordem das inicializações estaria incorrecta.
V DdeD() : DdeB() {}
O construtor por omissão invoca na sua lista de inicializadores o construtor por omissão da classe base. Está correcto, embora seja redundante. Pode-se eliminar a lista de inicializadores que o C++ invoca o construtor por omissão da classe base. Pode-se finalmente eliminar completamente o construtor por omissão, pois o C++ fornece um automaticamente que faz exactamente o mesmo.
[cotação: 1,5]
1.3 Dado o seguinte código:
double a = 3.0;
int* b = 0;
float c = 3.14;
int const* d = new int[4];
int* const e = new int[5];
Quais das seguintes instruções estão correctas?
F int* resultado = a < 3.5 ? a : 3.5;
Não se pode inicializar um ponteiro com um inteiro: os tipos são incompatíveis.
F b = &c;
Não se pode inicializar um ponteiro para um
intcom o endereço de umfloat: os tipos são incompatíveis.
V ++d;
O ponteiro não é constante, pelo que se pode alterar. O que é constante é a variável apontada pelo ponteiro.
F ++e;
Caso oposto. O ponteiro é constante, embora a variável apontada não o seja.
[cotação: 2]
Questão 2
Recorde-se que deve ler atentamente todo o enunciado antes de resolver as questões!
Pretende-se implementar, em C++, uma aplicação para apoio à divulgação de notícias. Esta aplicação permite aos seus utilizadores consultar e registar notícias. São mostradas todas as notícias que se encontram na lista gerida por esta aplicação sempre que um utilizador assim o peça.
A aplicação responsabiliza-se pela actualização das notícias disponíveis para consulta, analisando diariamente as condições necessárias à disponibilização de notícias: as notícias que não verifiquem as condições são removidas da lista gerida por esta aplicação.
A definição da classe GestorDeNotícias, seguidamente
apresentada, é uma simplificação da classe que gere as notícias na referida
aplicação:
class GestorDeNotícias {Método invocado diariamente para actualizar as notícias:
public:
GestorDeNotícias();
~GestorDeNotícias();
//O parâmetro
void actualiza(Data const& data_actual);
void registaNotícia(Notícia* notícia);
void cancelaAcontecimento(int identificação);
//nomeé o nome do utilizador que respondeu ao anúncio:Método invocado quando o utilizador pede a lista de notícias:
void registaRespostaAAnúncio(string const& nome, Data const& data_da_resposta);
//
void mostra() const;
private:
list<Notícia*> noticias;
};
Cada notícia contém:
Existem dois tipos concretos de notícias: anúncios e divulgação de acontecimentos.
Os anúncios são notícias que requerem uma resposta. A disponibilização de um anúncio, que no máximo dura todo o intervalo de validade, cessa logo que algum utilizador lhe responde. Cada anúncio contém o nome do utilizador que lhe respondeu e a data em que tal aconteceu. Esta informação é enviada a outro sistema antes da remoção do anúncio (esta operação não é da responsabilidade desta aplicação).
A divulgação de acontecimentos é uma notícia que indica a realização de um evento (e.g., um seminário). Os acontecimentos são noticiados durante todo o intervalo de validade definido.
Os acontecimentos podem ser cancelados. Nesse caso os acontecimentos continuam a ser noticiados até expirar o prazo de validade do acontecimento alertando, no entanto, para o seu cancelamento.
Considere a existência de uma classe Data onde se encontram
todas operações de que necessite para a resolução destas perguntas desta
questão.
2.1 Defina as classes Notícia, Anúncio
e Acontecimento. Não é necessário nesta alínea definir quaisquer
métodos das classes.
Para responder ao enunciado tem de se usar uma solução semelhante à que se segue. Note-se no entanto que esta modelação de classes não é a mais elegante, visto que a classe base vê-se obrigada a declarar duas operações abstractas (
cancela()eregistaResposta()) que fazem sentido apenas numa das classes derivadas (a primeira na classeAcontecimentoe a segunda na classeAnúncio). Não avance nunca para este tipo de solução: é preferível repensar totalmente a organização das classes, nomeadamente na constituição da classeGestorDeNotícias.
class Notícia {Declaração de construtores e destrutores:
public:
//Declaração de operações inspectoras:
Notícia(int identificação, string const& título,
string const& descrição, string const& fonte,
Data const& data_de_inicio, Data const& data_de_fim);
virtual ~Notícia() {}
//Declaração de operações modificadoras:
int identificação() const;
string const& título() const;
string const& descrição() const;
string const& fonte() const;
Data const& dataDeInicio() const;
Data const& dataDeFim() const;
virtual bool disponível(Data const& data_actual) const = 0;
virtual void mostra() const;
//;
virtual void cancela(Data const& data_cancelamento) = 0;
virtual void registaResposta(string const& nome_do_respondente,
Data const& data_da_resposta) = 0;
private:
int identificação_;
string título_;
string descrição_;
string fonte_;
Data data_de_inicio;
Data data_de_fim;
}
class Acontecimento : public Notícia {
public:
//Construtores e destrutores:
Acontecimento(int identificação, string const& título,
string const& descrição, string const& fonte,
Data const& data_de_inicio, Data const& data_de_fim);
//Declaração de métodos inspectores:
virtual bool disponível(Data const& data_actual) const;
virtual mostra() const;
//Declaração de operações inspectoras específicas:
Data const& dataDeCancelamento() const;
bool cancelado() const;
//Declaração de métodos modificadores:
virtual void cancela(Data const& data_cancelamento);
virtual void registaResposta(string const& nome_do_respondente,
Data const& data_da_resposta) {}
private:
bool cancelado_;
Data data_de_cancelamento;
};
class Anúncio : public Notícia {
public:
//Construtores e destrutores:
Anúncio(int identificação, string const& título,
string const& descrição, string const& fonte,
Data const& data_de_inicio, Data const& data_de_fim);
//Declaração de métodos inspectores:
virtual bool disponível(Data const& data_actual) const;
virtual mostra() const;
//Declaração de operações inspectoras específicas:
Data const& dataDaResposta() const;
bool respondido() const;
string const& nomeDoRespondente() const;
//Declaração de métodos modificadores:
virtual void cancela(Data const& data_cancelamento);
virtual void registaResposta(string const& nome_do_respondente,
Data const& data_da_resposta) {}
private:
Data data_da_resposta_;
bool respondido_;
string nome_do_respondente;
};
[cotação: 3]
2.2 Assuma que a invocação do método void
Notícia::mostra() const faz aparecer informação no ecrã com o
seguinte aspecto:
Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06
Implemente o método void Acontecimento::mostra() const,
que mostra no ecrã informação relativa a um acontecimento. Caso o
acontecimento tenha sido cancelado, a informação enviada para o ecrã tem o
seguinte aspecto:
CANCELADO: 2001/07/04
Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06
Caso o acontecimento não tenha sido cancelado, a informação enviada para o ecrã tem o seguinte aspecto:
Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06
void Acontecimento::mostra() const {
if(cancelado())
cout << " CANCELADO: " << dataDeCancelamento() << endl;
Notícia::mostra();
}
[cotação: 2]
2.3 Implemente o destrutor da classe GestorDeNotícias.
Este método é responsável pela destruição de todas as variáveis dinâmicas
contidas nesta classe, tenham ou não sido criadas dentro dela (i.e., pelos seus
métodos).
GestorDeNotícias::~GestorDeNotícias() {
typedef list<Notícia*>::iterator iterador;
for(iterator i = noticias.begin(); i != noticias.end(); ++i)
delete *i;
}
[cotação: 2]
2.4 Implemente o método void GestorDeNotícias::actualiza(Data
const& data_actual). Este método é responsável por
actualizar as notícias disponíveis na aplicação. Lembre-se que esta
aplicação é responsável pela actualização das notícias disponíveis para
consulta, analisando diariamente as condições necessárias à
disponibilização de notícias: as notícias que não verifiquem as condições
são removidas da lista gerida por esta aplicação. Implemente todos os
métodos das classes Notícia, Anúncio e Acontecimento
que forem necessários.
void GestorDeNotícias::actualiza(Data const& data_actual) {
list<Notícia*>::iterator i =noticias.begin();
while(i != noticias.end())
if(not (*i)->disponível(data_actual))
noticias.erase(i++);
else
++i;
}
bool Anúncio::disponível(Data const& data_actual) const {
return not respondido() and data_inicio <= data_actual and data_actual <= data_fim;
}
bool Acontecimento::disponível(Data const& data_actual) const {
return data_inicio <= data_actual and data_actual <= data_fim;
}
[cotação: 3]
Questão 3
Implemente o método void ListaDouble::transfereDe(ListaDouble&
outra_lista), pertencente à classe ListaDouble, cuja
definição é fornecida em anexo. O método em questão transfere todos
os itens de outra_lista para o fim da lista implícita. A outra_lista
fica vazia. Não use as classes iteradoras na implementação deste
método: use manipulações de ponteiros. Use tão poucas manipulações
de ponteiros quanto possíveis.
A implementação da lista baseia-se no conceito de cadeia duplamente ligada com guardas.
inline void ListaDouble::transfereDe(ListaDouble& outra_lista)
{
assert(cumpreInvariante() and outra_lista.cumpreInvariante());
/*Se forem a mesma lista transferir é... não fazer nada!
Seoutra_listaestiver vazia também não é preciso fazer nada:*/Basta trocar ponteiros!
if(this != &outra_lista and not outra_lista.estaVazia()) {
//
elo_final->anterior->seguinte = outra_lista.elo_inicial->seguinte;
outra_lista.elo_inicial->seguinte->anterior = elo_final->anterior;
outra_lista.elo_final->anterior->seguinte = elo_final;
elo_final->anterior = outra_lista.elo_final->anterior;
outra_lista.elo_inicial->seguinte = outra_lista.elo_final;
outra_lista.elo_final->anterior = outra_lista.elo_inicial;
numero_de_itens += outra_lista.numero_de_itens;
outra_lista.numero_de_itens = 0;
}
assert(cumpreInvariante() and outra_lista.cumpreInvariante());
}
[cotação: 3]
Questão 4
Exemplifique sumariamente o lançamento e a captura de uma excepção em C++ e explique para que servem e em que circunstâncias devem ser usadas.
O mecanismo de excepções em C++ destina-se ao controlo e recuperação de circunstâncias excepcionais, normalmente erros associados a recursos externos ao programa (acesso a ficheiros, reserva de memória, etc.) mas também, se necessário, erros de programação. Uma excepção é lançada quando se verifica um erro. O lançamento de uma excepção pode ser forçado através de uma instrução
throw. Uma excepção é capturada e tratada no interior de um bloco de tentativa:
try {...
//instruções passíveis de originar uma excepção....
} catch(TipoDeExcepção& nome) {
//instruções de tratamento de uma excepção do tipoTipoDeExcepção.
}Caso uma excepção lançada não seja capturada por um
catchadequado, o programa aborta.Exemplo:
Suponha-se uma classe
Figuraque representa o conceito de figura como uma agregação de formas e as classesErroeErroAoCarregar, para representar excepções:
class Erro {
};...
class ErroAoCarregar : public Erro {
};
class Figura {
public:
Figura(istream& entrada);
void carrega(istream& entrada);
void troca(Figura& outra_figura);
...
Em caso de erro na extracção lança-se explicitamente uma excepção!
private:
list<Forma*> formas;
};
inline Figura::Figura(istream& entrada) {
/*
Note-se que as extracções (operador>>) normalmente não lançam excepções,
assinalando erro ao colocar o canal em estado de erro, que tem de ser
verificado explicitamente.*/...
int numero_de_formas;
if(not (entrada >> numero_de_formas))
throw ErroAoCarregar();
try {
while(numero_de_formas-- != 0) {
string tipo_de_forma;
if(not (entrada >> tipo_de_forma))
throw ErroAoCarregar();
if(tipo_de_forma == "Poligono")
formas.push_back(new Poligono(entrada));
else if(tipo_de_forma ==)...
Captura-se qualquer excepção para evitar fuga de memória.
else
// Tipo de figura desconhecido.
throw ErroAoCarregar();
}
} catch(...) {
//...
//código que remove as formas entretanto reservadas.relança excepção capturada.
throw; //Usa-se o construtor...
}
}
inline void Figura::carrega(istream& entrada) {
//
Figura(entrada).troca(*this);
}
inline void Figura::troca(Figura& outra_figura) {
formas.swap(outra_figura.formas);
}Suponha-se uma classe
Editorcom a responsabilidade de criar e editar figuras:
class Editor {...
public:
void carrega();
...
private:...
bool figura_alterada;
Figura* figura;
};
void Editor::carrega()
{
string nome =; //pedido ao utilizador......
if(ifstream entrada(nome.c_str())) {
try {
Figura* nova_figura = new Figura(entrada);
if(figura_alterada)
guarda();
delete figura;
figura = nova_figura;
} catch(Erro& e) {
//Aviso de formato de ficheiro inválido.
} catch(bad_alloc) {...
//Aviso de memória insuficiente.
} catch(...) {...
//Aviso de erro desconhecido.
}...
} else
//Aviso de impossibilidade de aceder ao ficheiro.
}
[cotação: 2]
Anexo 1
Definição da classe ListaDouble:
#ifndef LISTA_DOUBLE_H@brief Representa listas de itens do tipo Item.
#define LISTA_DOUBLE_H
#include <iostream>
/**Item é actualmente é um sinónimo de
double, mas que pode ser alterado
facilmente para o tipo que se entender. Por convenção, chama-se "frente"
e "trás" ao primeiro e último item na lista. Os nomes "primeiro", "último",
"início" e "fim" são reservados para iteradores.
@invariant 0 <numero_de_itens.*/@brief Operador de inserção de listas num canal.
class ListaDouble {
public:
typedef double Item;
class Iterador;
class IteradorConstante;
ListaDouble();
~ListaDouble();
Item const& frente() const;
Item const& trás() const;
int comprimento() const;
bool estáVazia() const;
bool estáCheia() const;
IteradorConstante primeiro() const;
IteradorConstante último() const;
IteradorConstante início() const;
IteradorConstante fim() const;
IteradorConstante primeiraOcorrênciaDe(Item const& item) const;
IteradorConstante últimaOcorrênciaDe(Item const& item) const;
Item& frente();
Item& trás();
void põeNaFrente(Item const& novo_item);
void põeAtrás(Item const& novo_item);
void insereAntes(Iterador const& iterador, Item const& novo_item);
void insereAntes(IteradorConstante const& iterador, Item const& novo_item);
void tiraDaFrente();
void tiraDeTrás();
void esvazia();
void remove(Iterador& iterador);
void remove(IteradorConstante& iterador);
void remove(Iterador const& iterador);
void remove(IteradorConstante const& iterador);
void transfereDe(ListaDouble& outra_lista);
ListaDouble& operator += (ListaDouble const& outra_lista);
Iterador primeiro();
Iterador último();
Iterador início();
Iterador fim();
Iterador primeiraOcorrênciaDe(Item const& item);
Iterador últimaOcorrênciaDe(Item const& item);
private:
bool cumpreInvariante() const;
void poe(Elo* elo_anterior, Elo* elo_seguinte, Item const& novo_item);
void tira(Elo* elo_a_tirar);
struct Elo {
Elo(Elo* anterior = 0, Elo* seguinte = 0, Item const& item = Item());
Item item;
Elo* anterior;
Elo* seguinte;
};
int número_de_itens;
Elo* elo_inicial;
Elo* elo_final;
friend Iterador;
friend IteradorConstante;
};
/**
Todos os itens da lista são inseridos por ordem entre parênteses e separados por vírgulas.*/
std::ostream& operator << (std::ostream& saída, ListaDouble const& lista);
#include "lista_double_impl.H"
#endif // LISTA_DOUBLE_H