Practical Use of Pointers in Competitive Programming with C++
Confusing & and *: Inverse Operations
The unary operator & fetches the memory address of a variable, while * dereferences an address to access its stored value. They act as inverse operations.
int val = 10;
int* ptr;
ptr = &val;
std::cout << *ptr; // prints 10
Here, &val yields the address of val, which is stored in ptr. Dereferencing ptr with *ptr retrieves the value at that address, which is val's content.
Misunderstanding: *&val simplifies to val, but &*val causes an error because val is not an address; attempting to dereference it first fails.
Declaring Pointers Safely
Pointer declarations require attention to syntax:
int *pA, *pB, plainVar;
int* pX, y, z; // Only pX is a pointer; y and z are regular ints
To avoid undefined behavior, initialize pointers immediately:
int data;
int *ptr1 = &data;
int *ptr2 = nullptr;
Accessing Primitive Values via Pointers
Dereference the address to manipulate or read the underlying value:
int num = 5, *addr = nullptr;
addr = #
std::cout << *addr; // outputs 5
Working with Struct Pointers
Use -> to access members of a struct through a pointer:
struct Item {
int id, link;
};
Item obj, *ref = nullptr;
int main() {
ref = &obj;
obj.id = 7;
obj.link = 12;
std::cout << ref->id << '\n' << ref->link;
}
Accessing members incorrectly, such as *ref.id or ref.id, results in compilation errors.
Pointer Arithmetic
Pointers advance by the size of their pointed-to type:
int seq[10], *walker = nullptr;
seq[1] = 20;
seq[2] = 40;
walker = &seq[1];
std::cout << *walker << '\n'; // 20
walker++;
std::cout << *walker << '\n'; // 40
Advancing beyond array bounds leads to accessing invalid memory.
Modifying Variables Through Pointers
Change values indirectly via dereferenced addresses:
int var, *handle = nullptr;
handle = &var;
*handle = 8;
(*handle)++;
std::cout << *handle; // 9
For structs, direct assignment works between compatible types:
Item m, n, *ptr = nullptr;
ptr = &m;
ptr->id = 3;
m = n; // copies entire struct
Note: (*handle)++ differs from *handle++; the latter increments the pointer before dereferencing.
Passing Pointers to Functions for In-Place Updates
Passing by reference allows modification of caller variables:
void combine(int x, int y, int &result) {
result = x + y;
}
int main() {
int p = 4, q = 6, res = 0;
combine(p, q, res);
std::cout << res; // 10
}
Alternatively, passing a pointer achieves similar effect when using explicit address handling inside the function.
Enhancing Readability with Pointers in Loops
Repeated complex expressions can be simplified by capturing addresses:
// Original repetitive access
for (int i = 1; i <= N; ++i) {
if (NamespaceC2::minVals[NamespaceC::colors[i]].value > arr[i]) {
low = NamespaceC2::minVals[NamespaceC::colors[i]].value + K;
total += NamespaceC2::minVals[NamespaceC::colors[i]].value;
}
}
When only reading, store the value:
for (int i = 1; i <= N; ++i) {
int shortcut = NamespaceC2::minVals[NamespaceC::colors[i]].value;
if (shortcut > arr[i]) {
low = shortcut + K;
total += shortcut;
}
}
When modifying, use a pointer:
for (int i = 1; i <= N; ++i) {
auto *entry = &NamespaceC2::minVals[NamespaceC::colors[i]];
if (entry->value > arr[i]) {
entry->id = i;
entry->value = arr[i];
}
}
Using pointers reduces redundancy and clarifies intent in competitive code.