Percursos
/
C++
C++
/
Exercícios
/
Fundação Speedywagon
Fundação Speedywagon

Fundação Speedywagon

Exercício de aprendizagem

Introdução

Como muitas outras linguagens, C++ tem ponteiros. Já sabes que referências e ponteiros são semelhantes, mas pensa neles como um nível mais próximo do funcionamento interno do teu computador. Os ponteiros são variáveis que guardam endereços de objetos. São usados para interagir diretamente com objetos, permitindo alocação dinâmica de memória e manipulação eficiente de dados em C++.

Se estás a começar com ponteiros, podem parecer um pouco misteriosos, mas depois de te habituares a eles, tornam-se bastante simples.

São uma parte crucial do C++, por isso dedica algum tempo a compreendê-los realmente. A versão mais básica neste conceito também é chamada de ponteiro simples ou ponteiro bruto. Com o C++ moderno também existem ponteiros inteligentes; o tipo básico não é nada inteligente e tens de tratar de todo o trabalho manualmente.

Antes de aprofundares os detalhes, vale a pena compreender a utilização dos ponteiros. Os ponteiros são uma forma de partilhar o endereço de um objeto com outras partes do nosso programa, o que é útil por duas razões principais:

  1. Tal como as referências, os ponteiros evitam cópias e ajudam a reduzir a pegada de recursos do teu programa.
  2. Ao contrário das referências, os ponteiros podem ser reatribuídos a objetos diferentes.
  3. Os ponteiros também podem apontar para um valor nulo, para indicar que atualmente não apontam para nenhum objeto.

Sintaxe geral

Uma declaração de ponteiro em C++ envolve especificar o tipo de dados para o qual o ponteiro aponta, seguido de um asterisco (*) e do nome do ponteiro. Quando os ponteiros são declarados, não são inicializados automaticamente. Sem uma atribuição explícita, um ponteiro contém normalmente um valor indeterminado, muitas vezes designado por "endereço de lixo". Embora alguns compiladores possam inicializar ponteiros com nullptr, esse comportamento não é garantido em todos os compiladores, por isso é essencial não contar com isso. É boa prática inicializar explicitamente os ponteiros brutos e verificar se não são nulos antes de os utilizares, para evitares potenciais problemas.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Para atribuíres o endereço de uma variável a um ponteiro, usas o operador de endereço (&). A desreferenciação de um ponteiro é feita com o operador de indireção (*).

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Atenção: a desreferenciação tem de ser feita explicitamente, ao passo que as referências funcionavam simplesmente como um alias.

Aritmética de ponteiros

A aritmética de ponteiros permite efetuar operações aritméticas com ponteiros, o que é particularmente útil quando trabalhas com arrays. Somar um número inteiro a um ponteiro faz com que aponte para um elemento diferente.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

A aritmética de ponteiros em C++ pode facilmente levar a comportamento indefinido se não for tratada com cuidado. O comportamento indefinido pode manifestar-se em resultados inesperados do programa, falhas ou até vulnerabilidades de segurança. Um exemplo infame das consequências do comportamento indefinido ocorreu na explosão do foguetão Ariane 5 em 1996, em que uma exceção de software causada pela conversão de um número de vírgula flutuante de 64 bits para um número inteiro com sinal de 16 bits levou a uma falha catastrófica.

Acesso a variáveis membro

Em C++, o operador -> é usado para aceder aos membros de um objeto através de um ponteiro para esse objeto. É uma forma abreviada que simplifica o acesso aos membros de objetos apontados por ponteiros. Por exemplo, se ptr for um ponteiro para um objeto com uma variável membro x, em vez de usares (*ptr).x, podes usar diretamente ptr->x. Este operador melhora a legibilidade do código e reduz a verbosidade quando trabalhas com ponteiros para objetos.

Eis um breve exemplo, com uma struct Superhero que tem uma variável membro superpower. A função main cria um ponteiro dianaPrince para um objeto Superhero (que representa a Mulher-Maravilha). O operador -> é usado para aceder à variável membro superpower, mostrando o icónico "Lasso of Truth" da Mulher-Maravilha.

