C++ Pointers & Arrays
C++ Pointers & Arrays
CS 201
● Used to create and manipulate dynamic data structures (objects and arrays)
○ Book* B1 = new Book; // dynamically allocating an object
○ Book* B2 = new Book[5]; // dynamically allocating an array
○ Book B3[10]; // declaring an automatically allocated
// array, whose name is indeed a constant
// pointer (B3 is not a dynamically
// allocated array)
You will learn these declarations, allocations, necessary deallocations, and more
2
Pointers
● Pointers are variables that contain memory addresses as their values
○ Indirect reference: Through dereferencing if you know the address of this memory location
■ You do not need to declare each variable separately
● For example, you may declare an array corresponding to consecutive memory
locations and you can access all these memory locations using the array name
and the subscript operator (which indeed uses dereferencing)
■ You can access an out-of-the-scope variable through dereferencing
This makes pointers very powerful (as you can keep address values only in pointer variables)
3
Pointers
● To declare a pointer, use * before the variable name in a declaration line
int* p1; // p1 is a pointer (or points) to an integer
Book *B1, *B2; // B1 and B2 are pointers (or point) to
// Book objects
double *d1, d2, *d3; // d1 and d3 are pointers (or point) to
// doubles (but d2 is just a double variable)
int y;
int* yptr; // yptr is a pointer to an integer (its value is garbage now)
yptr = &y; // & takes the address of y and this address is assigned to yptr
5
Dereferencing operator (*)
● Returns the memory location whose address is kept in its operand
● Can only be applied to an address value (to a pointer variable)
● Its return value can be used as an rvalue or an lvalue
● The compiler should know the data type of the memory location pointed by its operand
○ Since it considers the operand’s value as the starting address but cannot know how many bytes to
access without knowing this data type
○ Thus, this operator cannot be applied to void* (void pointers cannot be dereferenced)
VERY IMPORTANT: Dereferencing a pointer that does not keep the address of a specific location in memory
may cause a fatal run-time error. It may accidentally modify important data and cause the program to run to
completion, possibly with incorrect results
int y, *yptr;
*yptr = 10; // THIS MAY CAUSE A PROGRAM CRASH -- RUN-TIME ERROR
yptr = &y;
y = 5; // direct access to y
*yptr = 10; // indirect access to y (by dereferencing yptr), *yptr is an lvalue
cout << *yptr; // indirect access to y (by dereferencing yptr), *yptr is an rvalue
6
Pointer operators int a, *b, **c;
double x = 3.4, *y = &x; // y points to x
} // swap c and d
// swaps the id data members of two Student objects swapIntegerPointers( ?, ? );
// (assuming that the Student class has been
// defined and it has a public id data member) // swap the ids of S and R
void swapStudentIds( ? S1, ? S2 ) { swapStudentIds( ?, ? );
} // swap S and R
// swaps two Student objects swapStudentObjects( ?, ? );
void swapStudentObjects( ? S1, ? S2 ) {
// swap X and Y
} swapStudentObjectPointers( ?, ? );
// swaps two Student object pointers
void swapStudentObjectPointers( ? P1, ? P2 ) { return 0;
}
}
11
Principle of least privilege
● Functions should not be given the capability to modify its parameters unless
it is absolutely necessary
int data;
int* ptr;
ptr = &data; // pointer ptr can be modified
*ptr = 5; // dereferencing operator can be applied on ptr
12
Principle of least privilege
● Non-constant pointer to constant data (using const qualifier)
○ Pointer value can be modified
○ Data value cannot be modified through dereferencing this pointer
int data;
const int* ptr;
ptr = &data; // pointer ptr can be modified
// *ptr = 5; Compile-time error: dereferencing operator CANNOT be
// applied on ptr
Another example:
void display( const int* arr, const int size ) {
for ( int i = 0; i < size; i++ )
cout << arr[i] << endl;
// *arr = 5; Compile-time error: dereferencing operator CANNOT be
// applied on ptr
// arr[2] = 2; Compile-time error: subscript operator uses dereferencing
}
13
Principle of least privilege
● Constant pointer to non-constant data (using const qualifier)
○ Pointer value cannot be modified
■ Must be initialized at its declaration
■ Always points to the same location
■ Default for an automatically allocated array name (but not for a dynamically allocated array
name)
○ Data value can be modified through dereferencing this pointer
int data;
int*const ptr = &data;
*ptr = 5; // dereferencing operator can be applied on ptr
// ptr = nullptr; Compile-time error: ptr CANNOT be modified
// int*const ptr2; Compile-time error: ptr2 must be initialized
Another example:
int arr[5]; // automatically allocated array declaration
// arr = new int [4]; Compile-time error: automatically allocated array
name is constant 14
Dynamic memory management
● Enables programmers to allocate and deallocate memory for any built-in or
user-defined type using the new and delete operators
● Operator new
○ Allocates memory for an object or an array from the heap (free-store) at execution time
○ Returns the starting address of the allocated memory
○ Calls a constructor if it is called for an object (or for all objects in an array of objects)
15
Dynamic memory management
● Enables programmers to allocate and deallocate memory for any built-in or
user-defined type using the new and delete operators
● Operator delete
○ Deallocates (releases) the memory allocated for an object or an array from the heap
○ So that, this deallocated memory can be reused for other allocations
○ Calls the destructor if it is called for an object (or for an array of objects)
○ When [] is put after delete, it calls the destructor for every object in the array. Otherwise, it
calls the destructor just for the first object.
MEMORY LEAK happens when you do not release the memory which is no longer
needed. There is no built-in garbage collection in C or C++.
16
Arrays
Arrays are data structures that contain values of any non-reference data type.
Array items are of the same type and kept in consecutive memory locations.
In C++, there are two ways to define arrays void foo() {
// Automatically allocated array
● An automatically allocated array through // declaration
// Neither the value of arr1 nor
declaration // its size can be changed.
○ Always remains the same size once created int arr1[5];
○ Its name is a constant pointer that keeps the // Dynamic array allocation
starting address of the array items // Array items are dynamically
○ If it is a local declaration, memory for array // allocated and arr2 keeps the
// starting address of the first
items is taken from the stack
// item. Throughout the execution,
// this array can be released and
● A dynamically allocated array // re-allocated.
○ Memory for array items is taken from the heap int* arr2 = new int[4];
delete[] arr2;
using the new operator
arr2 = new int[5];
○ Starting address of the allocated memory is delete[] arr2;
kept in a pointer whose value can be changed } 17
Declaring automatically allocated arrays
● Array size should be specified at its declaration
○ In standard C++, the size should be either an integer literal or a constant integer variable
○ It should be positive
○ It cannot be changed throughout the execution
const int arrSize = 5; // B1 and B2 are arrays of Book objects. They are
Book B1[ arrSize ]; // declared as automatically allocated arrays.
Book B2[ 12 ]; // If these are local declarations, their items
// are kept in the stack.
○ If the list contains less initializers, the remaining array items are initialized with 0
int n2[ 10 ] = { 5 };
○ If the list contains more initializers, the compiler gives a compile-time error
int n3[ 3 ] = { 0, 10, 30, 50 };
19
Subscript operator
● It provides a direct access to an individual array item whose position is
specified as an integer expression in square brackets
○ Very efficient as the access time is independent of the position specified in square brackets
In order to understand how the subscript operator works, you need to understand pointer
(address) arithmetic 20
Pointer (address) arithmetic
● Adding/subtracting an integer to/from a pointer
○ Using ++ , + , += , - , --, -= operators
● Subtracting one pointer from another (two pointers should be of the same type)
Example: Consider the following declarations on a machine that uses 4 bytes to represent int
int arr1[ 4 ];
int* arr2 = new int[ 5 ]; Pointer arithmetic is meaningless if not performed
int* ptr = arr2 + 3; on a pointer to an array. It is a logic error.
arr1 : address of the first item in the declared array (whose index is 0)
arr2 : address of the first item in the dynamically allocated array (whose index is 0)
ptr : address obtained by adding the number of bytes to represent 3 integers (in our example,
12 bytes) to arr2 . Thus, it gives the address of the fourth array item (whose index is 3)
ptr - arr2 : subtracts these two pointers and then gives how many integers (as they are integer
pointers) one can keep in the difference (in our example, 3)
21
Subscript operator
● C++ has no array bounds checking
○ C++ does not prevent the computer from referring to memory that has not been allocated
○ That is, C++ does not prevent the computer from dereferencing an unallocated memory
■ No compile-time error
■ May cause execution-time error (program crash)
■ But always logic error
int A[ 4 ];
int* B = new int[ 5 ];
// Each statement below always has a logic error and may cause a program crash.
// However, a program crash may occur in one run and may not in another. Thus,
// it is hard to detect statements with this logic error. Such statements often
// result in changes to the value of an unrelated variable or a fatal error
// that terminates the program execution.
A[ 5 ] = 30; // it is equivalent to *(A + 5) = 30
B[ 50 ] = 40; // it is equivalent to *(B + 50) = 40
22
Passing arrays to functions
// Write a function to initialize the
// array items with the value of C
● Functions can take arrays as arguments void init( int* A, int size, int C ) {
for ( int i = 0; i < size; i++ )
● Function parameter list must specify an A[i] = C;
}
array as a parameter int main() {
int A1[ 40 ];
int *A2 = new int[ 60 ];
void init( int* A, int size, int C ) {
void init( int A[], int size, int C ) { init( A1, 40, 10 );
void init( int A[40], int size, int C ){ init( A2, 60, 100 );
delete[] A2;
return 0;
}
23
Passing arrays to functions
// Write a function to initialize the
// array items with the value of C
● When this function is called, only an void init( int* A, int size, int C ) {
for ( int i = 0; i < size; i++ )
address will be passed A[i] = C;
○ This will be considered as the starting address of }
an array in the called function int main() {
int A1[ 40 ];
○ So the function knows where the array starts but int *A2 = new int[ 60 ];
does not know where it ends
○ Thus, the array size should be defined as init( A1, 40, 10 );
init( A2, 60, 100 );
another function parameter
// Initialize the items in the
● init function used pass-by-value to // first half of the array with 1
// and those in the second half
define its pointer (array) parameter A // of the array with -1
○ Thus, changes in the value of this parameter init( A2, 30, 1 );
cannot be seen after returning from the function init( A2 + 30, 30, -1 );
○ However, changes in the values of array items delete[] A2;
will be seen as it accesses the array items return 0;
through dereferencing (subscript operator) }
24
Dynamic memory management for a single object
● An object can be allocated in two different ways: (1) through object variable
declaration and (2) by dynamic allocation using the new operator
#include <iostream> void foo() {
using namespace std;
Book X( 10 ); // X is a local object allocated from the stack
class Book { // through variable declaration.
public:
Book( int i = 0 ); Book* Y; // Y is a local pointer variable stored in the
void displayId(); // stack. It is not an object but can keep the
// starting address of memory allocated for
private: // an object.
int id;
}; Y = new Book( 20 ); // The new operator dynamically allocates
Book::Book( int i ) { // memory for an object from the heap and
id = i; // returns the starting address of this memory.
} // The assignment operator puts this returned
void Book::displayId() // address inside pointer Y.
{
cout << id << endl; // continues with the next slide
} 25
Dynamic memory management for a single object
● An object can be allocated in two different ways: (1) through object variable
declaration and (2) by dynamic allocation using the new operator
#include <iostream> // ... void foo() function continues
using namespace std;
X.displayId(); // It invokes displayId() for object X.
class Book {
public:
Book( int i = 0 ); // Y.displayId(); Compile-time error since Y is not an object.
void displayId();
33
Example: Write a global function that takes an array and its size as input and creates an
exact copy of this input array and returns it (use the parameter list to return the output)
void deepCopy_1( const double* arr, const int arrSize, double*& output ) {
output = new double [ arrSize ];
for (int i = 0; i < arrSize; i++)
output[ i ] = arr[i];
}
void deepCopy_2( const double* arr, const int arrSize, double** output ) {
*output = new double [ arrSize ];
for (int i = 0; i < arrSize; i++)
(*output)[ i ] = arr[i];
}
If you need a separate copy of an array (array items), use DEEP COPY, do not shallow copy the
array. Otherwise, you will have two pointer variables (array names) that point to the same array.
That is, if you change an item of one array, you will also see this change in the other array.
34
Example: Write a global function that partitions an array such that its first part contains items
smaller than a pivot value and its second part contains greater items (use pointer iterators)
void partition( int arr[], int arrSize, int pivot ) {
int* first = arr;
int* second = arr + arrSize - 1; // Equivalent to second = &arr[ arrSize - 1 ];
int main() {
Student* arr = new Student[ 10 ];
// ...
if ( findStudent( arr, 10, 2000 ) == nullptr ) // Equality operator
cout << "Unsuccessful search" << endl;
else
cout << "Successful search" << endl;
40
C++ string class
#include <iostream>
● string objects are mutable #include <string>
using namespace std;
● string class provides many
int main() {
operators and member functions
○ subscript operator [] to access the string s1 = "happy", s2 = "birthday", s3;
character at a specified index // subscript operator
○ concatenation operators + and += s1[0] = 'H';
cout << "s1: " << s1 << endl;
○ comparison operators ==, !=, <, cout << "first char: " << s1[0] << endl;
<=, >=, > to compare two strings
○ member functions such as // string concatenation
s1 += s2;
append(), insert(), erase() cout << "s1: " << s1 << endl;
○ cin >> and cout << input/output cout << "s2: " << s2 << endl;
operations
cin >> str reads just one word into // continues with the next slide
str. To read the entire line, use the global
function getline( cin, str ) .
41
C++ string class
// ... int main() function continues
● string objects are mutable
● string class provides many // string comparison
// Outputs of the following statements
operators and member functions // are given as comments. The program
○ subscript operator [] to access the //
//
uses 0 to display false and 1 to
display true.
character at a specified index
○ concatenation operators + and += cout << ( s1 == s2 ) << endl; // 0
cout << ( s1 != s2 ) << endl; // 1
○ comparison operators ==, !=, <, cout << ( s1 > s2 ) << endl; // 0
<=, >=, > to compare two strings cout << ( s1 >= s2 ) << endl; // 0
○ member functions such as cout << ( s1 < s2 ) << endl; // 1
cout << ( s1 <= s2 ) << endl; // 1
append(), insert(), erase()
○ cin >> and cout << input/output
operations
cin >> str reads just one word into // continues with the next slide
str. To read the entire line, use the global
function getline( cin, str ) .
42
C++ string class
// ... int main() function continues
● string objects are mutable
cout << "Enter your name: ";
● string class provides many cin >> s3;
// cin reads just one word (reads the
operators and member functions // console input until it encounters a
○ subscript operator [] to access the // white space). To read the entire line
// use getline( cin, s3 ) global function
character at a specified index
○ concatenation operators + and += // some member functions
cout << "length of s2: " << s2.length();
○ comparison operators ==, !=, <, cout << "length of s2: " << s2.size();
<=, >=, > to compare two strings
○ member functions such as s1.append( " " + s3 );
cout << "s1: " << s1 << endl;
append(), insert(), erase() s1.insert( 5, " and happy " );
○ cin >> and cout << input/output cout << "s1: " << s1 << endl;
operations s1.erase( 5, 4 );
cout << "s1: " << s1 << endl;
cin >> str reads just one word into
str. To read the entire line, use the global return 0;
function getline( cin, str ) . }
43
Example: What is the output of the following program?
#include <iostream>
#include <string>
using namespace std;
y += x[0];
y[2] = 'e';
cout << "y: " << y << endl;
}
int main() {
string s1 = "python", s2 = "java";