Resolução do exame de 1ª época

Introdução à Programação

ETI

1º 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.

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.

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 <string>
#include <vector>

using namespace std;

class A {
  public:
    A(const char c = ' ', const int m = 1, const int n = 0);
    string f1(const char);
    int f2(int);
    char f3();
    unsigned t;
  private:
    string s;
    string f1(const unsigned);
};

...

int main()
{
    const char c = '0';
    A am('a', 3);
    vector<char> u(2);
    vector<int> v(2);
    u[0] = 'X';
    u[1] = 'M';
    v[0] = 7;
    v[1] = 13;
    ...
}

1.1  Admita que qualquer uma destas instruções é dada na função main() imediatamente após a definição do vector v.  Quais das seguintes instruções estão correctas?

 V   A a(u[0], v[0], v[1]);
 V   A a(c);

Os últimos dois parâmetros do construtor de A têm valores por omissão (dos argumentos).
 F   A a(u[0], u, v[0]);
O segundo parâmetro do construtor de A é um int e não um vector<char>.
 [cotação: 1,5]

1.2  Admita que qualquer uma destas instruções é dada na função main() do programa acima.  Quais das seguintes instruções estão correctas?

 F  c = am.f3();

c é uma constante, pelo que o seu valor não pode ser alterado.
 F  string s = am.f1(3U);
f1() é uma função membro privada da classe A, pelo que só pode ser invocada por funções ou procedimentos membro da classe A.
 V  int k = am.f2(c - '0');
f2() é uma função membro pública da classe A.
 V  am.t = v[0] + v[1];
t é uma variável membro pública da classe A do tipo unsigned int.  É permitido, embora pouco recomentdável, atribuir um inteiro com sinal a um inteiro sem sinal.
 [cotação: 2]

1.3  Assuma que as seguintes instruções são dadas dentro de uma função ou procedimento membro da classe A e que não são declaradas quaisquer variáveis ou constantes dentro dessa função ou procedimento (que não tem quaisquer parâmetros).  Quais das seguintes instruções estão correctas?

 F   am.t = f2(s.size());

Não existe qualquer variável am visível!
 V   s = f1(t);

 [cotação: 1]

1.4  Suponha o seguinte:

A::A(const char c, const int m, const int n)
    : t(n) {
    for(int i = 0; i != m; ++i)
        s += c;
}
char A::f3() {
    if(t == s.size())
        return s[0];
    return char('a' + t);
}
int main() {
    A a('x'), b('b', 3, 2);
    cout << a.f3() << "   " << b.f3() << endl;
}
Assumindo que os restantes métodos da classe estão definidos, qual é o resultado da invocação do programa?
Apenas uma das opções está correcta.

 F    x   b
 V    a   c
 F    a   b
 F    x   a

Os dois últimos parâmetros do construtor da A têm valores por omissão de 1 e 0, respectivamente.  Assim, a construção da variável a leva a que t seja inicializada com o valor 0 e s fique com exactamente um 'x'.  A execução de f3() para a devolve char('a' + 0), ou seja o código do caractere 'a'.  A inicialização de b leva à inicialização de t com 2 e à colocação de três 'b' em s.  A execução de f3() para b devolve char('a' + 2), ou seja o código do caractere 'c'.
 [cotação: 0,5]

Questão 2

Considere um projecto de trabalho numa empresa.  Um projecto de trabalho é constituído por várias tarefas que têm um dado ínicio e uma dada duração (ambos em dias).  Admita que o projecto se inicia no dia 1 e os restantes dias são contandos a partir daqui.  Em determinadas situações, como é o caso de certas reestruturações, é necessário adicionar ou remover tarefas de um projecto.  Naturalmente, só é possível remover tarefas que ainda não tenham começado, assim como só é possível adicionar tarefas com início depois do dia em que o projecto se encontra.

2.1  Defina a classe Tarefa de modo a guardar:

  1. A identificação da tarefa (uma cadeia de caracteres).
  2. O início da tarefa.
  3. A duração da tarefa.
A classe Tarefa deve dispor de um construtor com três parâmetros, sendo estes a sua identificação, o seu início e a sua duração.  Além disso deve dispor de funções membro que permitam inspeccionar a identificação, o ínicio e a duração de cada tarefa.  Este construtor deve poder ser invocado sem passar qualquer argumento (nesse caso a identificação é a cadeia vazia "", o início é 0, e a duração é 0).

Indique claramente quais dos métodos da classe não alteram a instância implícita.

Não é necessário nesta questão definir qualquer um dos métodos (funções ou procedimentos membro) da classe.

[cotação: 1]

2.2  Defina o construtor da classe Tarefa.

[cotação: 0,5]

