Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add exercise for pointer concept #825

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,18 @@
"references",
"headers"
]
},
{
"slug": "speedywagon",
"name": "Speedywagon Foundation",
"uuid": "ed2148a5-674c-4660-a050-b11ab240b876",
"concepts": [
"pointers"
],
"prerequisites": [
"classes",
"references"
]
}
],
"practice": [
Expand Down
46 changes: 46 additions & 0 deletions exercises/concept/speedywagon/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Hints

## General

- Pay close attention to pointer syntax.
You will be using `*` (dereference operator) and `->` (member access through pointer).
- Make sure to check for null pointers (`nullptr`) where necessary to avoid accessing invalid memory.
- You can run the tests even if your code isn't complete; a rough structure is enough to see test results.
- All functions used by the tests must be declared in the header file.
- If your program crashes or behaves unexpectedly, it's often due to null pointer dereferencing.
Double-check that you handle `nullptr` properly.
- When using pointer arithmetic, ensure that you stay within the bounds of the sensor array.
Going beyond the array's capacity can lead to memory issues.

## 1. Check Sensor Connection (`connection_check`)

- The task is mainly about verifying whether a pointer is null or not.
- Use the comparison operator `!=` to check if a pointer is valid.
- If you're unsure whether you're checking the pointer correctly, think about what `nullptr` represents (the absence of a valid memory address).


## 2. Count Activity of Sensors (`activity_counter`)

- You need to iterate over the array of sensors.
An array in C++ can be treated as a pointer to its first element.
- Use pointer arithmetic (`sensor_array + i`) to access the sensor at index `i`.
- The `->` operator is used to access a member of the struct through a pointer.

### Example

```cpp
int sum = (sensor_array + i)->activity; // Access activity using pointer arithmetic
```

## 3. Alarm Control (`alarm_control`)

- First, check if the pointer is null before accessing the sensor.
- Use the `->` operator to access the `activity` member of the `pillar_men_sensor` struct.
- Think carefully about what should happen if the sensor's activity level is `0`.
Should the alarm trigger?

## 4. Checking the data for anomalies with the `uv_alarm` function

- Use the `&` operator to pass a pointer to the sensor's data array into the `uv_light_heuristic` function.
- Ensure you correctly check for a null pointer before accessing the sensor's data.
- Compare the result of `uv_light_heuristic` with the sensor's `activity` value to determine if the alarm should trigger.
105 changes: 105 additions & 0 deletions exercises/concept/speedywagon/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Instructions

Welcome, Engineer!
You are one of the last veterans of the Speedywagon Foundation, a secret organization that, for decades, has been battling ancient threats like the Pillar Men.
In the course of this effort, you've spent years maintaining the Foundation's technological systems, built using a mix of cutting-edge tech and aging libraries.

However, in recent times, the sensors that track Pillar Men activities are malfunctioning.
The Foundation's systems are old, and the code interacts with a legacy C++ library that cannot be updated.
Your task is to implement four core functions that monitor Pillar Men sensor activity using an old-fashioned pointer-based library.

The Foundation's operations rely on you.

## 0. The Sensor Environment (`pillar_men_sensor`)

As sensor readings can be huge, we supply a mockup _struct_ that is used in the actual library.
The code has already been implemented in the header file for you.

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

## 1. Check Sensor Connection (`connection_check`)

Your first task is to ensure that the Pillar Men sensor is connected properly.
We can't have false alarms triggered by disconnected sensors.
You will write a function `connection_check`, which tests if the sensor's pointer is valid by checking for `nullptr`.

### Task

- Define a function that accepts a pointer a a `pillar_men_sensor` _struct_.
- The function should return `true` if the sensor pointer is not null, and `false` otherwise.

### Example

```cpp
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
```

## 2. Count Activity of Sensors (`activity_counter`)

Pillar Men are lurking in the shadows, and we need to know if sensors have detected any activity.
You will write the `activity_counter` function, which takes in an array of sensors and a capacity indicating the number of sensors in the array.

### Task

- Define a function that accepts a pointer to the first element of an array and the arrays capacity.
- Use pointer arithmetic to loop through the sensor array and accumulate the activity readings.
- Return the accumulated activity.

### Example

```cpp
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
```

## 3. Alarm Control (`alarm_control`)

Not every sensor should trigger an alarm unless there’s real danger.
The `alarm_control` function ensures that a sensor only triggers an alarm if its activity level is greater than 0.
This function should also check for null sensors to prevent system crashes.

### Task

- Define a function that accepts the pointer to a `pillar_men_sensor`.
- The function should first check for a `nullptr` sensor. If the sensor is `nullptr`, return `false`.
- If the sensor is valid and its activity is greater than 0, return `true`; otherwise, return `false`.

### Example

```cpp
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
```

## 4. Checking the data for anomalies with the `uv_alarm` function

In this task, you will implement the `uv_alarm` function to determine whether an alarm should be triggered based on UV light exposure levels and sensor activity.
The `uv_alarm` function should use the provided `uv_light_heuristic` function, which operates on a vector of data and returns a value based on certain thresholds.
This is a mockup version of the complex code that will run during production, please don't change the interface.

### Task

Define the `uv_alarm` function in the `speedywagon` namespace. It should:

- Take a pointer to a `pillar_men_sensor` _struct_ as its parameter.
- Return `false` if the sensor pointer is null.
- Call the `uv_light_heuristic` function, passing the address of the sensor's `data` array.
- Return `true` if the value returned by `uv_light_heuristic` is greater than the `sensor->activity` level, otherwise return `false`.

## Wrapping Up

You’ve been entrusted with an essential task for the Speedywagon Foundation.
By testing for valid sensor connections, counting activity, and implementing alarm controls, you’ve ensured that the Foundation's battle against the Pillar Men can continue uninterrupted.

As a modern C++ engineer, you’d prefer using smart pointers, but alas, legacy code demands respect for the old ways.
The fate of humanity may rest on these pointers, so proceed carefully, and may the Hamon energy guide you.
99 changes: 99 additions & 0 deletions exercises/concept/speedywagon/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Introduction

Like many other languages, C++ has _pointers_.
You already know _references_ and _pointers_ are similar, but think of them as a level closer to the inner workings of your computer.
_Pointers_ are variables that hold object addresses.
They are used to directly interact with objects, enabling dynamic memory allocation and efficient data manipulation in C++.

If you're new to _pointers_, they can feel a little mysterious but once you get used to them, they're quite straight-forward.

They're a crucial part of C++, so take some time to really understand them.
The bare-bone version in this concept is also called _dumb pointer_ or _raw pointer_.
With modern C++ there are also _smart pointers_, the basic type is not smart at all and you have to handle all the work manually.

Before digging into the details, it's worth understanding the use of _pointers_.
_Pointers_ are a way to share an object's address with other parts of our program, which is useful for two major reasons:
1. Like _references_, pointers avoid copies and help to reduce the resource-footprint of your program.
2. Unlike _references_, pointers can be reassigned to different objects.
3. Pointers can also point to a null value, to indicate, that they currently do not point to any object.

## General Syntax

A pointer declaration in C++ involves specifying the data type to which the the pointer is pointing, followed by an asterisk (`*`) and the pointer's name.
When pointers are declared, they are not automatically initialized.
Without explicit assignment, a pointer typically holds an indeterminate value, often referred to as a "garbage address."
While certain compilers might initialize pointers to `nullptr`, this behavior is not guaranteed across all compilers, so it's essential not to rely on it.
It's best practice to explicitly initialize raw pointers and verify their non-null status before utilization to avoid potential issues.

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

To assign the address of a variable to a pointer, you use the address-of operator (`&`).
Dereferencing a pointer is done using the _indirection operator_ (`*`) operator.

```cpp
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};
```

Attention: dereferencing has to be done explicitly, while _references_ just worked like an alias.

## Pointer Arithmetic

_Pointer arithmetic_ allows you to perform arithmetic operations on pointers, which is particularly useful when working with arrays.
Adding an integer to a pointer makes it point to a different element.

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

~~~~exercism/caution
Pointer arithmetic in C++ can easily lead to __undefined behavior__ if not handled carefully.
Undefined behavior can manifest in unexpected program outcomes, crashes, or even security vulnerabilities.
One infamous example of the consequences of undefined behavior occurred in the [explosion of the Ariane 5 rocket][ariane-flight-v88] in 1996, where a software exception caused by the conversion of a 64-bit floating-point number to a 16-bit signed integer led to a catastrophic failure.
~~~~

## Accessing member variables

In C++, the `->` operator is used to access members of an object through a pointer to that object.
It is a shorthand which simplifies accessing members of objects pointed to by pointers.
For instance, if `ptr` is a pointer to an object with a member variable `x`, instead of using `(*ptr).x`, you can directly use `ptr->x`.
This operator enhances code readability and reduces verbosity when working with pointers to objects.

Here's a brief example, with a _struct_ `Superhero` that has a member variable `superpower`.
The main function creates a pointer `dianaPrince` to a `Superhero` object (representing Wonder Woman).
The `->` operator is used to access the member variable `superpower`, showcasing Wonder Woman's iconic "Lasso of Truth."

```cpp
struct Superhero {
std::string superpower;
};

Superhero* dianaPrince = new Superhero;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;
// Memory cleanup:
delete dianaPrince;
```

## Pointers vs. references

Pointers and references both enable indirect access to objects, but they differ in their capabilities and safety considerations.
Pointers offer the flexibility of changing their target object and can be assigned null.
However, this flexibility introduces risks, such as dereferencing null pointers or creating dangling pointers.
References, on the other hand, cannot be null and are bound to valid objects upon creation, avoiding these risks.
Given their safer nature, references should be preferred over pointers unless the additional functionalities provided by pointers are necessary.

[ariane-flight-v88]: https://en.wikipedia.org/wiki/Ariane_flight_V88
3 changes: 3 additions & 0 deletions exercises/concept/speedywagon/.docs/introduction.md.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

%{concept:pointers}
20 changes: 20 additions & 0 deletions exercises/concept/speedywagon/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authors": [
"vaeng"
],
"files": {
"solution": [
"speedywagon.cpp",
"speedywagon.h"
],
"test": [
"speedywagon_test.cpp"
],
"exemplar": [
".meta/exemplar.cpp",
".meta/exemplar.h"
]
},
"forked_from": [],
"blurb": "Learn about pointers by writing wrappers for an old library."
}
25 changes: 25 additions & 0 deletions exercises/concept/speedywagon/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Design

