ISCTE - IGE

Programação I

Resolução da 2ª Frequência

1998/1999,  1º semestre



A azul e indentadas uma vez as resoluções dos exercícios.

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.

Qualquer alínea pode 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 {
  private: 
    int x;
    int y;

  public:
    A(int a, int b = 0);
    int f1();
    int f2(int);
    bool f3();
};

int main() {
    int i = 1, j = 2;
   
...
}
a)  Quais das seguintes definições estão correctas?

 V  A a = A(3);

Forma rebuscada de dizer: A a(3);.  É invocado o construtor por cópia para construir a a partir de uma instância temporária de A construída invocando o construtor de A que pode receber apenas um argumento inteiro.
 F  A a;
O construtor de A só pode ser invocado com um ou dois argumentos: não existe construtor por omissão.
 V  A a(1);
Ver primeira resposta.
 V  A a(i, j);
O construtor de A pode ser invocado com dois argumentos inteiros.
 [cotação: 2]

b) Assuma que está correctamente declarada e definida uma variável a da classe A.  Quais das seguintes instruções estão correctas?

 F  a.x = 1;

A variável membro x é privada.
 F  if(A.f3()) cout << "bla bla";
A não é nome de variável: é nome de classe.  Estaria correcto se tivesse a.f3().
 F  int z = a.f2();
A função membro f2() tem um parâmetro sem valor por omissão: exige um argumento.
 [cotação: 1.5]

c)  Assuma que as seguintes instruções são dadas dentro de uma função membro da classe A e que não são declaradas quaisquer variáveis dentro dessa função. Quais das seguintes instruções estão correctas?

 F  a.x = 1;

Não existe qualquer variável a...
 F  A.x = 1;
A é nome da classe, e não de instância de classe.
 V  x = 1;

 [cotação: 1.5]

 Questão 2

2.a)  Declare uma classe Conjunto que possa guardar um máximo de 100 valores inteiros diferentes.  Não é necessário nesta alínea definir qualquer um dos métodos.

Devem existir métodos para :

Deve também se declarada a sobrecarga do operador +=.  Este operador insere todos os elementos do conjunto dado como segundo operando no conjunto dado como primeiro operando (i.e., aquele que executa a operação).  Deve devolver uma referência para o conjunto alterado (primeiro operando).

A classe deve também ter um construtor que possa ser invocado sem quaisquer argumentos (construtor por omissão).

A declaração da classe deve ser tal que o seguinte exemplo de utilização seja válido:

Conjunto A, B;

A.insere(1);
A.insere(2);
// A = {1, 2}
B.insere(1);
B.insere(3);
// B = {1, 3}
B += A;
// B = {1, 2, 3}
cout << B.cardinal() << endl; // escreve 3 (e não 4).

[cotação: 3]

2.b)  Defina o construtor da classe Conjunto.  Este construtor deve inicializar coerentemente as variáveis de instância da classe.

[cotação: 3]

2.c)  Defina totalmente o método da classe Conjunto que, dado um argumento inteiro, devolve true se o seu valor pertence ao conjunto e false no caso contrário.

[cotação: 3]

2.d)  Assuma que todos os métodos declarados na classe Conjunto estão bem definidos, excepto o operador +=.  Defina o operador += para a classe Conjunto.  Este operador insere todos os elementos do conjunto dado como segundo operando no conjunto dado como primeiro operando (i.e., aquele através do qual a operação é efectuada).  Deve devolver uma referência para o conjunto alterado (para esse efeito deve usar a instrução return *this;).

Assuma como pré-condição do método que a união dos dois conjuntos tem um cardinal (número de elementos) não superior a 100.

[cotação: 3]

Apresenta-se a solução completa incluindo um programa de teste:
#include <iostream>
#include <cstdlib>

using namespace std;

class Conjunto {
  public:
    typedef int Elemento;
    static const int número_máximo_de_elementos = 100;

    Conjunto();
    void insere(const Elemento&);
    void remove(const Elemento&);
    bool vazio() const;
    bool contém(const Elemento&) const;
    int cardinal() const;
    Conjunto& operator += (const Conjunto&);
    friend ostream& operator << (ostream&, const Conjunto&);

  private:
    Elemento elementos[número_máximo_de_elementos];
    int número_de_elementos;
};

inline Conjunto::Conjunto() : número_de_elementos(0) {
}

void Conjunto::insere(const Elemento& elemento) {
    if(!contém(elemento)) {
        if(número_de_elementos == número_máximo_de_elementos) {
            cerr << "Conjunto encheu demais!" << endl;
            exit(1); // feio, mas depois se aprenderá melhor solução.
        }
        elementos[número_de_elementos++] = elemento;
    }
}

void Conjunto::remove(const Elemento& elemento) {
    for(int i = 0; i != número_de_elementos; i++)
        if(elementos[i] == elemento) {
            elementos[i] = elementos[--número_de_elementos];
            return;
        }
    cerr << "Elemento não existe!" << endl,
    exit(1); // feio, mas depois se aprenderá melhor solução.
}

inline bool Conjunto::vazio() const {
    return número_de_elementos == 0;
}

bool Conjunto::contém(const Elemento& elemento) const {
    for(int i = 0; i != número_de_elementos; i++)
        if(elementos[i] == elemento)
            return true;
    return false;
}

inline int Conjunto::cardinal() const {
    return número_de_elementos;
}

Conjunto& Conjunto::operator += (const Conjunto& c) {
    for(int i = 0; i != c.cardinal(); ++i)
        insere(c.elementos[i]);
    return *this;
}

ostream& operator << (ostream& saída, const Conjunto& c) {
    saída << '{';
    for(int i = 0; i != c.cardinal(); ++i) {
        saída << c.elementos[i];
        if(i != c.cardinal() - 1)
            saída << ", ";
    }
    return saída << '}';
}

int main()
{
    Conjunto a;
    a.insere(1);
    a.insere(2);
    a.insere(2);
    cout << a << endl;
    Conjunto b;
    b.insere(0);
    b.insere(2);
    b.insere(3);
    b.insere(4);
    b.remove(4);
    cout << b << endl;
    b += a;
    cout << b << endl;
}

Depois da execução do programa aparece

{1, 2}
{0, 2, 3}
{0, 2, 3, 1}
no ecrã.
Questão 3

Escreva um texto sucinto sobre a vantagem de usar variáveis membro privadas numa classe.

[cotação: 3]

As variáveis membro privadas não estão acessíveis ao utilizador da classe.  Assim, ficam protegidas de eventuais erros cometidos por esse utilizador.  Em geral é boa ideia esconder tanto quanto possível a implementação duma dada classe, utilizando-se para isso o conceito de membros privados.  A este princípio chama-se o princípio do encapsulamento.   O acesso (indirecto) do utilizador à implementação faz-se por intermédio duma interface, constituida normalmente por funções e procedimentos públicos.