Exercícios de Exame

Limpar todos os resolvidos?

Fundamentos de C++ e texto

Verdadeiro ou falso sobre fundamentos de C++Autoavaliação

Exame, 2019/20

Indique se cada uma das afirmações seguintes sobre C++ é verdadeira (V) ou falsa (F).

  1. Num programa em C++ é possível fazer a definição bool private = true;.
  2. Para testar se uma variável x do tipo int está no intervalo [-10..10] pode usar-se a instrução if (-10 <= x <= 10).
  3. O ciclo for (int i = 0; i < n; i++) std::cout << (char)('A' + i); escreve no ecrã as primeiras n letras do alfabeto.
  4. Para guardar a palavra "Hello" numa string de C é necessário um array de char com pelo menos 6 elementos.
  5. Se name é uma variável do tipo std::string, ao executar cin >> name; com o utilizador a escrever "Ana-2003 Porto", o valor atribuído a name é "Ana-2003".
  6. A função void f(const string &s, int &a) pode ser invocada com f("PROG", 210).

Solução

1: F, 2: F, 3: V, 4: V, 5: V, 6: F.

private é palavra reservada, pelo que a definição da 1 não compila. Em C++, -10 <= x <= 10 avalia primeiro -10 <= x (verdadeiro ou falso, convertido para 0 ou 1) e compara esse resultado com 10, pelo que a 2 é sempre verdadeira para qualquer x — não testa o intervalo. O ciclo da 3 imprime A, B, ... até à n-ésima letra. Uma string de C termina em '\0', pelo que "Hello" precisa de 6 carateres. O operador >> lê apenas até ao primeiro espaço, pelo que a 5 fica com "Ana-2003". Por fim, int &a não pode ligar-se à constante 210, pelo que a 6 é falsa.

Confere a tua resposta:

  • A 1 é classificada como falsa: private é uma palavra reservada.
  • A 2 é classificada como falsa: a condição não testa o intervalo.
  • A 3 é classificada como verdadeira.
  • A 4 é classificada como verdadeira: são 5 carateres mais o terminador.
  • A 5 é classificada como verdadeira: a leitura para no primeiro espaço.
  • A 6 é classificada como falsa: o segundo argumento não pode ser um literal para um parâmetro por referência não constante.
Normalizar uma linha de texto para maiúsculas e espaçosAutoavaliação

Exame, 2017/18

Escreva uma função string transformLine(string s) que recebe uma string

constituída por letras, espaços e outros carateres e retorna uma string constituída apenas por letras maiúsculas e espaços, substituindo os carateres que não são letras nem espaços pelo caráter espaço. A string recebida como parâmetro não deve ser modificada. Exemplo: para "A very, very short sentence!" a função retorna "A VERY VERY SHORT SENTENCE". Deve usar a função auxiliar void toUpperStr(string* s), que converte para maiúsculas, in loco, todas as letras da string apontada.

Solução
string transformLine(string s)
{
  string result = "";
  for (size_t i = 0; i < s.length(); i++)
  {
    char c = s.at(i);
    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ')
      result += c;
    else
      result += ' ';
  }
  toUpperStr(&result);
  return result;
}

Cada caráter é testado individualmente: letras e espaços copiam-se, todo o resto passa a espaço. Depois invoca-se toUpperStr sobre a string acumulada, que converte as minúsculas para maiúsculas in loco, e retorna-se essa string. A string original nunca é alterada porque se constrói uma nova.

Confere a tua resposta:

  • A função percorre todos os carateres da string original sem a modificar.
  • Os carateres que não são letras nem espaços são substituídos por espaço.
  • A conversão para maiúsculas é feita com toUpperStr sobre a string resultante antes de a retornar.

Funções e arrays

Média de classificações num array CAutoavaliação

Exame, 2017/18

Escreva o código da função

float average(const int grades[], int numStudents);

que calcula e retorna a média das numStudents classificações de uma prova, guardadas no array grades. Considere que numStudents é sempre maior ou igual a 1.