2.3  Defina as funções de inspecção da identificação, início e duração para a classe Tarefa.

[cotação: 0,5]

2.4  Defina a classe Projecto que deve guardar a informação sobre as tarefas que o projecto compreende, bem como o dia actual do projecto.  Deve declarar os métodos necessários para:

  1. Adicionar uma nova tarefa ao projecto.  Não é permitida a inserção de tarefas cujo início seja inferior ou igual ao dia actual do projecto.
  2. Remover uma tarefa do projecto, dada a sua identificação.  Não é permitida a remoção de tarefas em curso ou já terminadas.
  3. Avançar o dia actual.
  4. Devolver um booleano que indica se o projecto está concluído ou não.  O projecto está concluído se não existirem quaisquer tarefas em curso ou por realizar.
  5. Mostrar todas as tarefas do projecto, indicando se estão terminadas, em curso ou por iniciar.
Deve ainda declarar um construtor que irá iniciar o dia actual do projecto com 0, significando isto que o projecto ainda não começou.

Indique claramente quais dos métodos da classe não alteram a instância implícita.

Não é necessário nesta questão definir qualquer um dos métodos.  Coloque comentários indicando claramente para que serve cada uma das variáveis membro que definir.

[cotação: 2]

2.5  Defina o método da classe Projecto que remove uma tarefa do projecto dada a sua identificação.

[cotação: 2]

2.6  Defina o método da classe Projecto que adiciona uma nova tarefa ao projecto.

[cotação: 1]

2.7  Escreva um programa que utilize a classe Projecto (descrita e definida anteriormente).  Este programa deve inicialmente construir um projecto com n tarefas, n dado pelo utilizador do programa.  Para cada tarefa deve ser perguntado ao utilizador qual a sua identificação, o seu início e a sua duração.  Em seguida, o programa deverá simular o funcionamento do projecto até este terminar, isto é, até que todas as tarefas estejam terminadas.  A simulação terá início com o avanço do dia actual do projecto para o dia 1 e prosseguirá avançando dia a dia.  Em cada dia do projecto deverá ser exibido ao utilizador do programa o seguinte menu:

  1. Adicionar tarefa - permite adicionar uma nova tarefa, devem ser pedidos os dados da tarefa.
  2. Remover tarefa - permite remover uma tarefa, deve ser pedida a identificação da tarefa.
  3. Sair - sai do menú.
Este menu permitirá à empresa gerir as tarefas do seu projecto.  No fim de cada dia deverá ser exibido o estado do projecto, ou seja, todas as suas tarefas indicando o estado de cada uma e finalmente deve ser avançado o dia actual do projecto.

[cotação: 1]

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Tarefa {
  public:
    Tarefa(string const& = "", int const = 0, int const = 0);
    string identificacao() const;
    int inicio() const;
    int duracao() const;

  private:
    string identificacao_;
    int inicio_, duracao_;
};

inline Tarefa::Tarefa(string const& identificacao, int const inicio,
                      int const duracao)
    : identificacao_(identificacao), inicio_(inicio), duracao_(duracao) {
}

inline string Tarefa::identificacao() const {
    return identificacao_;
}

inline int Tarefa::inicio() const {
    return inicio_;
}

inline int Tarefa::duracao() const {
    return duracao_;
}

class Projecto {
  public:
    typedef vector<Tarefa>::size_type Tamanho;

    Projecto();

    void adiciona(Tarefa const& tarefa);
    void remove(string const& identificacao);
    void avancaDia();
    void mostra() const;
    bool concluido() const;

  private:
    vector<Tarefa> tarefas;
    int dia_actual;
};

inline Projecto::Projecto()
        : dia_actual(0) {
}

void Projecto::remove(string const& identificacao)
{
    for(Tamanho i = 0; i != tarefas.size(); ++i)
        if(tarefas[i].identificacao() == identificacao) {
            if(tarefas[i].inicio() <= dia_actual)
                cerr << "A tarefa não pode ser eliminada..." << endl;
                // É esta a melhor forma de resolver o problema?
            else {
                tarefas[i] = tarefas[tarefas.size() - 1];
                tarefas.pop_back();
            }
            return;
        }
}

void Projecto::adiciona(Tarefa const& t)
{
    if(t.inicio() <= dia_actual)
        cerr << "O início da nova tarefa deve posterior à data actual."
             << endl;
    else
        tarefas.push_back(t);
    // Que faria para evitar inserção de tarefas com identificadores repetidos?
}

inline void Projecto::avancaDia() {
    ++dia_actual;
}

