Exam Question Pattern 1
Pointers, dynamic arrays and template classes: a complete C++ examination revision guide based on recurring question patterns
Introduction
Examination questions often change their data and function names while continuing to test the same underlying programming skills. Across the recurring 2023, 2024 and 2025 patterns considered here, the central area remains consistent: pointers, dynamically allocated arrays, array-processing functions and classes.
The objective is therefore not to memorise three isolated programs. It is to recognise the pattern, understand what each function must do and write a correct solution even when the values, types or operation names change.
This guide concentrates on the techniques most likely to earn marks:
- declaring and using pointers correctly
- allocating and releasing dynamic arrays with
new[]anddelete[] - passing arrays and their sizes to functions
- displaying arrays in an exact required format
- calculating totals and averages
- counting occurrences and locating values
- finding maximum and minimum values
- writing class templates with constructors, getters and member functions
- handling ratio conversion and inversion correctly
The Recurring Examination Pattern
| Year | Question pattern | Skills being assessed |
|---|---|---|
| 2023 | Template class Ratio<T> | Class templates, constructor, conversion, inversion and getters |
| 2024 | Dynamic float array | Display, average of first n values, occurrence count and maximum |
| 2025 | Dynamic int array | Exact display formatting, total and linear search |
The questions are related because each one expects the student to construct a small, well-organised program from reusable building blocks.
Part 1: Pointer Foundations
What does a pointer store?
A pointer stores the memory address of another object. The address-of operator & obtains an object’s address, while the dereference operator * accesses the object stored at that address.
int number = 10;
int* pointer = &number;
std::cout << number << '\n'; // 10
std::cout << *pointer << '\n'; // 10
| Expression | Meaning |
|---|---|
| int* pointer | Declares a pointer to an int |
| &number | Obtains the address of number |
| pointer = &number | Stores that address in pointer |
| *pointer | Accesses the integer stored at that address |
Pointers and arrays
When an array is used in most expressions, it provides a pointer to its first element. This is why array indexing and pointer arithmetic are closely related.
int values[]{10, 20, 30};
int* pointer = values;
std::cout << values[1] << '\n'; // 20
std::cout << pointer[1] << '\n'; // 20
std::cout << *(pointer + 1) << '\n'; // 20
values[index], pointer[index] and *(pointer + index) access the same element when the pointer refers to the first element of the array.
Read-only array parameters
A function that only reads an array should normally receive a pointer to const data:
void display(const int* array, std::size_t size)
{
// The elements may be read but not modified.
}
This protects the caller’s data and clearly communicates that the function will not alter the array.
Part 2: Dynamic Arrays
Allocation and deallocation
A dynamic array is created while the program is running. The new[] expression allocates the storage and returns a pointer to its first element.
constexpr std::size_t size = 6;
int* values = new int[size]{6, 5, 4, 3, 2, 1};
// Use the array here.
delete[] values;
values = nullptr;
| Allocation | Required deallocation |
|---|---|
| new int{42} | delete pointer; |
| new int[size] | delete[] pointer; |
new must match delete, while new[] must match delete[]. Using delete for an array produces undefined behaviour.
Why must the size be passed separately?
A raw pointer does not contain information about the number of elements in a dynamic array. Every function that processes the array must therefore receive its size separately.
void display(const int* array, std::size_t size);
int total(const int* array, std::size_t size);
The standard traversal pattern
for (std::size_t index = 0; index < size; ++index)
{
// Process array[index].
}
size - 1. The loop condition must therefore be index < size, not index <= size.
Dynamic-array errors to avoid
| Error | Why it is wrong |
|---|---|
| Dereferencing nullptr | There is no valid object at the address. |
| Using an uninitialised pointer | The pointer contains an indeterminate address. |
| Accessing array[size] | This is one element beyond the valid range. |
| Forgetting delete[] | The allocated memory is leaked. |
| Using a pointer after delete[] | The pointer is dangling and no longer refers to a valid array. |
Part 3: The 2025 Dynamic Integer Array Pattern
The recurring 2025 pattern uses the dynamically allocated integer array {6, 5, 4, 3, 2, 1}. The expected operations are:
- display the values as
[6, 5, 4, 3, 2, 1] - calculate the total
- locate a target value and report whether it was found
Function design
| Function | Responsibility | Return type |
|---|---|---|
| display | Print every element using the exact required formatting | void |
| total | Add every element | int |
| locate | Return the matching index or a not-found value | int |
Complete model solution
#include <cstddef>
#include <iostream>
void display(const int* array, std::size_t size)
{
std::cout << '[';
for (std::size_t index = 0; index < size; ++index)
{
std::cout << array[index];
if (index + 1 < size)
{
std::cout << ", ";
}
}
std::cout << "]\n";
}
int total(const int* array, std::size_t size)
{
int result = 0;
for (std::size_t index = 0; index < size; ++index)
{
result += array[index];
}
return result;
}
int locate(const int* array, std::size_t size, int target)
{
for (std::size_t index = 0; index < size; ++index)
{
if (array[index] == target)
{
return static_cast<int>(index);
}
}
return -1;
}
int main()
{
constexpr std::size_t size = 6;
int* values = new int[size]{6, 5, 4, 3, 2, 1};
display(values, size);
std::cout << "Total: " << total(values, size) << '\n';
const int target = 4;
const int position = locate(values, size, target);
if (position != -1)
{
std::cout << target << " found at index "
<< position << '\n';
}
else
{
std::cout << target << " not found\n";
}
delete[] values;
values = nullptr;
return 0;
}
Expected output
[6, 5, 4, 3, 2, 1]
Total: 21
4 found at index 2
How the display formatting works
The separator is printed only when another element follows. This prevents an unwanted trailing comma.
if (index + 1 < size)
{
std::cout << ", ";
}
| Required | Incorrect |
|---|---|
| [6, 5, 4, 3, 2, 1] | [6, 5, 4, 3, 2, 1, ] |
How the total is calculated
The accumulator starts at zero because zero is the additive identity. Each value is added exactly once.
int result = 0;
for (std::size_t index = 0; index < size; ++index)
{
result += array[index];
}
For the supplied array:
6 + 5 + 4 + 3 + 2 + 1 = 21
How linear search works
The search starts at index 0 and inspects one element at a time. It returns immediately when it finds a match. If the loop finishes without finding the target, the function returns -1.
| Value | 6 | 5 | 4 | 3 | 2 | 1 |
|---|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 | 5 |
4 is the third element, but its index is 2 because C++ array indices begin at zero.
Likely variations
- search for a value that is not present
- return
boolwhen only found/not found is required - count how many times a target occurs
- find the minimum or maximum value
- read the elements from the user
- display the array in reverse order
Part 4: The 2024 Dynamic Float Array Pattern
The recurring 2024 pattern uses the dynamically allocated array {1.1, 2.2, 3.3, 2.2}. The expected operations are:
- print the complete array
- calculate the average of the first
nvalues - count occurrences of a target value
- find the maximum value
Complete model solution
#include <cstddef>
#include <iostream>
void print(const float* array, std::size_t size)
{
std::cout << '[';
for (std::size_t index = 0; index < size; ++index)
{
std::cout << array[index];
if (index + 1 < size)
{
std::cout << ", ";
}
}
std::cout << "]\n";
}
float average(const float* array,
std::size_t size,
std::size_t count)
{
if (count == 0 || count > size)
{
return 0.0F;
}
float sum = 0.0F;
for (std::size_t index = 0; index < count; ++index)
{
sum += array[index];
}
return sum / static_cast<float>(count);
}
std::size_t countOccurrence(const float* array,
std::size_t size,
float target)
{
std::size_t count = 0;
for (std::size_t index = 0; index < size; ++index)
{
if (array[index] == target)
{
++count;
}
}
return count;
}
float findMax(const float* array, std::size_t size)
{
float maximum = array[0];
for (std::size_t index = 1; index < size; ++index)
{
if (array[index] > maximum)
{
maximum = array[index];
}
}
return maximum;
}
int main()
{
constexpr std::size_t size = 4;
float* values = new float[size]{1.1F, 2.2F, 3.3F, 2.2F};
print(values, size);
std::cout << "Average of first 3: "
<< average(values, size, 3) << '\n';
std::cout << "Occurrences of 2.2: "
<< countOccurrence(values, size, 2.2F) << '\n';
std::cout << "Maximum: " << findMax(values, size) << '\n';
delete[] values;
values = nullptr;
return 0;
}
Expected output
[1.1, 2.2, 3.3, 2.2]
Average of first 3: 2.2
Occurrences of 2.2: 2
Maximum: 3.3
Average of the first n values
The function must add only the first count elements. When count is 3, the loop uses indices 0, 1 and 2.
1.1 + 2.2 + 3.3 = 6.6
6.6 / 3 = 2.2
count must be greater than zero and must not exceed the array size. Division by zero must never be attempted.
Counting occurrences
The entire array must be examined because the target may appear more than once. The counter starts at zero and increases every time an element matches the target.
if (array[index] == target)
{
++count;
}
In the supplied array, 2.2 appears at indices 1 and 3. The function therefore returns 2.
Floating-point comparison
Direct equality is usually accepted in this introductory pattern because the target is taken directly from the supplied data. In general C++ programming, calculated floating-point values may require approximate comparison.
#include <cmath>
bool approximatelyEqual(float left, float right)
{
constexpr float tolerance = 0.0001F;
return std::fabs(left - right) < tolerance;
}
Finding the maximum
The maximum should be initialised from the first element. The loop then compares the remaining elements against the current maximum.
float maximum = array[0];
for (std::size_t index = 1; index < size; ++index)
{
if (array[index] > maximum)
{
maximum = array[index];
}
}
maximum to 0.0F gives the wrong answer when every element is negative.
Likely variations
- calculate the average of the complete array
- read the value of
nfrom the user - find the minimum instead of the maximum
- count values above or below a threshold
- return the index of the maximum value
- validate an empty array or an invalid value of
n
Part 5: The 2023 Template Class Ratio<T> Pattern
The recurring 2023 summary identifies a class template named Ratio<T> containing a constructor, a convert operation, an invert operation and getter functions. The exact signature and meaning of convert must be taken from the original examination question.
The model below uses the most common interpretation: convert a ratio into its decimal value.
Class-template structure
template <typename T>
class Ratio
{
public:
// Public interface
private:
T m_numerator;
T m_denominator;
};
T is a type parameter. The compiler uses the same template to create different class specialisations.
Ratio<int> first{3, 4};
Ratio<double> second{2.5, 5.0};
Complete model solution
#include <iostream>
#include <stdexcept>
#include <utility>
template <typename T>
class Ratio
{
public:
Ratio(T numerator, T denominator)
: m_numerator{numerator},
m_denominator{denominator}
{
if (m_denominator == T{})
{
throw std::invalid_argument{
"The denominator cannot be zero."
};
}
}
double convert() const
{
return static_cast<double>(m_numerator)
/ static_cast<double>(m_denominator);
}
void invert()
{
if (m_numerator == T{})
{
throw std::domain_error{
"A zero numerator cannot be inverted."
};
}
std::swap(m_numerator, m_denominator);
}
T getNumerator() const
{
return m_numerator;
}
T getDenominator() const
{
return m_denominator;
}
private:
T m_numerator;
T m_denominator;
};
int main()
{
Ratio<int> ratio{3, 4};
std::cout << ratio.getNumerator()
<< '/' << ratio.getDenominator() << '\n';
std::cout << ratio.convert() << '\n';
ratio.invert();
std::cout << ratio.getNumerator()
<< '/' << ratio.getDenominator() << '\n';
std::cout << ratio.convert() << '\n';
return 0;
}
Expected output
3/4
0.75
4/3
1.33333
The constructor and member initialiser list
The constructor establishes the numerator and denominator when the object is created. The member initialiser list directly initialises both data members.
Ratio(T numerator, T denominator)
: m_numerator{numerator},
m_denominator{denominator}
{
}
Decimal conversion and integer division
If both operands are integers, C++ performs integer division and discards the fractional part. At least one operand must be converted before division when a decimal result is required.
| Expression | Result |
|---|---|
| 3 / 4 | 0 |
| static_cast<double>(3) / 4 | 0.75 |
The member function is marked const because conversion reads the ratio without changing it.
Inverting a ratio
Inversion swaps the numerator and denominator. The ratio 3/4 therefore becomes 4/3.
void invert()
{
std::swap(m_numerator, m_denominator);
}
A zero numerator must be considered because inverting 0/5 would produce the invalid ratio 5/0.
Getters and encapsulation
Private data members cannot be accessed directly from main(). Getter functions provide read access without allowing external code to place the object into an invalid state.
T getNumerator() const
{
return m_numerator;
}
Alternative invert specification
Some questions require invert() to return a new ratio instead of modifying the current object. In that case, follow the signature stated in the examination paper.
Ratio invert() const
{
return Ratio{m_denominator, m_numerator};
}
Alternative convert specification
If convert means converting Ratio<T> into Ratio<U>, the member itself can be a function template.
template <typename U>
Ratio<U> convert() const
{
return Ratio<U>{
static_cast<U>(m_numerator),
static_cast<U>(m_denominator)
};
}
convert means. Match the return type, parameters and required behaviour shown in the original question or class diagram.
How to Construct the Answer in the Examination
Read the interface carefully
Underline every required class, function, parameter, return type and output format. Do not silently replace the requested interface with your preferred design.
Write the structure first
Add the required headers, function definitions or declarations, and the basic structure of main().
Create the data
Allocate the correct type and number of elements. Check that the initializer contains exactly the required values.
Implement one operation at a time
Write and mentally trace each loop. Confirm its start index, stopping condition, accumulator or comparison variable, and return value.
Call every required function
A correctly written function earns little if it is never called when the question expects a complete program.
Release the memory
Place the matching delete[] after the final use of the dynamic array.
Perform a final trace
Check every loop boundary, array index, return path, bracket, comma, space and line break against the required output.
Mark-Scoring Checklist
| Check | What should be present |
|---|---|
| Allocation | Correct type, new[], size and initial values |
| Function parameters | Pointer plus size, with target or count where required |
| Const-correctness | const T* for functions that only read the array |
| Loop boundary | index < size or index < count |
| Accumulator | Initialised before it is used |
| Search | Correct found and not-found behaviour |
| Maximum or minimum | Initialised from a valid array element |
| Output | Required brackets, commas, spaces and labels |
| Cleanup | delete[] used exactly once after the final access |
| Template class | Template declaration, public interface and private data |
Common Mistakes That Lose Marks
| Mistake | Correction |
|---|---|
| index <= size | Use index < size. |
| Missing size parameter | Pass the array size separately with a raw pointer. |
| delete instead of delete[] | Match new[] with delete[]. |
| Uninitialised total or counter | Start totals and counters at zero. |
| Maximum initialised to zero | Initialise it from array[0]. |
| Division by zero in average | Validate the count before division. |
| Incorrect not-found result | Use the sentinel specified in the question, commonly -1. |
| Trailing comma | Print separators only between elements. |
| Zero denominator | Validate the denominator when required. |
| Integer division in convert | Convert an operand before division. |
| Missing const on getters | Mark non-modifying member functions const. |
| Changed function signature | Follow the interface given in the examination paper. |
Practice Questions
Practice 1: Dynamic integer array
Dynamically allocate an integer array containing {8, 3, 8, 1, 5}. Write separate functions to display it as [8, 3, 8, 1, 5], calculate the total, count occurrences of 8 and locate the first occurrence of 5. Release all dynamic memory.
Practice 2: Dynamic float array
Dynamically allocate a float array containing {4.5, 1.5, 3.0, 1.5}. Write functions to display it, calculate the average of the first three values, count occurrences of 1.5 and return the minimum value.
Practice 3: Search variation
Modify locate() so that it returns the final occurrence of a target instead of the first occurrence. Test it with {4, 2, 4, 7, 4} and the target 4.
Practice 4: Ratio<T>
Implement Ratio<T> with a constructor, getNumerator(), getDenominator(), convert() and invert(). Test Ratio<int>{5, 8} before and after inversion. Prevent construction with a zero denominator.
Practice 5: Timed mixed question
Without referring to the model answer, write a complete program in 25 minutes that dynamically allocates {9, 4, 2, 9, 6, 9}, displays it, calculates its total, counts the number of 9s, finds the maximum and reports the index of the first 6.
Short Model Answers for Common Variations
Count occurrences in an integer array
std::size_t countOccurrence(const int* array,
std::size_t size,
int target)
{
std::size_t count = 0;
for (std::size_t index = 0; index < size; ++index)
{
if (array[index] == target)
{
++count;
}
}
return count;
}
Find a minimum value
float findMin(const float* array, std::size_t size)
{
float minimum = array[0];
for (std::size_t index = 1; index < size; ++index)
{
if (array[index] < minimum)
{
minimum = array[index];
}
}
return minimum;
}
Return the final matching index
int locateLast(const int* array,
std::size_t size,
int target)
{
int result = -1;
for (std::size_t index = 0; index < size; ++index)
{
if (array[index] == target)
{
result = static_cast<int>(index);
}
}
return result;
}
Final Revision Sheet
| Topic | Rule to remember |
|---|---|
| Dynamic allocation | Type* array = new Type[size]{...}; |
| Dynamic deallocation | delete[] array; |
| Traversal | for (std::size_t i = 0; i < size; ++i) |
| Read-only parameter | const Type* array |
| Total | Initialise the sum to zero and add every element. |
| Average | Sum the required elements and divide by a non-zero count. |
| Occurrence count | Increment once for every match. |
| Search | Return on a match and use a defined not-found result. |
| Maximum or minimum | Initialise from the first element. |
| Exact display | Print separators between elements, not after the last. |
| Class template | template <typename T> class Name { ... }; |
| Constructor | Use the member initialiser list. |
| Getter | Return the private member and mark the function const. |
| Ratio conversion | Avoid integer division when a decimal result is required. |
| Ratio inversion | Swap the numerator and denominator and consider zero. |
new[] has a matching delete[].
Discover more from Tech For Talk
Subscribe to get the latest posts sent to your email.
Leave a Reply