C++ Pointers and References: nullptr and Borrowing
Learn C++ pointers and references in simple steps: copies, aliases, addresses, nullptr checks, const access and safe borrowing from a living owner.
What you will learn
- Explain a copy, a reference and a pointer using one integer.
- Read address-of and dereference symbols in their actual context.
- Check an optional pointer before accessing its object.
- Keep borrowed access inside the owner's valid lifetime.
- Use a checked lookup without confusing access with ownership.
Start with one value, then ask whether you need a copy or access
Imagine that a function needs to work with a score stored somewhere else. It might need its own copy, permission to change the existing score, or a way to report that no score was found. These are different requests. C++ offers values, references and pointers to express them. Learning the difference is more useful than memorizing an asterisk without knowing what problem it solves.
An ordinary int parameter receives a value. Changing that local integer does not change the caller's original integer. An int& parameter is a reference: another way to name an existing integer. Changing it changes that same integer. A pointer, written int*, stores an address that can identify an integer. Unlike a reference, an ordinary pointer can also represent no object with nullptr.
Neither a reference nor a raw pointer automatically owns the object it refers to. In this lesson, owning means being responsible for the object's lifetime or its resource cleanup. Borrowing means using an existing object for a limited time without taking over that responsibility. Think of borrowing a key rather than buying the house, but remember the important limit: C++ does not automatically stop every use of a key after the house is gone.

See a copy, a reference and a pointer work on one integer
Read the program from top to bottom. The copy changes on its own; the reference and pointer both access the original integer.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Score: 10
Copy: 20
After editing inputs, calculate a new prediction; this reference describes only the starter.
Read & and * according to where they appear
In int& alias = score, the ampersand belongs to the declared type: alias is a reference to an integer. In &score, the ampersand is an expression that obtains score's address. In int* pointer, the asterisk says the variable is a pointer. In *pointer, the asterisk follows that pointer to access the selected integer. The same printed symbol can have different roles, so always read the surrounding declaration or expression.
The first program does not create three versions of score. copy is a separate integer initialized with seven. alias and pointer access the existing score. Setting alias to nine therefore changes score. Adding one through *pointer changes that same score to ten. Meanwhile, copy remains twenty because its assignment changed a different integer. Writing these values beside each line is a good way to check your mental model.
A pointer can later be assigned another valid address or nullptr. An ordinary reference cannot be reseated in the same way: assigning through a reference changes its referred-to object. Keep declarations simple while learning; prefer one pointer declaration per statement instead of relying on a compact multi-variable declaration that makes the type harder to read. Clear names such as selected and original also help show whether the code is selecting an object or creating a new value.
- score starts at 7; copy receives another 7.
- copy = 20 changes only the copy.
- alias = 9 changes score because alias names score.
- *pointer += 1 changes score again, from 9 to 10.
nullptr means no object: check before following the pointer
A lookup may not find the requested reading. Returning int* lets it return an address on success or nullptr on failure. The caller must test the result before using *selected. Do not treat nullptr as an integer zero stored somewhere: it means there is no selected integer to read. A real integer with value zero can still have a perfectly valid, non-null address.
The second example deliberately selects nothing and prints Not found. Its safe behavior comes from the if check, not from a special protected dereference operation. Accessing through a null pointer is not a reliable way to get an exception or an error message. We do not execute that mistake. The useful repair is to make the absent path explicit before any read or write through the pointer.
A non-null pointer is not automatically a valid pointer. If its object has been destroyed or its vector element was invalidated, the stored address can still be non-null. This is called a dangling pointer: it no longer provides valid access to the old object. A null check answers whether the stored pointer is absent, while lifetime reasoning answers whether an existing borrow may still be used. Both questions matter, and one cannot replace the other.
Handle an absent result without touching an object
The selected pointer is empty, so the program takes the reporting branch instead of reading an integer.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Not found
After editing inputs, calculate a new prediction; this reference describes only the starter.
Use const when the borrowed object should only be read
const int* means a pointer through which you cannot change the selected integer. The pointer itself can still select another integer unless it is also declared const. int* const means the pointer cannot be reseated, but the integer can still be changed through it. Start by asking two separate questions: may this handle select something else, and may this access path change the selected value? The placement of const answers them.
The intentional compile-error example tries to change a value through const int*. Repair the example by reading the value instead, or choose a mutable borrowing interface only when the operation really needs permission to write. Do not cast away const to hide a design mismatch. A read-only access path is useful precisely because it lets the compiler reject some accidental writes before the program runs.
A borrowed pointer must not outlive its valid target. Never return the address of an ordinary local integer that is about to leave scope. Do not save a vector element pointer across an operation that may invalidate it. Our lookup returns an address into an array owned by main, and main uses the result before that array leaves scope. The function does not allocate, delete or transfer anything; its job is only selection.
Repair a write through read-only access
Intentional compile error. Replace the write with an observation; the original const access contract should stay intact.
Predict the output, edit the source, then select Run code.
Build a checked selection before applying a small update
The lab keeps three readings, ten, twenty and thirty, in an array owned by main. Read a one-based position and a change amount on two complete integer lines. The change must be between minus one hundred and one hundred. Malformed or missing required input, or a change outside that range, prints Invalid input and returns status one before the lookup or update happens.
The lookup accepts positions one through three and returns the matching element's address. Other well-formed positions produce nullptr. The caller prints Not found with status zero for an absent result, because a completed search with no match is not a parsing failure. For a match, print Before and After around the update. The bounded initial readings and change keep the arithmetic small, and no operation changes the array's size or lifetime.
Test every valid position, zero, a negative position, position four, zero change and both allowed change endpoints. Also test numeric suffixes and an input line that overflows int. A final valid line without a newline should still work. Explain which object owns the selected integer and why that object is alive at every access. That explanation is the real safety argument; simply saying the pointer was non-null leaves an important part unanswered.
Compile one program and compare its output
The lesson editors run complete programs only when the compiler sandbox is available. You can also save one example as main.cpp and use your installed compiler. Run only the repaired version of an intentional compile-error example.
macOS / Linux (choose your compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson
Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Select an optional reading from a living array, check the result, and update only the selected value through a borrowed pointer.
Acceptance checklist
- Own the array {10, 20, 30} in main.
- Read a whole integer position and a whole integer change in -100..100; reject bad input as Invalid input with status 1.
- Return an element address only for positions 1..3, validating before converting the index.
- For no match print Not found with status 0 and do not dereference.
- For a match print Before and After, using the pointer only while the owning array remains alive.
Need a hint?
- Return nullptr before subtracting one for an invalid position.
- Keep ownership in main; the lookup function borrows the array.
- Check selected before using *selected, then reason separately about the array's lifetime.
Write and run your lab
Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.
Predict the output, edit the source, then select Run code.
Your lab evidence and notes
Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.
Loading local notes…
Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.
Reveal the explained lab solution
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
Expected output
Before: 20
After: 25
The array owns the values and outlives the lookup result. The raw pointer only borrows access; it must not delete an array element.
Invalid positions are rejected before subtraction and unsigned conversion. A no-match pointer is checked before any dereference.
The input bounds keep the update between -90 and 130, so the small arithmetic does not rely on unchecked large additions.
Review before moving on
Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.
- Explain a copy, a reference and a pointer using one integer.
- Read address-of and dereference symbols in their actual context.
- Check an optional pointer before accessing its object.
- Keep borrowed access inside the owner's valid lifetime.
- Use a checked lookup without confusing access with ownership.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.