void Projecto::mostra() const
{
    for(Tamanho i = 0; i != tarefas.size(); ++i) {
        cout << "----------------------------------------" << endl;
        cout << "Tarefa: " << lista_de_tarefas[i].identificacao() << endl;
        cout << "Início: " << lista_de_tarefas[i].inicio() << endl;
        cout << "Duração: " << lista_de_tarefas[i].duracao() << endl;
        cout << "Estado: ";
        if(tarefas[i].inicio() > dia_actual)
            cout << "por iniciar" << endl;
        else if (tarefas[i].inicio() +
                 tarefas[i].duracao() <= dia_actual)
            cout << "terminada" << endl;
        else
            cout << "em curso" << endl;
    }
    cout << "----------------------------------------" << endl;
}

bool Projecto::concluido() const {
    for(Tamanho i = 0; i != tarefas.size(); ++i)
        if(tarefas[i].inicio() + tarefas[i].duracao() > dia_actual)
            return false;
    return true;
}

int main()
{
    Projecto p;

    cout << "Indique o número de tarefas do projecto: ";
    int numero_de_tarefas;
    cin >> numero_de_tarefas;

    for(int i = 0; i != numero_de_tarefas; ++i) {
        cout << "Identificação da tarefa: ";
        string identificacao;
        cin >> identificacao;

        cout << "Inicio: ";
        int inicio;
        cin >> inicio;

        cout << "Duração: ";
        int duracao;
        cin >> duracao;

        p.adiciona(Tarefa(identificacao, inicio, duracao));
    }

    p.avancaDia();
    while(!p.concluido()) {
        int opcao;
        do {
            cout << "1 - Adicionar tarefa" << endl;
            cout << "2 - Remover tarefa" << endl;
            cout << "3 - Próximo dia" << endl;
            cout << endl << "Opção: " << endl;
            cin >> opcao;
            switch(opcao) {
              case 1:
                cout << "Identificação da tarefa: ";
                string identificacao;
                cin >> identificacao;

                cout << "Inicio: ";
                int inicio;
                cin >> inicio;

                cout << "Duração: ";
                int duracao;
                cin >> duracao;

                p.adiciona(Tarefa(identificacao, inicio, duracao));
                break;

              case 2:
                cout << "Identificação da tarefa: ";
                string identificacao;
                cin >> identificacao;

                p.remove(identificacao);
                break;

              case 3:
                p.avancaDia();
                break;
            }
        } while(opcao != 3);
        p.mostra();
    }
}

Questão 3

Defina completamente uma classe (ListaDePalavras) que permite manipular um texto de modo a obter listagem das palavras que o constituem.  Deve declarar a classe e definir:

  1. O construtor ListaDePalavras::ListaDePalavras(const string& nome_do_ficheiro) que recebe como argumento o nome de um ficheiro e constrói a lista de palavras que constituem (sem repetições).  Este construtor não deve poder ser utilizado para converter implicitamente de string para ListaDePalavras.
  2. Um método void ListaDePalavras::mostra() const que permite visualizar no ecrã a lista construída.
Deve ainda sobrecarregar o operador += que permite somar uma instância da classe ListaDePalavras a outra.  Isto significa acrescentar à instância usada como operando esquerdo do operador todas as palavras da instância usada como segundo operando (e, novamente, evitando as repetições).

Supondo que o ficheiro texto.txt tem o seguinte conteúdo:

isto é um teste
claro que poderia não ser um teste
mas é um
O seguinte troço de programa
ListaDePalavras l1("texto.txt");

l1.mostra();

escreve no ecrã:
isto
é
um
teste
claro
que
poderia
não
ser
mas
Se o conteúdo de texto2.txt for:
isto é o segundo teste
poderia haver outros
mas não há
O seguinte troço de programa
ListaDePalavras l1("texto.txt");
ListaDePalavras l2("texto2.txt");
l1 += l2;

l1.mostra();

teria como resultado:
 
isto
é
um
teste
claro
que
poderia
não
ser
mas
o
segundo
poderia
haver
outros
[cotação: 3,5]
#include <iostream>
#include <string>
#include <vector>
#include <fstream>

using namespace std;

class ListaDePalavras {
  public:
    typedef vector<string>::size_type Tamanho;

    explicit ListaDePalavras(string const&);
    void mostra() const;

    ListaDePalavras& operator += (ListaDePalavras const&);

  private:
    vector<string> palavras;
};

ListaDePalavras::ListaDePalavras(string const& nome_do_ficheiro)
{
    ifstream entrada(nome_do_ficheiro.c_str());
    if(!entrada) {
        cerr << "Erro: não foi possível abrir o ficheiro <"
             << nome_do_ficheiro << ">." << endl;
        exit(1);
    }

    string palavra;
    while(entrada >> palavra) {
        Tamanho i = 0;
        while(i != palavras.size() && palavras[i] != palavra)
            ++i;
        if(i == palavras.size())
            palavras.push_back(palavra);
    }
}