Solução
float average(const int grades[], int numStudents)
{
  float sum = 0;
  for (int i = 0; i < numStudents; i++)
    sum += grades[i];
  return sum / numStudents;
}

Um array recebido como parâmetro decai para um apontador, pelo que o ciclo percorre as numStudents posições acumulando em sum. A divisão sum / numStudents é feita em vírgula flutuante porque sum é float, devolvendo a média como float.

Confere a tua resposta:

  • A função percorre as numStudents posições do array acumulando a soma.
  • A média é devolvida como float, dividindo a soma pelo número de estudantes.
Hora mais cedo e mais tarde num array de structsAutoavaliação

Exame de Recurso, 2023/24

Considere o tipo time_of_day, definido num ficheiro de cabeçalho, que representa horas com precisão de minutos:

struct time_of_day {
  unsigned char h; // horas [0,23]
  unsigned char m; // minutos [0,59]
};

Escreva uma função

void minmax(const time_of_day times[], int n, time_of_day& min, time_of_day& max);

que recebe um array times com n > 0 horas válidas e atribui a min e max a hora mais cedo e a hora mais tarde existentes no array. Não pode usar contentores (vector, list, set, ...) nem funções de biblioteca.

Solução
void minmax(const time_of_day times[], int n, time_of_day& min, time_of_day& max)
{
  min = max = times[0];
  for (int i = 1; i < n; i++)
  {
    int t = times[i].h * 60 + times[i].m;
    int tmin = min.h * 60 + min.m;
    int tmax = max.h * 60 + max.m;
    if (t < tmin) min = times[i];
    if (t > tmax) max = times[i];
  }
}

Comparar apenas as horas não chega (por exemplo, 10:50 é maior que 11:05? não, mas 11:05 > 10:50 em horas seria falso se só se olhasse para h). Convertendo cada hora para minutos desde a meia-noite, h * 60 + m, a comparação fica correta. O primeiro elemento serve de candidato inicial a mínimo e máximo.

Confere a tua resposta:

  • A função inicializa min e max com o primeiro elemento e percorre o resto do array.
  • A comparação entre horas tem em conta horas e minutos (por exemplo, convertendo para minutos desde a meia-noite).
  • Não usa contentores nem funções de biblioteca.
Heterograma numa string de CAutoavaliação

Exame de Recurso, 2023/24

Uma sequência de carateres é um heterograma se cada letra ocorre no máximo uma vez. Por exemplo, "sun" é um heterograma e "moon" não é (a letra 'o' ocorre mais do que uma vez).

Escreva uma função

bool heterogram(const char s[], char r[]);

tais que:

  • s é uma string de C com letras maiúsculas ou minúsculas e o caráter espaço; os espaços devem ser ignorados e uma letra minúscula deve ser considerada equivalente à maiúscula correspondente;
  • a função retorna true se e só se s é um heterograma;
  • no retorno, r é uma string de C em minúsculas com todas as letras repetidas em s, por ordem alfabética (r fica vazia se s for um heterograma).

Sugestão: use um array interno de 26 posições para contar as letras. Não pode usar classes nem funções de biblioteca (vector, string, sort, ...).

Solução
bool heterogram(const char s[], char r[])
{
  int count[26] = {0};
  int nrep = 0;
  for (int i = 0; s[i] != '\0'; i++)
  {
    char c = s[i];
    if (c == ' ') continue;
    if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
    int idx = c - 'a';
    count[idx]++;
    if (count[idx] == 2) r[nrep++] = c;
  }
  r[nrep] = '\0';
  // ordenar r (insertion sort, por ser curta)
  for (int i = 1; i < nrep; i++)
  {
    char x = r[i];
    int j = i - 1;
    while (j >= 0 && r[j] > x) { r[j + 1] = r[j]; j--; }
    r[j + 1] = x;
  }
  return nrep == 0;
}

