Introduction
Pattern programming in C is more than an exercise in printing numbers, symbols, or geometric arrangements. As programs become more complex, it becomes important to understand how data is accessed, modified, and passed between functions.
Pointers play a significant role in this process. They allow functions to access existing objects through their memory addresses, modify values when required, and work efficiently with arrays and dynamically allocated memory.
When pointers are passed to pattern functions, the pattern-generation logic can be separated from data storage and manipulation. This approach is particularly useful when a pattern depends on existing array elements, requires in-place modification, or uses dynamically allocated data.
In this article, we will explore how pointers are passed to functions in C and how they can be used to generate different patterns. We will examine pointer parameters, pointer arithmetic, array traversal, pointer-to-pointer parameters, and dynamic memory allocation through practical examples.
By the end of this tutorial, you will understand how to design reusable pattern functions that use pointers safely and effectively.
1. Understanding Pointers as Function Parameters
A pointer stores the address of an object. When a pointer is passed to a function, the function receives a copy of that address.
This allows the function to access the original object through the copied pointer. However, changing the local pointer variable itself does not automatically change the caller's pointer.
Consider the following example:
#include <stdio.h>
void printValue(const int *ptr)
{
printf("%d\n", *ptr);
}
int main(void)
{
int num = 10;
printValue(&num);
return 0;
}Output
10Explanation
The function printValue() accepts a pointer to a constant integer.
In the function call:
printValue(&num);the address of num is passed to the function.
Inside printValue(), the expression *ptr dereferences the pointer and retrieves the value stored at that address.
The const qualifier indicates that the function cannot modify the integer through this pointer.
This principle can be extended to pattern programming, where a pointer provides access to the data used to construct a pattern.
Key concept: A pointer parameter gives a function access to an existing object without requiring the object itself to be copied.
2. Pattern 1: Printing a Triangular Pattern Using a Pointer Parameter
Let us begin with a simple pattern in which a function receives a pointer to an array's first element and prints the values in triangular form.
Expected output
10
10 20
10 20 30
10 20 30 40
10 20 30 40 50C program
#include <stdio.h>
void printTriangle(const int *ptr, int n)
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
{
printf("%d ", *(ptr + j));
}
printf("\n");
}
}
int main(void)
{
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
printTriangle(arr, n);
return 0;
}Explanation
The function declaration is:
void printTriangle(const int *ptr, int n)The parameter ptr points to the first element of the array, while n specifies the number of available elements.
The expression:
*(ptr + j)accesses the element located j positions after the pointer's starting position.
The outer loop controls the number of rows, and the inner loop prints the appropriate number of elements for each row.
Since the function only reads the data, the pointer is declared as const int *ptr.
Key concept: Pointer parameters allow pattern functions to process array elements through memory addresses rather than requiring array-index notation.
Time complexity: (O(n^2)).
Auxiliary space complexity: (O(1)).
3. Pattern 2: Printing a Reverse Triangular Pattern Using Pointers
The same pointer-based approach can be used to generate a pattern whose rows decrease in length.
Expected output
10 20 30 40 50
10 20 30 40
10 20 30
10 20
10C program
#include <stdio.h>
void printReverseTriangle(const int *ptr, int n)
{
for (int i = n; i > 0; i--)
{
for (int j = 0; j < i; j++)
{
printf("%d ", *(ptr + j));
}
printf("\n");
}
}
int main(void)
{
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
printReverseTriangle(arr, n);
return 0;
}Explanation
The function begins with i = n and decreases the row length until it reaches one element.
The inner loop accesses the first i elements using pointer arithmetic. Because the pointer remains unchanged, each row begins at the same array element.
This demonstrates that pointer arithmetic does not require modifying the original pointer variable. Expressions such as ptr + j calculate addresses relative to the original pointer.
Key concept: A pointer can be reused across multiple pattern functions, allowing the same data to be displayed in different arrangements.
Time complexity: (O(n^2)).
4. Pattern 3: Generating a Palindromic Numeric Pattern Using Pointers
A palindromic numeric pattern increases toward a central value and then decreases symmetrically.
Expected output
1
1 2 1
1 2 3 2 1
1 2 3 4 3 2 1
1 2 3 4 5 4 3 2 1C program
#include <stdio.h>
void printPalindrome(const int *ptr, int n)
{
for (int i = 1; i <= n; i++)
{
/* Print the ascending sequence */
for (int j = 0; j < i; j++)
{
printf("%d ", *(ptr + j));
}
/* Print the descending sequence */
for (int j = i - 2; j >= 0; j--)
{
printf("%d ", *(ptr + j));
}
printf("\n");
}
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printPalindrome(arr, n);
return 0;
}Explanation
The pattern function receives a pointer to the first array element and uses two inner loops to generate each row.
The first loop prints the ascending sequence. The second loop traverses the previously printed elements in reverse order.
For the fourth row, the ascending loop produces:
1 2 3 4The descending loop produces:
3 2 1Together, they form:
1 2 3 4 3 2 1The descending loop starts at i - 2 to avoid printing the central element twice.
The pointer remains read-only throughout the process, making the function reusable without altering the original array.
Key concept: Pointer parameters make it possible to construct symmetric patterns by traversing existing data in both directions.
Time complexity: (O(n^2)).
5. Pattern 4: Modifying Array Elements Through a Pointer Function
Unlike the previous examples, some pattern functions need to modify the data before printing it.
Passing a pointer allows a function to update the original array elements.
Consider a pattern in which each row displays the original array values multiplied by the row number.
Expected output
For the array {1, 2, 3, 4}:
1
2 4
3 6 9
4 8 12 16C program
#include <stdio.h>
void printMultiplicationTriangle(const int *ptr, int n)
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
{
printf("%d ", *(ptr + j) * i);
}
printf("\n");
}
}
int main(void)
{
int arr[] = {1, 2, 3, 4};
int n = sizeof(arr) / sizeof(arr[0]);
printMultiplicationTriangle(arr, n);
return 0;
}Explanation
Although the pattern uses multiplication, the function does not modify the original array. It calculates each output value when printing it.
For example, the third row multiplies the first three array elements by 3, producing 3 6 9.
This is an example of transforming values during pattern generation without changing the underlying data.
If the task instead required multiplying each array element in place, the function could use a modifiable pointer:
void multiplyElements(int *ptr, int n, int factor)
{
for (int i = 0; i < n; i++)
{
*(ptr + i) *= factor;
}
}Calling multiplyElements(arr, n, 2) would modify the original array.
Key concept: Use const int * when a function only reads elements and int * when it must modify them.
6. Pattern 5: Generating Floyd's Triangle Using a Pointer
Floyd's Triangle arranges consecutive integers in increasing order across successive rows.
Expected output
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15In this example, the pointer function receives a previously populated array and prints its elements in triangular order.
C program
#include <stdio.h>
void printFloydTriangle(const int *ptr, int rows)
{
int index = 0;
for (int i = 1; i <= rows; i++)
{
for (int j = 1; j <= i; j++)
{
printf("%d ", *(ptr + index));
index++;
}
printf("\n");
}
}
int main(void)
{
int arr[15];
int value = 1;
int index = 0;
int rows = 5;
/* Populate the array */
for (int i = 1; i <= rows; i++)
{
for (int j = 1; j <= i; j++)
{
arr[index++] = value++;
}
}
printFloydTriangle(arr, rows);
return 0;
}Explanation
The program first stores consecutive integers in the array. The pointer function then traverses the stored values using an index.
For rows = 5, the array must contain at least 15 integers because the total number of elements is:
[
\frac{n(n+1)}{2}
]
The function assumes that the supplied pointer refers to an array containing enough initialized elements for the requested number of rows.
This separation of data generation and pattern printing makes the implementation easier to understand and maintain.
Key concept: A pointer-based pattern function can receive prepared data and determine how that data should be arranged.
Time complexity: (O(n^2)).
7. Pattern 6: Passing a Pointer to a Two-Dimensional Array
Two-dimensional arrays are commonly used to represent matrices, grids, and other structured data.
When passing a two-dimensional array to a function, the pointer type must account for the number of columns in each row.
Consider this declaration:
int (*ptr)[4];It defines ptr as a pointer to an array of four integers. It is not the same as int *ptr, which points to a single integer.
The following example uses a pointer to a row to print a matrix.
Expected output
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16C program
#include <stdio.h>
void printMatrix(int rows, const int (*ptr)[4])
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%-4d", ptr[i][j]);
}
printf("\n");
}
}
int main(void)
{
int arr[4][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16}
};
printMatrix(4, arr);
return 0;
}Explanation
The function receives the number of rows and a pointer to a row containing four integers.
The expression ptr[i][j] accesses the element at row i and column j.
The pointer advances by an entire row whenever its row index increases. Because each row contains four integers, the compiler can calculate the correct address for every element.
This is particularly useful when pattern functions need to process matrix data without copying the matrix.
Key concept: The pointer type must match the structure of the data being accessed.
Time complexity: (O(r \times c)), where r is the number of rows and c is the number of columns.
8. Pattern 7: Using a Pointer-to-Pointer for Dynamically Allocated Pattern Data
A pointer-to-pointer is a pointer that stores the address of another pointer.
For example:
int value = 10;
int *ptr = &value;
int **pptr = &ptr;Here, pptr points to ptr, and *pptr provides access to ptr.
Pointer-to-pointer parameters are useful when a function must modify a caller's pointer or when working with dynamically allocated structures represented by arrays of pointers.
The following example creates a triangular pattern using dynamically allocated rows.
Expected output
For three rows:
1
1 2
1 2 3C program
#include <stdio.h>
#include <stdlib.h>
void printTriangle(int *const *rows, int n)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
{
printf("%d ", rows[i][j]);
}
printf("\n");
}
}
int main(void)
{
int n = 3;
int **rows = malloc((size_t)n * sizeof(*rows));
if (rows == NULL)
{
printf("Memory allocation failed.\n");
return 1;
}
int allocated = 0;
for (int i = 0; i < n; i++)
{
rows[i] = malloc((size_t)(i + 1) * sizeof(*rows[i]));
if (rows[i] == NULL)
{
for (int k = 0; k < allocated; k++)
{
free(rows[k]);
}
free(rows);
printf("Memory allocation failed.\n");
return 1;
}
allocated++;
for (int j = 0; j <= i; j++)
{
rows[i][j] = j + 1;
}
}
printTriangle(rows, n);
for (int i = 0; i < n; i++)
{
free(rows[i]);
}
free(rows);
return 0;
}Explanation
The program allocates an array of pointers, with each pointer referring to a separately allocated row.
The first row contains one integer, the second contains two integers, and the third contains three integers.
The declaration:
int *const *rowsmeans that rows points to pointer elements that the function will not modify through this parameter. The integers themselves are not declared const, although this printing function does not modify them.
The function accesses each row through rows[i] and each element through rows[i][j].
The program checks every allocation. If an allocation fails, it releases any rows already allocated before exiting. After successful printing, it frees each row and then frees the outer pointer array.
Key concept: A pointer-to-pointer can provide access to dynamically allocated rows whose lengths differ.
Time complexity: (O(n^2)), because the program initializes and prints a total of (n(n+1)/2) elements.
Space complexity: (O(n^2)) for the stored elements, plus (O(n)) for the array of row pointers.
9. Understanding the Difference Between Pointer Parameters
Choosing the correct pointer type is essential when designing pattern functions.
| Parameter | Purpose |
|---|---|
int *ptr | Allows reading and modifying integer elements. |
const int *ptr | Allows reading elements but prevents modification through this pointer. |
int (*ptr)[4] | Points to a row containing four integers. |
int **ptr | Points to a pointer, often used with arrays of pointers or when modifying a caller's pointer. |
int *const ptr | The local pointer cannot be reassigned, but its integer elements can be modified. |
These declarations are not interchangeable. Their meanings depend on what the pointer points to and whether the function needs to modify the pointer, the data, or both.
For one-dimensional array patterns, int * and const int * are usually sufficient. For matrix patterns, a pointer-to-array or a suitable multidimensional array parameter is often more appropriate.
10. Important Rules When Passing Pointers to Functions
Follow these guidelines to make pointer-based pattern functions safe and maintainable.
10.1 Always initialize pointers
Never dereference a pointer that has not been assigned a valid address.
For example:
int value = 10;
int *ptr = &value;This initializes the pointer before it is used.
10.2 Pass the number of elements separately
A pointer does not carry information about the size of the array it points to.
A function such as:
void printPattern(const int *ptr, int n)relies on the caller to supply the correct element count.
10.3 Avoid out-of-bounds pointer arithmetic
A pointer can be used to access elements within the same array object. A pointer one position past the last element may be formed but must not be dereferenced.
10.4 Use const where appropriate
When a function only prints a pattern, prefer const int * or another suitable pointer-to-const type.
This makes the function's intended behavior clearer and prevents accidental writes through that parameter.
10.5 Check dynamically allocated memory
Always verify that malloc() succeeds before using the returned pointer.
If memory is allocated in multiple steps, ensure that previously allocated blocks are released if a later allocation fails.
10.6 Understand pointer ownership
A function that receives a pointer does not automatically own the memory it references.
Do not call free() on memory owned by the caller unless the function's contract explicitly transfers ownership. Clearly document which function is responsible for releasing dynamically allocated storage.
11. Common Mistakes to Avoid
The following mistakes can cause incorrect output or undefined behavior in pointer-based pattern programs:
Dereferencing an uninitialized or null pointer.
Passing a pointer to an array that contains fewer elements than the function expects.
Using the wrong pointer type for a multidimensional array.
Confusing a pointer-to-pointer with a pointer to an array.
Modifying a pointer when only the pointed-to data should change.
Assuming that passing a pointer creates a separate copy of the underlying data.
Forgetting to release dynamically allocated memory.
Freeing memory that was not dynamically allocated or has already been freed.
Ignoring allocation failures when building dynamic patterns.
Performing calculations that overflow the chosen integer type.
A reliable debugging strategy is to check the pointer's type, the address it references, the valid element range, and the loop boundaries before each memory access.
12. Practice Problems
Use the following exercises to strengthen your understanding of pointer-based pattern functions.
Beginner level
Write a function that accepts an integer pointer and prints a right-angled triangular pattern.
Create a function that prints an array in reverse triangular order.
Generate a palindromic numeric pattern using a pointer parameter.
Intermediate level
Write a function that receives a pointer and prints Floyd's Triangle from stored data.
Create a function that modifies array elements before printing a numeric pattern.
Pass a pointer to a two-dimensional array to a function that displays its elements.
Implement a matrix pattern using pointer arithmetic instead of array-index notation.
Advanced level
Write a function that accepts a pointer-to-pointer and prints dynamically allocated triangular rows.
Create a function that modifies a caller's pointer through a pointer-to-pointer parameter.
Generate a matrix spiral using a pointer-to-array parameter.
Build a collection of reusable pattern functions that share a common array.
Implement a dynamically allocated matrix pattern with proper allocation-failure handling and memory cleanup.
For each exercise, define the function's contract first. Determine whether the function reads data, modifies data, changes a pointer, or manages memory. Then choose the pointer type that matches those responsibilities.
Conclusion
Passing pointers to pattern functions in C provides a foundation for writing reusable and memory-aware programs. Pointer parameters allow functions to access existing data, perform calculations, traverse arrays, and work with dynamically allocated structures without copying entire arrays.
Through triangular patterns, reverse arrangements, palindromic sequences, Floyd's Triangle, matrix traversal, and dynamically allocated rows, we have explored how pointers support a range of pattern-generation techniques.
The central principle is that a pointer-based function should clearly define what memory it can access, whether it can modify that memory, and who is responsible for managing its lifetime.
Mastering these concepts will strengthen your understanding of C memory management and prepare you for more advanced programming topics such as linked lists, dynamic data structures, and modular algorithm design.
For all Pattern Programs list click here:
…till the next post, bye-bye & take care

No comments:
Post a Comment