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

Fundação Speedywagon

Exercício de aprendizagem

Introdução

Assim como muitas outras linguagens, o C++ tem ponteiros. Você já sabe que referências e ponteiros são parecidos, mas pense neles como um nível mais próximo do funcionamento interno do seu computador. Os ponteiros são variáveis que guardam endereços de objetos. Eles são usados para interagir diretamente com objetos, permitindo a alocação dinâmica de memória e a manipulação eficiente de dados em C++.

Se você está começando com ponteiros, eles podem parecer um pouco misteriosos, mas, depois que você se acostuma, são bem simples.

Eles são uma parte crucial do C++, então reserve um tempo para entendê-los de verdade. A versão mais básica neste conceito também é chamada de ponteiro burro ou ponteiro bruto. Com o C++ moderno, também existem os ponteiros inteligentes; o tipo básico não é nada inteligente e você precisa cuidar de todo o trabalho manualmente.

Antes de entrar nos detalhes, vale a pena entender para que servem os ponteiros. Os ponteiros são uma forma de compartilhar o endereço de um objeto com outras partes do nosso programa, o que é útil por dois motivos principais:

  1. Assim como as referências, os ponteiros evitam cópias e ajudam a reduzir o consumo de recursos do seu programa.
  2. Diferentemente das referências, os ponteiros podem ser reatribuídos a objetos diferentes.
  3. Os ponteiros também podem apontar para um valor nulo, indicando que no momento não apontam para nenhum objeto.

Sintaxe geral

A declaração de um ponteiro em C++ envolve especificar o tipo de dado para o qual o ponteiro aponta, seguido de um asterisco (*) e do nome do ponteiro. Quando um ponteiro é declarado, ele não é inicializado automaticamente. Sem uma atribuição explícita, um ponteiro costuma guardar um valor indeterminado, muitas vezes chamado de "endereço de lixo". Embora alguns compiladores possam inicializar ponteiros com nullptr, esse comportamento não é garantido em todos os compiladores, então é essencial não contar com ele. A boa prática é inicializar explicitamente os ponteiros brutos e verificar se não são nulos antes de usá-los, para evitar possíveis problemas.

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

Para atribuir o endereço de uma variável a um ponteiro, você usa 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 precisa ser feita explicitamente, enquanto as referências funcionavam apenas como um alias.

Aritmética de ponteiros

A aritmética de ponteiros permite realizar operações aritméticas com ponteiros, o que é especialmente útil ao trabalhar com arrays. Somar um número inteiro a um ponteiro faz com que ele 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 se manifestar em resultados inesperados do programa, travamentos ou até vulnerabilidades de segurança. Um exemplo famoso das consequências do comportamento indefinido ocorreu na explosão do foguete Ariane 5 em 1996, quando uma exceção de software causada pela conversão de um número de ponto flutuante de 64 bits para um inteiro com sinal de 16 bits levou a uma falha catastrófica.

Acessando variáveis membro

Em C++, o operador -> é usado para acessar os membros de um objeto por meio de um ponteiro para esse objeto. É uma forma abreviada que simplifica o acesso aos membros de objetos apontados por ponteiros. Por exemplo, se ptr é um ponteiro para um objeto com uma variável membro x, em vez de usar (*ptr).x, você pode usar diretamente ptr->x. Esse operador melhora a legibilidade do código e reduz a verbosidade ao trabalhar com ponteiros para objetos.

Veja um exemplo breve, 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 acessar a variável membro superpower, mostrando o icônico "Laço da Verdade" 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

Ponteiros e referências permitem o acesso indireto a objetos, mas diferem em suas capacidades e nas considerações de segurança. Os ponteiros oferecem a flexibilidade de mudar o objeto para o qual apontam e podem receber null. No entanto, essa flexibilidade traz riscos, como desreferenciar ponteiros nulos ou criar ponteiros pendentes. As referências, por outro lado, não podem ser nulas e são vinculadas a objetos válidos no momento da criação, evitando esses riscos. Como são mais seguras, as referências devem ser preferidas aos ponteiros, a menos que as funcionalidades adicionais oferecidas pelos ponteiros sejam necessárias.

Instruções

Bem-vindo, Engenheiro! Você é um dos últimos veteranos da Fundação Speedywagon, uma organização secreta que, por décadas, vem combatendo ameaças antigas como os Pillar Men. No curso desse esforço, você passou anos mantendo 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 rastreiam as atividades dos Pillar Men estão com defeito. Os sistemas da Fundação são antigos, e o código interage com uma biblioteca legada de C++ que não pode ser atualizada. Sua tarefa é implementar quatro funções centrais que monitoram a atividade dos sensores de Pillar Men usando uma biblioteca antiquada baseada em ponteiros.

Como engenheiro de C++ moderno, você preferiria usar ponteiros inteligentes, mas, infelizmente, o código legado exige respeito aos velhos costumes. O destino da humanidade pode depender desses ponteiros, então prossiga com cuidado, e que a energia Hamon guie você.

Note

Como as leituras dos sensores podem ser enormes, fornecemos uma struct simulada que é usada na biblioteca real. O código já foi implementado no arquivo de cabeçalho para você.

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

1. Verificar a conexão do sensor (connection_check)

Sua primeira tarefa é garantir que o sensor de Pillar Men esteja conectado corretamente. Não podemos ter alarmes falsos disparados por sensores desconectados. Você vai escrever uma função connection_check, que testa se o ponteiro do sensor é válido verificando se ele é nullptr.

  • Defina uma função que aceita um ponteiro para uma struct pillar_men_sensor.
  • A função deve retornar 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. Contar a atividade dos sensores (activity_counter)

Os Pillar Men estão à espreita nas sombras, e precisamos saber se os sensores detectaram alguma atividade. Você vai escrever a função activity_counter, que recebe um array de sensores e uma capacidade que indica o número de sensores no array.

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

3. Controle do alarme (alarm_control)

Nem todo sensor deve disparar um alarme, a menos que haja perigo real. A função alarm_control garante que um sensor só dispare um alarme se o nível de atividade dele for maior que 0. Essa função também deve verificar se há sensores nulos para evitar travamentos do sistema.

  • Defina 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, retorne false.
  • Se o sensor for válido e a atividade dele for maior que 0, retorne true; caso contrário, retorne 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, você vai implementar a função uv_alarm para determinar se um alarme deve ser disparado com base nos níveis de exposição à luz UV e na atividade do sensor. A função uv_alarm deve usar a função uv_light_heuristic fornecida, que opera sobre um vetor de dados e retorna um valor com base em certos limites. Esta é uma versão simulada do código complexo que vai rodar em produção; por favor, não altere a interface.

Defina a função uv_alarm no namespace speedywagon. Ela deve:

  • Receber um ponteiro para uma struct pillar_men_sensor como parâmetro.
  • Retornar false se o ponteiro do sensor for nulo.
  • Chamar a função uv_light_heuristic, passando o endereço do array data do sensor.
  • Retornar true se o valor retornado por uv_light_heuristic for maior que o nível sensor->activity, caso contrário, retornar false.
Editar via GitHub O link abre em uma nova janela ou aba
C++ Exercism

Tudo pronto para começar Fundação Speedywagon?

Crie sua conta no Exercism para aprender e dominar C++ com 19 conceitos100 exercícios e mentoria humana de verdade, tudo de graça.