Cada letra (depois de passar a minúscula) é mapeada para um índice entre 0 e 25. A segunda vez que uma letra aparece, essa letra é acrescentada a r. Como as letras são registadas pela ordem em que aparecem, r precisa de ser ordenada no fim; como há no máximo 26 letras, um insertion sort simples chega. O heterograma é detetado quando nenhuma letra se repete.

Confere a tua resposta:

  • A função usa um array de 26 inteiros para contar as ocorrências de cada letra, ignorando espaços e unificando maiúsculas/minúsculas.
  • Retorna false mal detete a segunda ocorrência de uma letra.
  • r recebe as letras repetidas em minúsculas e por ordem alfabética, terminada em '\0'.

Ficheiros e dados externos

De um ficheiro de texto a uma lista de palavras ordenadaAutoavaliação

Exame, 2017/18

Considere já implementada a função void decomposeLine(string line, vector<string>* words), que decompõe uma linha de texto nas suas palavras e as acrescenta ao vetor words.

Escreva um programa que lê todas as linhas do ficheiro text.txt, gera a lista com todas as palavras nelas existentes e grava essa lista no ficheiro words.txt, por ordem alfabética, uma palavra por linha. Considere que ambos os ficheiros abrem sempre com sucesso e que as palavras repetidas devem ser mantidas na lista.

Solução
int main()
{
  ifstream in("text.txt");
  ofstream out("words.txt");
  vector<string> total;
  string line;
  while (getline(in, line))
  {
    vector<string> words;
    decomposeLine(line, &words);
    total.insert(total.end(), words.begin(), words.end());
  }
  in.close();
  sort(total.begin(), total.end());
  for (const string& w : total)
    out << w << endl;
  out.close();
  return 0;
}

Cada linha lida é decomposta em palavras, que se juntam ao vetor acumulador total com insert. Depois ordena-se o vetor com o algoritmo sort da STL e escreve-se cada palavra numa linha do ficheiro de saída. Como não se usa um set, as repetidas mantêm-se.

Confere a tua resposta:

  • O programa lê o ficheiro linha a linha com getline e decompõe cada linha com decomposeLine.
  • Todas as palavras são acumuladas num único vetor.
  • O vetor é ordenado com sort antes de escrever o ficheiro de saída, uma palavra por linha.
Importar um tabuleiro de Campo Minado de um ficheiroAutoavaliação

Exame, 2019/20

No jogo Campo Minado, as minas estão espalhadas por um tabuleiro retangular. O ficheiro do jogo indica, na primeira linha, o tamanho do tabuleiro (linhas x colunas) e, nas linhas seguintes, a posição de cada mina (linha , coluna). Exemplo para um tabuleiro 10x10:

10 x 10
1 , 0
1 , 1
2 , 4

Implemente o construtor da classe:

class Game {
public:
  Game(const string &filename);
  // ... outros métodos
private:
  vector<vector<char>> mines;
};

O construtor deve importar o conteúdo do ficheiro para o vetor mines, com o tamanho indicado no ficheiro, colocando o caráter 'M' nas células com mina e o caráter espaço nas restantes. Se o ficheiro não existir, o programa deve terminar com código de saída igual a 1. Considere que o ficheiro não contém valores errados.

Solução
Game::Game(const string &filename)
{
  ifstream f(filename);
  if (!f.is_open())
    exit(1);
  char sep;
  int numLines, numCols, lin, col;
  f >> numLines >> sep >> numCols;
  mines.resize(numLines, vector<char>(numCols, ' '));
  while (f >> lin >> sep >> col)
    mines.at(lin).at(col) = 'M';
}

A primeira linha tem o formato 10 x 10, pelo que se lêem dois inteiros separados por um caráter (sep). O vetor é redimensionado para as dimensões lidas, todo preenchido com espaços, e cada par seguinte marca uma mina com 'M'. O ciclo while termina naturalmente no fim do ficheiro. Se o ficheiro não abrir, exit(1) termina o programa com o código pedido.

Confere a tua resposta:

  • O construtor abre o ficheiro e termina com exit(1) se a abertura falhar.
  • Lê as dimensões e redimensiona o vetor bidimensional preenchido com espaços.
  • Lê cada par linha , coluna e coloca 'M' na célula correspondente.