## Goal

The goal of this exercise is to teach the basics of pointers and how they work in C++.

## Learning objectives

- Know how to create a pointer from a variable
- Know how to work with pointers to structs
- Know how to work with array pointers

## Out of scope

- Smart pointers

## Concepts

The Concepts this exercise unlocks are:

- `pointers`: how to work with pointers;

## Prerequisites

- `classes`
38 changes: 38 additions & 0 deletions exercises/concept/speedywagon/.meta/exemplar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "speedywagon.h"

namespace speedywagon {

int uv_light_heuristic(std::vector<int>* data_array) {
double avg{};
for (auto element : *data_array) {
avg += element;
}
avg /= data_array->size();
int uv_index{};
for (auto element : *data_array) {
if (element > avg) ++uv_index;
}
return uv_index;
}

bool connection_check(pillar_men_sensor* sensor) { return sensor != nullptr; }

int activity_counter(pillar_men_sensor* sensor_array, int capacity) {
int sum{};
for (int i{}; i < capacity; ++i) {
sum += (sensor_array + i)->activity;
}
return sum;
}

bool alarm_control(pillar_men_sensor* sensor) {
if (sensor == nullptr) return false;
return sensor->activity > 0;
}

bool uv_alarm(pillar_men_sensor* sensor) {
if (sensor == nullptr) return false;
return uv_light_heuristic(&(sensor->data)) > sensor->activity;
}

} // namespace speedywagon
24 changes: 24 additions & 0 deletions exercises/concept/speedywagon/.meta/exemplar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include <string>
#include <vector>

namespace speedywagon {

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

int uv_light_heuristic(std::vector<int>* data_array);

bool connection_check(pillar_men_sensor* sensor);

int activity_counter(pillar_men_sensor* sensor_array, int capacity);

bool alarm_control(pillar_men_sensor* sensor);

bool uv_alarm(pillar_men_sensor* sensor);

} // namespace speedywagon
Loading