-
What is pointer?
-
Declaring pointers
-
Storing addresses in pointers
-
Dereferencing pointers
-
Dynamic memory allocation
-
Pointer arithmetic
-
Pointers and arrays
-
Pass-by-reference with pointers
-
const and pointers
-
Using pointers to functions
-
Potential pointer pitfalls
-
What is a reference?
-
Review passing references to functions
-
const and references
-
Reference variables in range-based for loops
-
Potnetial reference pitfalls
-
Raw vs. Smart pointers
-
A variable
- whose value is an address
-
What can be at the address?
- Another variable
- A function
-
Pointers point to variables or functions?
-
If x is an integer variable and its value is 10, then I can declare a pointer that points to it
-
To use the data that the pointer is pointing to you must know its type
-
Can't I just use the variable or function itself?
- Yes, but not always
-
Inside functions, pointers can be used to access data that are defined outside the function. Those variables may not be in scope so you can't access them by their name.
-
Pointers can be used to operate on arrays very efficiently
-
We can allocate memory dynamically on the
- This memory doesn't even have a variable
- The only way to get to it is via a pointer
-
With OO. pointers are how polymorphism works!
-
Can access specific addresses in memeory
- useful in medded and systems applications
variable_type *pointer_name;
int *int_ptr;
double* double_ptr; // not typo, C++ compiler doesn't care where * is placed
char *char_ptr;
string *string_ptr;
// all contains garbage data without initialization
variable_type *pointer_name {nullptr}
int *int_ptr {};
double* double_ptr {nullptr};
char *char_ptr {nullptr};
string *string_ptr {nullptr};
// initialize pointer to point no where
- Always initialize pointers
- Uninitialized pointers contain garbage data and can 'point anywhere'
- Initializing to zero or
nullptr
(C++11)- implies that the pointer is 'pointing nowhere'
- If you don't initialize a pointer to point to a variable or function then you should initialize it to
nullptr
to 'make it null'
&
the address operator
- Vairables are stored in unique addresses
- Unary operator
- Evaluates to the address of its operand
- Operand cannot be a constant or expression that evalueate to temp values
int num {10};
cout << "Value of num is: " << num << endl; // 10
cout << "sizeof of num is: " << sizeof num << endl; // 4 byte: how much storage is used
cout << "Address of num is: " << &num << endl; // 0x61ff1c: hex number of location in memory (base 16)
&
the address operator - example
int *p;
cout << "Value of p is: " << p << endl; // 0x61ff60 - garbage since we didn't initialize anything to p
cout << "Address of p is: " << &p << endl; // 0x61ff18
cout << "sizeof of p is: " << sizeof p << endl; // 4 bytes of storage
p = nullptr; // set p to point nowhere
cout << "Value of p is: " << p << endl; // 0 meaning pointing no where
sizeof
a pointer variable
- Don't confuse the size of a pointer and the size of what it points to
- All pointers in a program have the same size
- They may be pointing to very large or very small types
// All pointer hold the same size
int *p1 {nullptr};
double *p2 {nullptr};
unsigned long long *p3 {nullptr};
vector<string> *p4 {nullptr};
string *p5 {nullptr};
- The compiler will make sure that the address stored in a pointer variable is of the correct type
int score {10};
double high_temp {100.7};
int *score_ptr {nullptr};
score_ptr = &score; // OK
score_ptr = &high_temp; // Compile Error
// There is a type conflict
&
the address operator
- Pointer are variables so they can change
- Pointer can be null
- Pointer can be uniinitialized (not recommended)
double high_temp {100.7};
double low_temp {37.2};
double *temp_ptr; // pointring anywhere
temp_ptr = &high_temp; // points to high_temp
temp_ptr = &low_temp; // points to low_temp
temp_ptr = nullptr;
Go to SimplePointers folder
- Access the data we're pointing to - dereferencing a pointer
- If
score_prt
is a pointer and has a valid address - Then you can access the data at the address contained in the
score_prt
using the dereferencing operator*
Yes... same operator as declaring a pointer
int score {100};
// declaring pointer
int *score_ptr {&score};
// dereferencing pointer
cout << *score_ptr << endl; // Output: 100
*score_ptr = 200;
cout << *score_ptr << endl; // Output: 200
cout << score << endl; // Output: 200
- Access the data we're pointing to
double high_temp {100.7};
double low_temp {37.4};
double *temp_ptr {&high_temp};
cout << *temp_ptr << endl; // Output: 100.7
temp_ptr = &low_temp;
cout << *temp_ptr << endl; // Output: 37.4
string name {"Frank"};
string *string_ptr {&name};
cout << *string_ptr << endl; // Output: Frank
name = "James";
cout << *string_ptr << endl; // Output: James
Go to Dereference folder
Allocating storage from the heap at runtime
- We often don't know how much storage we need until we need it
- We can allocate storage for a variable at run time
- Recall C++ arrays
- We had to explicityly provide the size and it was fixed
- But vectors grow and shrink dynamically
- We can use pointers to access newly allocated heap storage
using new
to allocate stroage
int *int_ptr {nullptr};
int_ptr = new int; // allocate an integer on the heap
cout << int_ptr << endl; // address
// Dereference pointer
cout << *int_ptr << endl; // garbage
*int_ptr = 100; // dereference and put value 100 in
cout << *int_ptr << endl; // Output: 100
using delete
to deallocate storage
int *int_ptr {nullptr};
int_ptr = new int; // allocate an integer on the heap
...
delete int_ptr; // frees the allocated storage
using new []
to allocate storage for an array
int *array_ptr {nullptr};
int size {};
cout << "How big do you wan the array? ";
cin >> size;
array_ptr = new int[size]; // allocate array on the heap
// We can access the array here
using delete []
to deallocate storage for an array
int *array_ptr {nullptr};
int size {};
cout << "How big do you wan the array? ";
cin >> size;
array_ptr = new int[size]; // allocate array on the heap
...
delete [] array_ptr; // free allocated storage, [] must be empty
Go to DynamicMemory folder
- The value of an array name is the address of the first element in the array
- The value of a pointer variable is an address
- If the pointer points to the same data type as the array element then the pointer and array name can be used interchangably (almost)
// ========== Arrays ==========
int scores[] {100, 95, 89};
cout << scores << endl; // Output: addreess
cout << *scores << endl; // Output: 100
// Yes, one can dereference array
// ========== Pointer ==========
int *score_ptr {scores};
cout << score_ptr << endl; // Output: same addreess as scores
cout << *scores_ptr << endl; // Output: 100
int scores[] {100, 95, 89};
int *score_ptr {scores};
cout << score_ptr[0] << endl; // Output: 100
cout << score_ptr[1] << endl; // Output: 95
cout << score_ptr[2] << endl; // Output: 89
int scores[] {100, 95, 89};
int *score_ptr {scores};
cout << score_ptr << endl; // Output: address (i.e. 0x61ff10)
// adding 1 size of address therefore Output isn't 0x61ff11 but 0x61ff14
cout << (score_ptr + 1) << endl; // Output: 0x61ff14
cout << (score_ptr + 2) << endl; // Output: 0x61ff18
int scores[] {100, 95, 89};
int *score_ptr {scores};
cout << *score_ptr << endl; // Output: 100
cout << *(score_ptr + 1) << endl; // Output: 95
cout << *(score_ptr + 2) << endl; // Output: 89
int array_name[] {1,2,3,4,5};
int *pointer_name {array_name};
Subscript Notation | Offset Notation |
---|---|
array_name [index] |
`*(array_name + index) |
pointer_name [index] |
`*(pointer_name + index) |
Go to ArraysAndPointers folder
-
Pointers can be ussed in
- Assignment expression
- Arithmetic expression
- Comparison expression
-
C++ allows pointer arithmetic
-
Pointer arithmetic only makes sense with raw arrays
-
Increments a pointer to point to the next array element (
++
)- i.e.
int_ptr++;
- i.e.
-
Decrements a pointer to point to the previous array element (
--
)- i.e.
int_ptr--;
- i.e.
-
Increment pointer by
n * sizeof(type)
(+
)int_ptr += n;
orint_ptr = int_ptr + n;
-
Decrement pointer by
n * sizeof(type)
(-
)int_ptr -= n;
orint_ptr = int_ptr - n;
- Determine the number of elements between the pointers
- Both pointers must point to the same data type, otherwise we get compiler error
int n = int_ptr2 - int_ptr1;
- Determine if two pointers point to the same location
- does NOT compare the data where they point!
- you must compare the referenced pointers
string s1 {"Frank"};
string s2 {"Frank"};
string *p1 {&s1};
string *p2 {&s2};
string *p3 {&s1};
// data is considered with pointer comparison but only locations
cout << (p1 == p2) << endl; // Output: False
cout << (p1 == p3) << endl; // Output: True
// compare data between two pointers
cout << (*p1 == *p2) << endl; // Output: True
cout << (*p1 == *p3) << endl; // Output: True
Go to PointerArithmetic folder
const
and Pointers
- There are several ways to qualify pointers using
const
- Pointers to constants
- Constant pointers
- Constant pointers to constants
- The data pointed to by the pointers is constant and cannot be changed.
- The pointer itself can change and point somewhere else
int high_score {100};
int low_score {65};
const int *score_ptr {&high_score};
*score_ptr = 86; // Error
score_ptr = &low_score; // OK
- The data pointed to by the pointers can be changed
- The pointer itself cannot change and point somewhere else
int high_score {100};
int low_score {65};
int *const score_ptr {&high_score};
*score_ptr = 86; // OK
score_ptr = &low_score; // Error
- The data pointd to by the pointer is constant and cannot be changed.
- The pointer itself cannot change and point somewhere else
int high_score {100};
int low_score {65};
const int *const score_ptr {&high_score};
*score_ptr = 86; // Error
score_ptr = &low_score; // Error
- Pass-by-reference with pointer parameters
- We can use pointers and the reference operator to achieve pass-by-reference
- The function parameter is a pointer
- The acutal parameter can be a pointer or address of a variable
// Function prototype
void double_data(int *int_ptr);
// Function definition
void double_data(int *int_ptr) {
*int_ptr *= 2;
// *int_ptr = *int_ptr * 2;
}
int main() {
int value {10};
cout << value << endl; // Output: 10
double_data( &value);
cout << value << endl; // Output: 20
}
Go to PassingPinters folders
- Function can also return pointers:
type *function();
- Should return pointers to
- Memory dynamically allocated in the function
- To data that was passed in
- Never return a pointer to a local function variable!
int *largest_int(int *int_ptr1, int *int_ptr2) {
if (*int_ptr1 > *int_ptr2)
return int_ptr1;
else
return int_ptr2;
}
int main() {
int a{100};
int b{200};
int *largest_ptr{nullptr};
largest_ptr = largest_int( &a, &b);
cout << *largest_ptr << endl; // Output: 200
return 0;
}
int *create_array(size_t size, int init_value = 0) {
int *new_storage {nullptr};
new_storage = new int[size];
for (size_t i{0}; i < size; ++i)
*(new_storage + i) = init_value;
return new_storage;
}
int main() {
int *my_array; // will be allocated by the function
my_array = create_array(100,20); // create the array
// use it
delete [] my_array; // be sure to free the storage
return 0;
}
int *dont_do_this() {
int size {};
...
return &size;
}
int *or_this() {
int size {};
int *int_ptr {&size};
...
return int_ptr;
}
Go to ReturnPointer folder
- Uninitialized pointers
- Dangling Pointers
- Not checking if new failed to allocate memory
- Leaking memory
int *int_ptr; // pointing anywhere - garbage
...
*int_ptr = 100; // Hopefully a crash
- Pointer that is pointing to released memory
- For example, 2 pointers point to the same data
- 1 pointer releases the data with delete
- The other pointer access the release data
- Pointer that points to memory that is invalid
- We saw this when we returned a pointer to a function local variable
- If
new
fails an exeception is thrown - We can use exception handling to catch exceptions
- Dereferencing a null pointer will cause your program to crash
- Forgetting to release allocated memory with delete
- If you lose your pointer to the storage allocated on the heap you have no way to get to that storage again
- The memory is orphaned or leaked
- One of the most common pointer problems
- An alias for a variable
- Must be initialized to a variable when declared
- Cannot be null
- Once initialized cannot be made to refer to a different variable
- Very useful as function parameters
- Might be helpful to think of a reference as a constant pointer that is automatically dereferenced
vector<string> stooges {"Larry", "Moe", "Curly"};
for (auto str : stooges)
str = "Funny"; // changes the copy
for (auto str : stooges)
cout << str << endl; // Output: Larry, Moe, Curly
vector<string> stooges {"Larry", "Moe", "Curly"};
for (auto &str : stooges)
str = "Funny"; // changes the actual
for (auto str : stooges)
cout << str << endl; // Output: Funny, Funny, Funny
vector<string> stooges {"Larry", "Moe", "Curly"};
for (auto const &str : stooges)
str = "Funny"; // compiler error
vector<string> stooges {"Larry", "Moe", "Curly"};
for (auto const &str : stooges)
cout << str << endl; // Output: Larry, Moe, Curly
Go to References folder
Refer to Section 11 for more details regarding passing references to functions
- values that have names and are addressable
- modifiable if they are not constants
// ========== L-values ==========
int x {100}; // x is an L-value
x = 1000;
x = 1000 + 20;
string name; // name is an L-value
name = "Frank";
// ========== NOT L-values ==========
// in fact they are R-values
100 = x; // 100 is NOT an L-value
(1000 + 20) = x; // (1000 + 20) is NOT an L-value
string name;
name = "Frank";
"Frank" = name; // "Frank" is NOT an L-value
- values that's not an L-value
- on the right-hand side of an assignment expression
- a literal
- a temporary which is intended to be non-modifiable
- values that are non-addressable and non-assignable
- R-values can be assigned to L-values explicitly
int x {100}; // 100 is an R-value
int y = x + 200; // (x + 200) is an R-value
string name;
name = "Frank"; // "Frank" is an R-value
int max_num = max(20,30); // max(20,30) is an R-value
int x {100};
int &ref1 = x; // ref1 is reference to L-value
ref1 = 1000;
int &ref2 = 100; // Error 100 is an R-value
// Similarly with pass-by-refernce
int square (int &n) {
return n*n;
}
int num {10};
square(num); // OK
square(5); // Error - can't reference R-value 5
Go to Debugger folder
When to use pointers vs. references parameters
-
Pass-by-value
- when the function does NOT modify the actual parameter, and
- the parameter is small and efficient to copy like simple types (
int
,char
,double
, etc.)
-
Pass-by-reference using a pointer
- when the function does modify the actual parameter, and
- the parameter is expensive to copy, and
- it is OK to the pointer is allowed a
nullptr
value
-
Pass-by-reference using a pointer to
const
- when the function does NOT modify the actual parameter, and
- the parameter is expensive to copy, and
- it is OK to the pointer is allowed a
nullptr
value
-
Pass-by-reference using a
const
pointer toconst
- when the function does modify the actual parameter, and
- the parameter is expensive to copy, and
- it is OK to the pointer is allowed a
nullptr
value, and - you don't want to modify the pointer itself
-
Pass-by-reference using a reference
- when the function does modify the actual parameter, and
- the parameter is expensive to copy, and
- the parameter will never be
nullptr
value
-
Pass-by-reference using a
const
reference- when the function does NOT modify the actual parameter, and
- the parameter is expensive to copy, and
- the parameter will never be
nullptr
value