Organizar notas por cadeira e por nome de estudanteAutoavaliação

Exame de Recurso, 2023/24

Escreva uma função

void organize_grades(const string& input_fname, const string& output_fname);

que lê as linhas de um ficheiro de entrada, cada uma com um identificador de cadeira, o nome de um estudante e uma nota inteira (por exemplo, L.EIC009 Maria 19), e escreve noutro ficheiro uma secção por cadeira: a secção começa pela linha com o identificador da cadeira, seguida de uma linha por estudante dessa cadeira com o nome e a nota. As secções de saída devem estar ordenadas por identificador de cadeira e, dentro de cada secção, os estudantes por ordem alfabética de nome. Exemplo:

Entrada:

L.EIC009 Maria 19
L.EIC009 Manuel 10
L.EIC008 Alberto 10
L.EIC008 Maria 12
L.EIC009 Alberto 20

Saída:

L.EIC008
Alberto 10
Maria 12
L.EIC009
Alberto 20
Manuel 10
Maria 19

Pode assumir que, em cada cadeira, existe no máximo uma nota por estudante.

Solução
void organize_grades(const string& input_fname, const string& output_fname)
{
  ifstream in(input_fname);
  ofstream out(output_fname);
  map<string, map<string, int>> grades;
  string course, name;
  int grade;
  while (in >> course >> name >> grade)
    grades[course][name] = grade;
  for (const auto& c : grades)
  {
    out << c.first << endl;
    for (const auto& s : c.second)
      out << s.first << ' ' << s.second << endl;
  }
}

Um map<string, map<string, int>> associa cada cadeira a um map de nome para nota. Como os map mantêm as chaves ordenadas, ao percorrer a estrutura externa e a interna na ordem natural escrevem-se as secções por cadeira ordenadas e, dentro de cada uma, os estudantes por nome, sem ser preciso chamar sort.

Confere a tua resposta:

  • Usa uma estrutura ordenada por chave, como map<string, map<string, int>>, que garante cadeiras e nomes ordenados.
  • Lê o ficheiro de entrada linha a linha, separando cadeira, nome e nota.
  • Escreve as secções por ordem da chave do map exterior e, dentro de cada uma, por ordem do nome.

Contentores e algoritmos da STL

Gerar um vetor de números aleatóriosAutoavaliação

Exame, 2019/20

Escreva uma função genRandom que gera e retorna um vector<unsigned int> com n números inteiros aleatórios no intervalo [0..m], em que n e m são parâmetros da função. Defina o tipo de retorno e os parâmetros como achar apropriado.

Solução
vector<unsigned int> genRandom(unsigned int n, unsigned int m)
{
  vector<unsigned int> v;
  for (unsigned int i = 0; i < n; i++)
    v.push_back(rand() % (m + 1));
  return v;
}

rand() % (m + 1) produz valores uniformes entre 0 e m, inclusive. O ciclo corre n vezes, acrescentando cada valor ao vetor, que é devolvido por valor (a STL trata da cópia).

Confere a tua resposta:

  • A função cria um vetor vazio e acrescenta n elementos com push_back.
  • Cada elemento é gerado com rand() % (m + 1), ficando no intervalo [0..m].
  • O vetor é devolvido por valor.
Remover de um vetor os elementos comuns a outroAutoavaliação

Exame, 2019/20

Escreva uma função vectorReduce que recebe dois vetores, v1 e v2, e modifica v2, removendo-lhe todos os elementos que também pertencem a v1, e retorna a contagem dos elementos removidos. A função não escreve nada no ecrã. Exemplo: se v1 = {1, 2, 3} e v2 = {2, 4, 2, 5, 3}, após a invocação v2 = {4, 5} e o valor retornado é 3.

