Pointer & Reference
Pointer
Definition
Pointers are the variables that store the memory addresses of other variables.
You can declare a pointer’s type by appending
*
to the pointed-to type.int *p; // p is a pointer to an int
Operator
&
is used to get the address of a variable(fundamental or user-defined).int num = 30; // the type of p is int * int *p = # // p stores the address of num
Operator
*
is used to access the value stored in the pointer.*p = 40; // assign 40 to the variable pointed to by p << num << endl; // print 40 cout
Declaration of Pointer
When declaring a pointer, you need to specify the type of the pointer, which is the type of the variable it points to.
- It’s a good practice to initialize a pointer to
nullptr
to indicate that it currently does not point to any valid memory address. - Sometimes you may see people use
NULL
instead ofnullptr
. This is also valid, butnullptr
is preferred in modern C++.
int num = 30;
// declare pointer
int *p1 = nullptr, *p2 = nullptr;
= # // assign address of num to p1
p1 = p1; // assign address stored in p1 to p2
p2
*p1 = 40; // assign 40 to the variable pointed to by p1
*p2 = 50; // assign 50 to the variable pointed to by p2
How pointer works
Declaration
Dereference operator *
int num = 30;
// declare pointer
int *p1 = #
int *p2 = p1;
*p1 = 40; // now *p2 is also 40
- You can directly assign the address of a variable to a pointer.
*
is dereference operator.*p
accesses the value stored in the memory locationp
points to.
*p
help you modify the value stored in the memory locationp
points to.
Print pointer
- The value of pointer is the memory address it points to, we can print it out using
cout << p
. - The value of
*p
is the value stored in the memory locationp
points to, we can print it out usingcout << *p
.
int num = 30;
int *p = #
<< p << endl; // print the address stored in p
cout << *p << endl; // print the value stored in the memory location p points to cout
Pointer of pointer
int num = 30;
int *p = #
int **pp = &p;
- Pointer are variables that store the address of another variable.
- A pointer of pointer is a variable that stores the address of another pointer.
Pointer of struct
We can use pointer to point to a struct
.
struct Student {
char name[4];
int born;
bool male;
};
= {"Tom", 2000, true};
Student stu *pStu = &stu; Student
Here, pStu
is a pointer to a Student
struct. 3
There are two ways to access the members of a struct
pointed to by a pointer.
- Use
*pStu
to get thestruct
pointed to bypStu
, then use.
dot operator to access the members of thestruct
.- should use brackets
()
to group*pStu
first to get thestruct
before using the dot operator.
.
- should use brackets
(*pStu).name = "Amy";
(*pStu).born = 2001;
(*pStu).male = false;
- Use
->
arrow operator to access the members of thestruct
directly.
->name = "Amy";
pStu->born = 2001;
pStu->male = false; pStu
const
pointer
A const
pointer is a pointer that cannot be changed after it is initialized.
int num = 30;
int *const p = #
int num2 = 40;
= &num2; // error: cannot assign a new address to a const pointer
p *p = 50; // valid: change the value stored in the memory location p points to
p
stores the address ofnum
.p
cannot be changed after it is initialized.- i.e. you cannot assign a new address to
p
.
- i.e. you cannot assign a new address to
*p
can be changed.- i.e. you can change the value stored in the memory location
p
points to.
- i.e. you can change the value stored in the memory location
Pointer to const
int num = 1;
const int *p = #
int num2 = 2;
= &num2; // valid: change the address stored in p
p *p = 3; // error: cannot assign a new value to a const int
p
stores the address ofnum
.*p
is aconst int
.- Doesn’t mean the value stored in the memory location
p
points to is a constant. - Just mean you cannot change the value stored in the memory location
p
points to through*p
.
- Doesn’t mean the value stored in the memory location
p
itself can be changed.- i.e. you can assign a new address to
p
.
- i.e. you can assign a new address to
const
pointer to const
You cannot change the address stored in p
, nor the value stored in the memory location p
points to.
int num = 1;
const int *const p = #
int num2 = 2;
= &num2; // error: cannot assign a new address to a const pointer
p *p = 3; // error: cannot assign a new value to a const int
Rule of thumb
Read from right to left, *
divides the type into two parts.
- After
*
is the type of the pointer - Before
*
is the type of the variable the pointer points to
Examples:
int *p
meansp
is a pointer to anint
.const int *p
meansp
is a pointer to aconst int
.int *const p
meansp
is aconst
pointer to anint
.const int *const p
meansp
is aconst
pointer to aconst int
.
Pointers and Arrays
Array name as pointer
An array name acts like a pointer to the first element of the array.
int arr[] = {1, 2, 3, 4};
int *p1 = arr;
int *p2 = &arr[0];
<< arr << endl; // print the address stored in arr
cout << p1 << endl; // print the address stored in p1
cout << p2 << endl; // print the address stored in p2 cout
arr
,p1
, andp2
all store the address of the first element of the array.arr
Difference between pointer and array
int *p = nullptr;
int arr[] = {1, 2, 3, 4};
<< p << endl; // print the address stored in p
cout << arr << endl; // print the address stored in arr
cout << sizeof(arr) << endl; // print the size of arr
cout << sizeof(p) << endl; // print the size of the pointer p cout
arr
is a constant pointer to the first element of the array.- You cannot assign a new address to
arr
. - You can assign a new value to the elements of the array.
- You cannot assign a new address to
sizeof(arr)
returns the size of the array.sizeof(p)
returns the size of the pointerp
.
Array as function parameter
When you pass an array to a function, the array name is decayed to a pointer to the first element of the array.
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
<< arr[i] << " ";
cout }
}
int arr[] = {1, 2, 3, 4};
(arr, 4); printArray
arr
is decayed to a pointer to the first element of the array.- You must pass the size of the array to the function.
Pointer arithmetic
Pointer can be added or subtracted by an integer.
- The compiler figures out the new address by adding the integer to the pointer and then multiplying the integer by the size of the type the pointer points to.
- i.e. new address = old address + integer * (size of the type)
int arr[] = {1, 2, 3, 4};
int *p = arr;
<< *(p + 1) << endl; // print 2
cout << *(p + 2) << endl; // print 3
cout << *(arr + 2) << endl; // print 3 cout
An example with struct
:
struct Student {
char name[4];
int born;
bool male;
};
[] = {{"Tom", 2000, true}, {"Amy", 2001, false}, {"Ted", 2002, true}};
Student stu*p = stu;
Student
<< (stu + 1)->name << endl; // print Amy
cout << (p + 1)->name << endl; // print Amy
cout << (*(stu + 1)).name << endl; // print Amy
cout << (*(p + 1)).name << endl; // print Amy cout
(stu + 1)
gets the address of the second element of the array.*(stu + 1)
gets the second element of the array.(*(stu + 1)).name
gets the value stored in thename
field of the second element of the array.
[]
operator
The []
operator is equivalent to pointer arithmetic.
We use arr
and pointer arithmetic to access the elements of the array.
int arr[] = {1, 2, 3, 4};
int *p = arr;
<< arr[2] << endl; // print 3
cout << *(arr + 2) << endl; // print 3
cout << p[2] << endl; // print 3
cout << *(p + 2) << endl; // print 3 cout
Here, arr[2]
is equivalent to *(arr + 2)
, []
can be applied to both array and pointer.
Out of bounds
Both pointer arithmetic and []
operator don’t do boundary checking.
int arr[] = {1, 2, 3, 4};
<< arr[10] << endl; // out of bounds
cout << *(arr + 10) << endl; // out of bounds cout
Sometimes programmers perform pointer arithmetic or use the []
operator on a pointer to a fundamental type. This practice is dangerous and should be avoided.
int num = 1;
int *p = #
<< p[-1] << endl; // out of bounds
cout << *(p + 1) << endl; // out of bounds cout
Reference
Definition
Refrences are safer, more convenient versions of pointers.
- A reference is an alias for a variable. (Kinds of like a pointer, but you don’t need to use
*
to access the value.) - A reference must be initialized when declared.
- Once a reference is initialized to a variable, it cannot be reseated to refer to another variable.
To declare a reference, use &
appended to the type name.
int num = 1;
int &ref = num; // ref is a reference to num
Background
If you have a huge struct and you want to pass it to a function,
struct Student {
char name[20];
int born;
char address[100];
};
void print_student(const Student stu) {
<< "Name: " << stu.name << endl;
cout << "Born: " << stu.born << endl;
cout << "Address: " << stu.address << endl;
cout }
const
is used to ensure that the struct is not modified.- The whole struct is copied into the function, which is not efficient.
The pointer version
void print_student(const Student *pStu) {
<< "Name: " << pStu->name << endl;
cout << "Born: " << pStu->born << endl;
cout << "Address: " << pStu->address << endl;
cout }
- Good: Only pass the pointer, not the whole struct.
const
also ensures the stuct is not modified.
Two issues:
- The user can pass a
nullptr
to the function. - The function logic is simple, sometimes you assign the pointer to sth else by mistake.
Updated pointer version
void print_student(const Student *const pStu) {
if (pStu == nullptr) {
<< "Student is nullptr" << endl;
cout return;
}
<< "Name: " << pStu->name << endl;
cout << "Born: " << pStu->born << endl;
cout << "Address: " << pStu->address << endl;
cout }
- Two
const
ensures the pointer is notnullptr
and the pointer is not reassigned. - There’s a check if the
pStu
isnullptr
to avoid runtime error.
The reference version (Pass by reference)
void print_student(const Student &stu) {
<< "Name: " << stu.name << endl;
cout << "Born: " << stu.born << endl;
cout << "Address: " << stu.address << endl;
cout }
- Pass by reference also saves the copy of the struct.
- Reference must be initialized when declared, so no
nullptr
check is needed. stu
is the alias of the variable passed to the function,const
ensures the variable is not modified. 4
Another example
Swap two numbers using reference and pointer.
Reference version:
void swap(int &a, int &b) {
int temp = a;
= b;
a = temp;
b }
int x = 1, y = 2;
(x, y);
swap<< x << " " << y << endl; // print 2 1 cout
Pointer version:
void swap(int *a, int *b) {
if (a == nullptr || b == nullptr) {
<< "Invalid input" << endl;
cout return;
}
int temp = *a;
*a = *b;
*b = temp;
}
int x = 1, y = 2;
(&x, &y);
swap<< x << " " << y << endl; cout
Dynamical array
Stack and heap
Stack allocation
A stack allocation is a memory allocation method that is used for local variables and function parameters.
- The size of memory allocated on the stack is fixed at compile time.
- When the function returns, the memory is automatically freed.
- Fast allocation and deallocation compared to heap allocation.
- Limited by the stack size.
Heap allocation
A heap allocation is a memory allocation method that is used for dynamic memory allocation.
- Heap memory is accessible or exists until it is explicitly freed.
- There’s no automatic de-allocation, you need to manually free the memory when you are done using it.
- Slower than stack allocation.
- The size of the Heap-memory is quite larger as compared to the Stack-memory.
Why use heap allocation?
- Dynamic size: The size of the array is determined at runtime.
- Large size: The size of the array can be large.
- Extended lifetime: The array can be accessed after the function returns.
- Manual management: Heap allocation gives programmers more control over memory usage
- …
Operator new
new
is used to allocate memory on the heap.delete
is used to free the memory on the heap.
// allocate an int, do nothing
int *p = new int();
// allocate an int, initialize to 0
int *p2 = new int();
int *p3 = new int{}; // C++11
// allocate an int, initialize to 5
int *p4 = new int(5);
int *p5 = new int{5}; // C++11
// allocate a struct
*pStu = new Student();
Student *pStu2 = new Student {'Tom', 2000, true}; // C++11 Student
Operator delete
delete
is used to free the memory which is allocated by new
.
To prevent memory leak, you need to free the memory allocated by new
when you are done using it.
// free the memory allocated by new
delete p;
delete p2;
delete p3;
delete p4;
delete p5;
delete pStu;
Operator new[]
new[]
is used to allocate memory on the heap for an array.delete[]
is used to free the memory on the heap for an array.
int * pa1 = new int[3];
// allocate and initialize to 0
int *pa2 = new int[3]();
int *pa3 = new int[3]{}; // C++11
// allocate 16 int, the first 4 are initialized to 1, 2, 3, 4
// rest are initialized to 0
int *pa4 = new int[16]{1, 2, 3, 4};
// allocate memory for 16 Student structs
*paStu = new Student[16];
Student *paStu2 = new Student[16]{{"Tom", 2000, true}, {"Amy", 2001, false}}; // C++11 Student
Operator delete[]
delete[]
is used to free the memory on the heap for an array.
delete[] pa1;
delete[] pa2;
delete[] pa3;
delete[] pa4;
delete[] paStu;
delete[] paStu2;
Allocate multidimensional array
When you allocate a 2-d array on the heap, you need to delete it in a nested way.
int **arr = new int*[3];
for (int i = 0; i < 3; i++) {
[i] = new int[3];
arr}
for (int i = 0; i < 3; i++) {
delete[] arr[i];
}
delete[] arr;
arr
is a pointer to an array of “pointers toint
”.arr[i]
is a pointer to an array ofint
.- You need to delete the inner arrays first before deleting the outer array.
Footnotes
Note that
p1
andp2
are also variables, they are also stored in memory. Suppose we have them on a 32-bit machine, the size ofp1
andp2
is 4 bytes.↩︎The addresses in the figure are not the actual addresses in your computer. They are just for illustration.↩︎
The grey box represents paddings to align the struct to the memory address.↩︎
When reviewing the code, you should also take a look at how to call the functions.↩︎
Image from hyperskill.org↩︎