void ListaDePalavras::mostra() const
{
    for(Tamanho i = 0; i != palavras.size(); ++i)
        cout << palavras[i] << endl;
}

ListaDePalavras& ListaDePalavras::operator += (ListaDePalavras const& l)
{
    for(Tamanho i = 0; i != l.palavras.size(); ++i) {
        Tamanho j = 0;
        while(j != palavras.size() && palavras[j] != l.palavras[i])
            ++j;
        if(j == palavras.size())
            palavras.push_back(l.palavras[i]);
    }

    return *this;
}

Questão 4

Considere a função int decimal(const int m[], const int n) que devolve o inteiro correspondente ao número na base 8 recebido como uma matriz de dígitos de tamanho n.  Para compreender melhor como se interpreta um número na base 8, atente no seguinte exemplo:

(271)8  = 185

(271) 8 = 2×82 + 7×81 + 1×80 = 2×64 + 7×8  + 1×1 = 128 + 56 + 1 = 185

A função deve poder ser usada como se segue:
int m[3] = {2, 7, 1};
int valor = decimal(m, 3);
cout << "O valor é: " << valor << '.' << endl;
que escreve no ecrã
O valor é 185.
Admita que está disponível a seguinte função:
// PC: n >= 0
// CO: potência = xn
int potência(const int x, const int n)
{
    int potência = 1;
    for(int i = 0; i != n; ++i)
        potência *= x;
    return potência;
}
4.1  Dada a pré-condição n > 0, indique a condição objectivo (CO), a condição invariante (CI) e a guarda (G) do ciclo necessário à construção desta função.

[cotação: 0,5]

CO: soma = (S j : 0 <=  j < n : m[j] × 8n - 1 - j)
CIsoma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n
G: i <> n
4.2  Defina completamente a função indicada.

[cotação: 1]

int converteOctal(int const m[], int const n)
{
    int soma = 0;
    for(int i = 0; i != n; ++i)
        soma += m[i] * potencia(8, n - 1 - i);

    return soma;
}

4.3  Prove que o ciclo está correcto verificando que:
  1. Se a PC for verdadeira, então a CI também é verdadeira após a inicialização das variáveis e antes da primeira iteração do ciclo, i.e.:
    1. // PC
      init
      // implica CI.
       
        // PC: n > 0
        // é evidente que a PC implica
        // 0 = (S j : 0 <= j < 0 : m[j] × 8n - 1 - j) e 0 <= 0 <= n, ou seja,
        // 0 = 0 e 0 <= n, ou seja,
        // n >= 0
        int soma = 0;
        int i = 0;
        // CI: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n.
  2. Se a guarda G e a CI  do ciclo forem verdadeiras no início de um passo, então a CI também é verdadeira no fim desse passo, i.e.:
    1. // G e CI
      passo
      // implica CI
       
        // CI e G: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n e i <> n, ou seja,
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i < n
        soma += m[i]*potencia(8, n - 1 - i);
        ++i;
        // implica CI: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n.

        A demonstração pode ser feita de uma forma directa, deduzindo as asserções verdadeiras após cada instrução:

        // CI e G: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n e i <> n, ou seja,
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i < n
        soma += m[i] * potencia(8, n - 1 - i);
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) + m[i] × 8n - 1 - i e 0 <= i < n, ou seja,
        // soma = (S j : 0 <= j < i + 1 : m[j] × 8n - 1 - j) e 0 <= i < n
        ++i;
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i - 1 < n, ou seja,
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 1 <= i < n + 1, ou seja,
        // soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 1 <= i <= n
        // que implicaCI: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n.
         

  3. Se a guarda G for falsa e a CI for verdadeira, então a CO é verdadeira, i.e.:
    1. // CI e ¬G implicaCO.
       
        // CI e ¬G: soma = (S j : 0 <= j < i : m[j] × 8n - 1 - j) e 0 <= i <= n e i = n, ou seja,
        // soma = (S j : 0 <= j < n : m[j] × 8n - 1 - j) e 0 <= n <= n e i = n, ou seja, dada a PC,
        // soma = (S j : 0 <= j < n : m[j] × 8n - 1 - j) e V e i = n, que implica
        // CO: soma = (S j : 0 <= j < n : m[j] × 8n - 1 - j)
[cotação: 1]

Questão 5

Explique brevemente o que é a Condição Invariante de Instância (CII) de uma classe (lembre-se da classe Racional).  Para que serve?

[cotação: 1]

Ver Folhas teóricas, Secção 6.3.4 (Condição Invariante de Instância).