Solução
unsigned int vectorReduce(const vector<unsigned int> &v1, vector<unsigned int> &v2)
{
  unsigned int count = 0;
  for (size_t i = 0; i < v2.size(); )
  {
    bool found = false;
    for (size_t k = 0; k < v1.size(); k++)
      if (v2[i] == v1[k]) { found = true; break; }
    if (found)
    {
      v2.erase(v2.begin() + i);
      count++;
    }
    else
      i++;
  }
  return count;
}

Percorre-se v2 elemento a elemento e, para cada um, verifica-se se pertence a v1. Se pertencer, remove-se com erase e incrementa-se o contador; como a remoção desloca os elementos seguintes para a esquerda, o índice só avança quando o elemento atual não é removido. No exemplo, 2, 2 e 3 são removidos, sobrando {4, 5} e com 3 remoções registadas. Alternativamente, pode usar-se o idiom erase-remove com remove_if e find.

Confere a tua resposta:

  • A função percorre os elementos de v1 e, para cada um, procura-o em v2.
  • Cada elemento encontrado é removido de v2 com erase e contado.
  • O número de elementos removidos é devolvido.
Procurar um valor num vector<string> com findAutoavaliação

Exame, 2017/18

A STL de C++ disponibiliza a função find com o seguinte protótipo:

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val);

Diga se é possível usar esta função para procurar o valor "31" numa variável v do tipo vector<string>. Em caso afirmativo, escreva o pedaço de código que procura o valor e escreve uma mensagem a indicar se foi encontrado ou não. Justifique a resposta.

Solução
auto it = find(v.begin(), v.end(), "31");
if (it != v.end())
  cout << "Element found";
else
  cout << "Element not found";

Sim, é possível: basta passar v.begin() e v.end() como iteradores de entrada e "31" como valor a procurar (a comparação usa operator== de string). A função retorna um iterador para a primeira ocorrência ou para v.end() se não existir, pelo que a comparação com v.end() decide se o valor foi encontrado.

Confere a tua resposta:

  • A resposta é afirmativa: find funciona com os iteradores begin()/end() do vetor.
  • O código compara o iterador retornado com v.end() para saber se encontrou.
  • A justificação refere que find retorna um iterador para o elemento ou para o fim do intervalo.
Porque falha a declaração de um set<Position>Autoavaliação

Exame, 2017/18

Um programador queria usar num programa uma estrutura de dados do tipo set<Position>, em que Position é a struct

struct Position { int lin, col; };

mas obtuvo um erro de compilação ao declarar variáveis deste tipo. Indique a causa mais provável deste problema e aponte uma solução. Não escreva código.

Solução

Um std::set é um contentor associativo ordenado que, por omissão, usa std::less<Position>, o que exige o operador < para Position. Como a struct não o sobrecarrega, a instanciação do set falha na compilação. A solução é definir bool operator<(const Position&, const Position&) (por exemplo, comparando primeiro lin e depois col) ou declarar o set com um tipo comparador próprio, como set<Position, ComparaPosition>.

Os contentores ordenados da STL (set, map, ...) precisam de uma relação de ordem estrita entre os elementos. Sem operator< para Position, o modelo interno do set não compila. Basta acrescentar a sobrecarga do operador ou fornecer um functor comparador como segundo parâmetro do template.

Confere a tua resposta:

  • A justificação indica que std::set precisa de comparar elementos com operator< e a struct Position não o define.
  • A solução proposta é sobrecarregar o operator< para Position ou passar um comparador/comparação ao set.

Classes e objetos

Registar uma nota só se o estudante estiver matriculadoAutoavaliação

Exame, 2019/20

Considere a classe parcial Student:

class Student {
public:
  Student(int id = 0, const string &name = "");
  void enrollCourse(Course *course);
  bool setGrade(int courseId, int grade);
  // ... outros métodos
private:
  int id;
  string name;
  vector<Course *> courses;  // UCs em que está matriculado
  map<int, int> grades;       // notas obtidas (chave = courseId)
};

Escreva o código do método setGrade, que regista a nota grade na cadeira courseId e retorna true, ou retorna false se: (1) o estudante não estiver matriculado nessa cadeira (verificando através de courses), ou (2) já existir uma entrada em grades com a chave courseId.

