Exam Question Pattern 1

Pointers, dynamic arrays and template classes: a complete C++ examination revision guide based on recurring question patterns

Vivek Bhadra  |  C++ Pointers Arrays Templates Exam Preparation

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[] and delete[]
  • 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
Revision priority: Perfect these recurring patterns first. Once you can write and explain them without assistance, move to the less frequently examined topics.

The Recurring Examination Pattern

YearQuestion patternSkills being assessed
2023Template class Ratio<T>Class templates, constructor, conversion, inversion and getters
2024Dynamic float arrayDisplay, average of first n values, occurrence count and maximum
2025Dynamic int arrayExact 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.

1. Create or initialise the data
2. Pass the data to focused functions
3. Process the values safely
4. Print or return the required result
5. Release dynamically allocated memory

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
ExpressionMeaning
int* pointerDeclares a pointer to an int
&numberObtains the address of number
pointer = &numberStores that address in pointer
*pointerAccesses 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
Exam point: 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;
AllocationRequired deallocation
new int{42}delete pointer;
new int[size]delete[] pointer;
Important: 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].
}
Boundary rule: The final valid index is size - 1. The loop condition must therefore be index < size, not index <= size.

Dynamic-array errors to avoid

ErrorWhy it is wrong
Dereferencing nullptrThere is no valid object at the address.
Using an uninitialised pointerThe 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

FunctionResponsibilityReturn type
displayPrint every element using the exact required formattingvoid
totalAdd every elementint
locateReturn the matching index or a not-found valueint

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 << ", ";
}
RequiredIncorrect
[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.

Value654321
Index012345
Exam point: The value 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 bool when 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 n values
  • 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
Validation: 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;
}
Exam decision: Use the comparison method requested or taught in the module. Do not add unnecessary complexity when the question clearly expects a direct equality test.

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];
    }
}
Common mistake: Initialising 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 n from 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}
{
}
Class invariant: The denominator must never be zero. If validation is part of the required design, perform it when the object is constructed.

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.

ExpressionResult
3 / 40
static_cast<double>(3) / 40.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)
    };
}
Exam rule: Do not guess what 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

1

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.

2

Write the structure first

Add the required headers, function definitions or declarations, and the basic structure of main().

3

Create the data

Allocate the correct type and number of elements. Check that the initializer contains exactly the required values.

4

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.

5

Call every required function

A correctly written function earns little if it is never called when the question expects a complete program.

6

Release the memory

Place the matching delete[] after the final use of the dynamic array.

7

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

CheckWhat should be present
AllocationCorrect type, new[], size and initial values
Function parametersPointer plus size, with target or count where required
Const-correctnessconst T* for functions that only read the array
Loop boundaryindex < size or index < count
AccumulatorInitialised before it is used
SearchCorrect found and not-found behaviour
Maximum or minimumInitialised from a valid array element
OutputRequired brackets, commas, spaces and labels
Cleanupdelete[] used exactly once after the final access
Template classTemplate declaration, public interface and private data

Common Mistakes That Lose Marks

MistakeCorrection
index <= sizeUse index < size.
Missing size parameterPass the array size separately with a raw pointer.
delete instead of delete[]Match new[] with delete[].
Uninitialised total or counterStart totals and counters at zero.
Maximum initialised to zeroInitialise it from array[0].
Division by zero in averageValidate the count before division.
Incorrect not-found resultUse the sentinel specified in the question, commonly -1.
Trailing commaPrint separators only between elements.
Zero denominatorValidate the denominator when required.
Integer division in convertConvert an operand before division.
Missing const on gettersMark non-modifying member functions const.
Changed function signatureFollow 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

TopicRule to remember
Dynamic allocationType* array = new Type[size]{...};
Dynamic deallocationdelete[] array;
Traversalfor (std::size_t i = 0; i < size; ++i)
Read-only parameterconst Type* array
TotalInitialise the sum to zero and add every element.
AverageSum the required elements and divide by a non-zero count.
Occurrence countIncrement once for every match.
SearchReturn on a match and use a defined not-found result.
Maximum or minimumInitialise from the first element.
Exact displayPrint separators between elements, not after the last.
Class templatetemplate <typename T> class Name { ... };
ConstructorUse the member initialiser list.
GetterReturn the private member and mark the function const.
Ratio conversionAvoid integer division when a decimal result is required.
Ratio inversionSwap the numerator and denominator and consider zero.
Final check: Before submitting your answer, verify every function name and signature against the examination paper, check every loop boundary, confirm the exact required output and ensure every new[] has a matching delete[].

Discover more from Tech For Talk

Subscribe to get the latest posts sent to your email.

Leave a Reply