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 intOperator
&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 numOperator
*is used to access the value stored in the pointer.*p = 40; // assign 40 to the variable pointed to by p cout << num << endl; // print 40
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
nullptrto indicate that it currently does not point to any valid memory address. - Sometimes you may see people use
NULLinstead ofnullptr. This is also valid, butnullptris preferred in modern C++.
int num = 30;
// declare pointer
int *p1 = nullptr, *p2 = nullptr;
p1 = # // assign address of num to p1
p2 = p1; // assign address stored in p1 to p2
*p1 = 40; // assign 40 to the variable pointed to by p1
*p2 = 50; // assign 50 to the variable pointed to by p2How 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.*paccesses the value stored in the memory locationppoints to.
*phelp you modify the value stored in the memory locationppoints 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
*pis the value stored in the memory locationppoints to, we can print it out usingcout << *p.
int num = 30;
int *p = #
cout << p << endl; // print the address stored in p
cout << *p << endl; // print the value stored in the memory location p points toPointer 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;
};
Student stu = {"Tom", 2000, true};
Student *pStu = &stu;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
*pStuto get thestructpointed to bypStu, then use.dot operator to access the members of thestruct.- should use brackets
()to group*pStufirst to get thestructbefore using the dot operator..
- should use brackets
(*pStu).name = "Amy";
(*pStu).born = 2001;
(*pStu).male = false;- Use
->arrow operator to access the members of thestructdirectly.
pStu->name = "Amy";
pStu->born = 2001;
pStu->male = false;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;
p = &num2; // error: cannot assign a new address to a const pointer
*p = 50; // valid: change the value stored in the memory location p points topstores the address ofnum.pcannot 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
*pcan be changed.- i.e. you can change the value stored in the memory location
ppoints 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;
p = &num2; // valid: change the address stored in p
*p = 3; // error: cannot assign a new value to a const intpstores the address ofnum.*pis aconst int.- Doesn’t mean the value stored in the memory location
ppoints to is a constant. - Just mean you cannot change the value stored in the memory location
ppoints to through*p.
- Doesn’t mean the value stored in the memory location
pitself 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;
p = &num2; // error: cannot assign a new address to a const pointer
*p = 3; // error: cannot assign a new value to a const intRule 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 *pmeanspis a pointer to anint.const int *pmeanspis a pointer to aconst int.int *const pmeanspis aconstpointer to anint.const int *const pmeanspis aconstpointer 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];
cout << 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 p2arr,p1, andp2all 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};
cout << 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 parris 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++) {
cout << arr[i] << " ";
}
}
int arr[] = {1, 2, 3, 4};
printArray(arr, 4);arris 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;
cout << *(p + 1) << endl; // print 2
cout << *(p + 2) << endl; // print 3
cout << *(arr + 2) << endl; // print 3An example with struct:
struct Student {
char name[4];
int born;
bool male;
};
Student stu[] = {{"Tom", 2000, true}, {"Amy", 2001, false}, {"Ted", 2002, true}};
Student *p = stu;
cout << (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(stu + 1)gets the address of the second element of the array.*(stu + 1)gets the second element of the array.(*(stu + 1)).namegets the value stored in thenamefield 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;
cout << arr[2] << endl; // print 3
cout << *(arr + 2) << endl; // print 3
cout << p[2] << endl; // print 3
cout << *(p + 2) << endl; // print 3Here, 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};
cout << arr[10] << endl; // out of bounds
cout << *(arr + 10) << endl; // out of boundsSometimes 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 = #
cout << p[-1] << endl; // out of bounds
cout << *(p + 1) << endl; // out of boundsReference
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 numBackground
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) {
cout << "Name: " << stu.name << endl;
cout << "Born: " << stu.born << endl;
cout << "Address: " << stu.address << endl;
}constis 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) {
cout << "Name: " << pStu->name << endl;
cout << "Born: " << pStu->born << endl;
cout << "Address: " << pStu->address << endl;
}- Good: Only pass the pointer, not the whole struct.
constalso ensures the stuct is not modified.
Two issues:
- The user can pass a
nullptrto 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) {
cout << "Student is nullptr" << endl;
return;
}
cout << "Name: " << pStu->name << endl;
cout << "Born: " << pStu->born << endl;
cout << "Address: " << pStu->address << endl;
}- Two
constensures the pointer is notnullptrand the pointer is not reassigned. - There’s a check if the
pStuisnullptrto avoid runtime error.
The reference version (Pass by reference)
void print_student(const Student &stu) {
cout << "Name: " << stu.name << endl;
cout << "Born: " << stu.born << endl;
cout << "Address: " << stu.address << endl;
}- Pass by reference also saves the copy of the struct.
- Reference must be initialized when declared, so no
nullptrcheck is needed. stuis the alias of the variable passed to the function,constensures 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;
a = b;
b = temp;
}
int x = 1, y = 2;
swap(x, y);
cout << x << " " << y << endl; // print 2 1Pointer version:
void swap(int *a, int *b) {
if (a == nullptr || b == nullptr) {
cout << "Invalid input" << endl;
return;
}
int temp = *a;
*a = *b;
*b = temp;
}
int x = 1, y = 2;
swap(&x, &y);
cout << x << " " << y << endl;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
newis used to allocate memory on the heap.deleteis 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
Student *pStu = new Student();
Student *pStu2 = new Student {'Tom', 2000, true}; // C++11Operator 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
Student *paStu = new Student[16];
Student *paStu2 = new Student[16]{{"Tom", 2000, true}, {"Amy", 2001, false}}; // C++11Operator 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++) {
arr[i] = new int[3];
}
for (int i = 0; i < 3; i++) {
delete[] arr[i];
}
delete[] arr;arris 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
p1andp2are also variables, they are also stored in memory. Suppose we have them on a 32-bit machine, the size ofp1andp2is 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↩︎