Solução
bool Student::setGrade(int courseId, int grade)
{
  bool isEnrolled = false;
  for (auto c : courses)
    if (c->getId() == courseId)
    {
      isEnrolled = true;
      break;
    }
  if (!isEnrolled) return false;
  if (grades.find(courseId) != grades.end()) return false;
  grades[courseId] = grade;
  return true;
}

Percorre-se o vetor de apontadores courses para confirmar a matrícula; se nenhum curso tiver o courseId, retorna-se false. Depois, grades.find(courseId) != grades.end() deteta uma nota já existente. Só se ambas as verificações passarem se insere a nota no map (com grades[courseId] = grade ou insert) e se retorna true.

Confere a tua resposta:

  • O método verifica primeiro se existe algum curso em courses com o identificador pedido.
  • Depois verifica se grades já contém a chave courseId (por exemplo, com find).
  • Só regista a nota e retorna true se nenhuma das verificações falhar.
Média arredondada das notas de um estudanteAutoavaliação

Exame, 2019/20

Considerando ainda a classe Student da pergunta anterior, escreva o código de um método int getAverageGrade() const que retorna a classificação média do estudante em todas as cadeiras registadas em grades, arredondada para o inteiro mais próximo.

Solução
int Student::getAverageGrade() const
{
  float sum = 0;
  for (auto x : grades)
    sum += x.second;
  return (int)(sum / grades.size() + 0.5);
}

Num map<int, int>, cada elemento é um par (chave, valor); a nota é o second. Somam-se todos os valores e divide-se pelo número de elementos. Somar 0.5 antes do truncamento para int arredonda para o inteiro mais próximo (para valores positivos).

Confere a tua resposta:

  • O método percorre o map grades acumulando a soma das notas (os valores, second).
  • A média é arredondada para o inteiro mais próximo antes de ser devolvida.
Colocar um navio num tabuleiroAutoavaliação

Exame, 2017/18

No jogo da Batalha Naval, um tabuleiro é representado pela classe parcial:

class Board {
public:
  Board(size_t numLines = 10, size_t numColumns = 10);
  bool putShip(const Ship &s);
private:
  bool canPutShip(Position pos, char dir, size_t size);
  size_t numLines, numColumns;
  vector<vector<int>> board; // cada elemento = id do navio ou -1 ("mar")
  vector<Ship> ships;
};

A classe Ship tem os métodos Position pos() const, char dir() const ('H' ou 'V'), size_t size() const e unsigned int id() const. O método canPutShip já está implementado e verifica se o navio cabe no tabuleiro, na posição e direção dadas, sem sair dos limites nem ocupar células ocupadas.

Escreva o código do método putShip, que tenta colocar o navio s no tabuleiro (modificando board e ships) e retorna true ou false, consoante tenha sido possível ou não.

Solução
bool Board::putShip(const Ship &s)
{
  if (!canPutShip(s.pos(), s.dir(), s.size()))
    return false;
  if (s.dir() == 'H')
    for (size_t i = 0; i < s.size(); i++)
      board.at(s.pos().lin).at(s.pos().col + i) = s.id();
  else
    for (size_t i = 0; i < s.size(); i++)
      board.at(s.pos().lin + i).at(s.pos().col) = s.id();
  ships.push_back(s);
  return true;
}

Primeiro delega-se a validação em canPutShip. Se passar, preenchem-se as size células a partir do canto superior esquerdo (pos()), na horizontal (col + i) ou na vertical (lin + i), com o identificador do navio, e regista-se o navio em ships. A devolução é true apenas se o navio ficou colocado.

Confere a tua resposta:

  • O método chama canPutShip com a posição, direção e tamanho do navio e retorna false se não for possível.
  • Se for possível, preenche as células do navio em board com o identificador do navio, na horizontal ou na vertical consoante dir().
  • O navio é acrescentado a ships e o método retorna true.

Herança e polimorfismo