struct Superhero {
    std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

Ponteiros vs. referências

Os ponteiros e as referências permitem ambos o acesso indireto a objetos, mas diferem nas suas capacidades e considerações de segurança. Os ponteiros oferecem a flexibilidade de alterar o objeto de destino e podem receber o valor nulo. No entanto, essa flexibilidade introduz riscos, como desreferenciar ponteiros nulos ou criar ponteiros pendentes. As referências, por outro lado, não podem ser nulas e ficam associadas a objetos válidos aquando da criação, evitando estes riscos. Dada a sua natureza mais segura, deve dar-se preferência às referências em vez dos ponteiros, a menos que as funcionalidades adicionais proporcionadas pelos ponteiros sejam necessárias.

Instruções

Bem-vindo, Engenheiro! És um dos últimos veteranos da Fundação Speedywagon, uma organização secreta que, durante décadas, tem combatido ameaças antigas como os Pillar Men. No decorrer deste esforço, passaste anos a manter os sistemas tecnológicos da Fundação, construídos com uma mistura de tecnologia de ponta e bibliotecas antigas.

No entanto, recentemente, os sensores que monitorizam as atividades dos Pillar Men estão a funcionar mal. Os sistemas da Fundação são antigos, e o código interage com uma biblioteca C++ legada que não pode ser atualizada. A tua tarefa é implementar quatro funções principais que monitorizam a atividade dos sensores de Pillar Men usando uma biblioteca antiquada baseada em ponteiros.

Enquanto engenheiro de C++ moderno, preferirias usar ponteiros inteligentes, mas, infelizmente, o código legado exige respeito pelos velhos costumes. O destino da humanidade pode depender destes ponteiros, por isso avança com cuidado, e que a energia Hamon te guie.

Note

Como as leituras dos sensores podem ser enormes, fornecemos uma struct de simulação que é usada na biblioteca real. O código já foi implementado para ti no ficheiro de cabeçalho.

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. Verifica a ligação do sensor (connection_check)

A tua primeira tarefa é garantir que o sensor de Pillar Men está ligado corretamente. Não podemos ter falsos alarmes desencadeados por sensores desligados. Vais escrever uma função connection_check, que testa se o ponteiro do sensor é válido, verificando se é nullptr.

  • Define uma função que aceita um ponteiro para uma struct pillar_men_sensor.
  • A função deve devolver true se o ponteiro do sensor não for nulo e false caso contrário.
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. Conta a atividade dos sensores (activity_counter)

Os Pillar Men espreitam nas sombras e precisamos de saber se os sensores detetaram alguma atividade. Vais escrever a função activity_counter, que recebe um array de sensores e uma capacidade que indica o número de sensores no array.

  • Define uma função que aceita um ponteiro para o primeiro elemento de um array e a capacidade do array.
  • Usa aritmética de ponteiros para percorrer o array de sensores e acumular as leituras de atividade.
  • Devolve a atividade acumulada.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. Controlo do alarme (alarm_control)

Nem todos os sensores devem desencadear um alarme, a não ser que haja perigo real. A função alarm_control garante que um sensor só desencadeia um alarme se o seu nível de atividade for superior a 0. Esta função também deve verificar se há sensores nulos para evitar falhas no sistema.

  • Define uma função que aceita o ponteiro para um pillar_men_sensor.
  • A função deve primeiro verificar se o sensor é nullptr. Se o sensor for nullptr, devolve false.
  • Se o sensor for válido e a sua atividade for superior a 0, devolve true; caso contrário, devolve false.
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. Verificar anomalias nos dados com a função uv_alarm

Nesta tarefa, vais implementar a função uv_alarm para determinar se um alarme deve ser desencadeado com base nos níveis de exposição à luz UV e na atividade dos sensores. A função uv_alarm deve usar a função uv_light_heuristic fornecida, que opera sobre um vetor de dados e devolve um valor com base em determinados limiares. Esta é uma versão de simulação do código complexo que será executado em produção; não alteres a interface, por favor.

Define a função uv_alarm no namespace speedywagon. Esta deve:

  • Receber um ponteiro para uma struct pillar_men_sensor como parâmetro.
  • Devolver false se o ponteiro do sensor for nulo.
  • Chamar a função uv_light_heuristic, passando o endereço do array data do sensor.
  • Devolver true se o valor devolvido por uv_light_heuristic for superior ao nível de sensor->activity; caso contrário, devolver false.
Editar via GitHub A ligação abre numa nova janela ou separador
C++ Exercism

Estás pronto para começar Fundação Speedywagon?

Inscreve-te no Exercism para aprenderes e dominares C++ com 19 conceitos100 exercícios, e mentoria humana real, tudo grátis.