Saída de um programa com funções virtuais e cópia por valorAutoavaliação

Exame, 2017/18

Considere as classes e o programa seguintes:

class Base {
public:
  Base(int a) { _a = a; }
  virtual void show() const { cout << _a; }
protected:
  int _a;
};

class Derived : public Base {
public:
  Derived(int a, int b) : Base(a) { _b = b; }
  void show() const { cout << _a << ' ' << _b; }
private:
  int _b;
};

int main()
{
  Derived d(2, 3);
  d.show();
  cout << endl;
  Base b = d;
  b.show();
  cout << endl;
  Base *pb = &d;
  pb->show();
  cout << endl;
}

(a) Qual a saída do programa? (b) Qual seria a saída se o qualificador virtual fosse retirado de show() na classe Base? (c) O que acontece se protected em Base for substituído por private? Justifique.

Solução

(a) Com virtual: 23, 2, 23. (b) Sem virtual na base: 23, 2, 2. (c) Com private em vez de protected, erro de compilação.

d.show() chama o método de Derived (23). A atribuição Base b = d; copia apenas a parte base do objeto (slicing), pelo que b.show() chama Base::show e imprime 2. A chamada pb->show() é dinâmica por causa de virtual, apesar de o apontador ser de Base, e imprime 23. Sem virtual, essa chamada resolve estaticamente para Base::show, imprimindo 2. Se _a fosse private, a classe Derived não poderia aceder-lhe no seu próprio show(), pelo que o código nem compilava.

Confere a tua resposta:

  • A saída com virtual é 23, 2, 23 (um valor por linha).
  • Sem virtual, a terceira chamada mostra apenas 2.
  • Com private, o programa não compila, porque Derived deixaria de aceder a _a.
Hierarquia Vehicle, Car e Bike com displayInfoAutoavaliação

Exame de Recurso, 2023/24

Considere a classe base Vehicle, num ficheiro de cabeçalho:

class Vehicle {
protected:
  std::string make;   // marca
  std::string model;
  int year;
public:
  Vehicle(std::string make, std::string model, int year)
      : make(make), model(model), year(year) {}
  virtual void displayInfo() const {
    std::cout << make << " " << model << " " << year << " ";
  }
};

Escreva a implementação das classes Car e Bike, que herdam de Vehicle e acrescentam, respetivamente, o atributo e método int numberOfDoors() const e o atributo e método bool isElectric() const. Ambas devem sobrepor o método displayInfo() para mostrar também a sua informação específica, para além da informação base.

Solução
class Car : public Vehicle {
public:
  Car(std::string make, std::string model, int year, int numberOfDoors)
      : Vehicle(make, model, year), numberOfDoors_(numberOfDoors) {}
  int numberOfDoors() const { return numberOfDoors_; }
  void displayInfo() const override {
    Vehicle::displayInfo();
    std::cout << numberOfDoors_ << " doors";
  }
private:
  int numberOfDoors_;
};

class Bike : public Vehicle {
public:
  Bike(std::string make, std::string model, int year, bool isElectric)
      : Vehicle(make, model, year), isElectric_(isElectric) {}
  bool isElectric() const { return isElectric_; }
  void displayInfo() const override {
    Vehicle::displayInfo();
    std::cout << (isElectric_ ? "electric" : "muscular");
  }
private:
  bool isElectric_;
};

Cada subclasse chama o construtor da base com Vehicle(make, model, year) na lista de inicialização e guarda o atributo próprio. O método displayInfo() sobreposto chama primeiro Vehicle::displayInfo() para reutilizar a saída base e depois acrescenta a informação específica, com override para o compilador confirmar a sobreposição do método virtual.

Confere a tua resposta:

  • Car e Bike herdam publicamente de Vehicle e chamam o construtor da base.
  • Cada uma guarda o seu atributo extra (numberOfDoors_ / isElectric_) e expõe o método de acesso pedido.
  • Ambas sobrepõem displayInfo() chamando Vehicle::displayInfo() e acrescentando a informação específica.

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.