0% found this document useful (0 votes)
23 views

C++ Pointers & Arrays

This document discusses pointers and arrays in C++. It covers how pointers are used to simulate pass-by-reference and dynamically allocate memory. Pointers contain memory addresses as their values and can be used to indirectly access memory locations through dereferencing. The address-of and dereferencing operators are also explained.

Uploaded by

alhussainraad0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

C++ Pointers & Arrays

This document discusses pointers and arrays in C++. It covers how pointers are used to simulate pass-by-reference and dynamically allocate memory. Pointers contain memory addresses as their values and can be used to indirectly access memory locations through dereferencing. The address-of and dereferencing operators are also explained.

Uploaded by

alhussainraad0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

Pointers and Arrays

CS 201

This slide set covers pointers and


arrays in C++. You should read
Chapter 8 from your Deitel &
Deitel book.
Pointers

● Used to simulate pass-by-reference (without using a reference parameter)


Especially important in C, since it does not support the pass-by-reference mechanism

● 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

● There are two ways of accessing a memory location


○ Direct reference: Through the name of a variable associated with this memory location, which
requires declaring each variable separately and being in its scope

○ 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)

● Operators closely related with pointers


○ Address (or address-of) operator
○ Dereferencing (or indirection) operator

● Other operators also defined for pointers


○ Assignment operator
○ Equality operators Relational and arithmetic operators are
○ Relational operators meaningless if their operands do not point to
○ Arithmetic (addition and subtraction) operators the members of the same array
4
Address operator (&)
● Returns the memory address of its operand
● Can be applied to any memory location (to a variable of any data type)
● Its return value can be used only as an rvalue

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

// return value of the address operator cannot be used as an lvalue


// &y = yptr; COMPILE-TIME ERROR

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

● Assignment operator (=) assigns b = &a; // b points to a


c = &b; // c points to b
the address values to pointers of the a = 5; // direct access to a
**c = 20; // indirect access to a
same type
// through double dereferencing

// Compile-time errors: RHS and LHS


● Its right-hand-side can be // should be of the same type
○ A pointer of the same type // b = y; b is int* and y is double*
○ nullptr (indicating that it is an invalid // y = b; y is double* and b is int*
memory address) // y = &a; y is double* and &a is int*
// c = &a; c is int** and &a is int*
■ pointers that store nullptr should
// *c = *b; *c is int* and *b is int
not be dereferenced
○ A value obtained by the address operator // Compile-time error: Arbitrary values
(but cannot be an arbitrary value) // cannot be assigned to pointers
// b = 100000;

// The following is a valid assignment


// since *b is int and *y is double
*b = *y;
7
Do not confuse declarations and operators
// all * given in red are pointer declarations (variables, parameters, or return types)
// all * given in blue are dereferencing operators
// all & given in green are reference declarations (variables, parameters, or return types)
// all & given in pink are address operators

int* bar(int* x, int& a, int** v) {


// ...
}
int& foo( ) {
// ...
}
int main() {
int a, *b = &a, **c = &b, *d = *c, *e = b, f = **c, &g = a;
*b = 3;
**c = a;
b = *c;
c = &d;
b = bar(&a, f, &d);
e = bar(*c, *b, c);
a = foo( );
} 8
What is the output of the following program?
int main() { Output:
int x = 4, y = 5, *a = &x, *b = a, **c = &b;
Address of x: 0x7fff57a1c9c8
cout << "Address of x: " << &x << endl;
Address of y: 0x7fff57a1c9c4
cout << "Address of y: " << &y << endl;
Address of a: 0x7fff57a1c9b8
cout << "Address of a: " << &a << endl;
Address of b: 0x7fff57a1c9b0
cout << "Address of b: " << &b << endl;
Address of c: 0x7fff57a1c9a8
cout << "Address of c: " << &c << endl;
**c: 4
cout << "**c: " << **c << endl; *c : 0x7fff57a1c9c8
cout << "*c : " << *c << endl; c : 0x7fff57a1c9b0
cout << "c : " << c << endl; &c : 0x7fff57a1c9a8
cout << "&c : " << &c << endl << endl;
**c: 3
b = &y; *c : 0x7fff57a1c9c4
*b = 3; c : 0x7fff57a1c9b0
cout << "**c: " << **c << endl; &c : 0x7fff57a1c9a8
cout << "*c : " << *c << endl;
cout << "c : " << c << endl; *&c: 0x7fff57a1c9b0
cout << "&c : " << &c << endl << endl; &*c: 0x7fff57a1c9b0
*&x: 4
cout << "*&c: " << *&c << endl;
cout << "&*c: " << &*c << endl;
cout << "*&x: " << *&x << endl;
// cout << "&*x: " << &*x; compile-time error
return 0;
} 9
Getting effects of pass-by-reference using pointers
● Using a pointer parameter and the dereferencing operator, one can get the
effects of pass-by-reference without using a reference parameter
○ Caller function passes the address of a
#include <iostream>
variable that it wants to change using namespace std;
○ Called function takes this address in a void nextIntByPointer( int* );
pointer parameter
int main() {
○ Called function changes the variable’s int x = 30;
nextIntByPointer( &x );
value by indirectly accessing it through the
cout << x << endl;
dereferencing operator return 0;
}
void nextIntByPointer( int* y ) {
BE CAREFUL: Here the aim is NOT to
(*y)++;
change the value of the parameter y (if }
it was, it would not work). But, the aim
is to change the value of the location nextIntByPointer uses pass-by-value
(a copy of the x’s address is passed to the function)
where y points to.
10
Implement and call them with and without using reference parameters
// swaps two integers int main() {
void swapIntegers( ? i1, ? i2 ) { int a, b, *c = &a, *d = &b;
Student S, R, *X = &S, *Y = &R;
}
// swaps two integer pointers // swap a and b
void swapIntegerPointers( ? p1, ? p2 ) { swapIntegers( ?, ? );

} // 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

● Non-constant pointer to non-constant data (no const qualifier is used)


○ Pointer value can be modified
○ Data value can be modified through dereferencing this pointer

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)

Book* B1 = new Book( 2001, 20.5 ); // dynamically allocates a single object


Book* B2 = new Book[5]; // dynamically allocates an array of
// five objects
double* d = new double[4]; // dynamically allocates an array of
// four doubles

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.

delete B1; // deallocates a single object


delete[] B2; // deallocates an array of objects
delete[] d; // deallocates an array of doubles

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.

// int no = 3; Compile-time error: In standard C++, the size used in


// Book B3[ no ]; an array declaration should be a literal or a constant
// variable. no is not a constant variable.

// delete[] B2; Compile-time error: B2 is not a dynamic array.

● Multiple arrays of the same type can be declared in a single declaration


int D[ 5 ], E, F[ 6 ]; // D and F are integer arrays whereas E is an integer.
18
Initializing automatically allocated arrays
● If it is a local array declaration, array items have garbage values
● Array items can be initialized at array declaration using an initializer list
○ You may omit the array size if you use an initializer list. In this case, the compiler
determines the array size based on the number of initializers.
int n1[ ] = { 10, 20, 30, 40, 50 };

○ 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

● Regardless of whether it is an automatically allocated array or a dynamically


created array, the subscript operator dereferences the address calculated
using the pointer value (or the array name) and the value specified in square
brackets
int A[ 4 ];
int* B = new int[ 5 ];
A[ 2 ] = 10; // it is equivalent to *(A + 2) = 10
B[ 3 ] = 20; // it is equivalent to *(B + 3) = 20

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 );

// Initialize the items in the


All three are equivalent. In the last one, the size // first half of the array with 1
specified in square brackets will be ignored by the // and those in the second half
// of the array with -1
compiler.
init( A2, 30, 1 );
init( A2 + 30, 30, -1 );

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();

private: (*Y).displayId(); // It first accesses the dynamically allocated


int id; // object by dereferencing pointer Y and then
}; // invokes displayId() for the accessed object.
Book::Book( int i ) {
id = i;
} Y->displayId(); // The arrow operator is defined for pointers.
void Book::displayId() // Y->displayId() is equivalent to
{ // (*Y).displayId()
cout << id << endl;
} 26
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;
delete Y; // The delete operator releases the allocated
class Book { // memory whose starting address is kept in
public: // pointer Y. But, it does not release the
Book( int i = 0 ); // pointer itself.
void displayId();
// delete X; Compile-time error since X is not an address.
private: delete &X; // Program crash since X is not dynamically
int id; // allocated. The delete operator should only
}; // be called for the objects that are
Book::Book( int i ) { // dynamically allocated from the heap.
id = i;
} Y = &X; // It assigns the address of X to Y.
void Book::displayId() Y->displayId();
{ delete Y; // Program crash since Y does not keep the
cout << id << endl; // address of an object that is dynamically
} // allocated from the heap. 27
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;
Y = new Book( 30 );
class Book { Y = new Book( 40 ); // Memory leak since the object allocated by
public: // the previous statement is not released and
Book( int i = 0 ); // cannot be accessed anymore.
void displayId( );
delete Y;
private: delete Y; // Program crash since the memory pointed by
int id; // Y has already been released in the previous
}; // statement. Note that some runs may cause
Book::Book( int i ) { // this program to crash while some others
id = i; // may not. However, there is always a logic
} // error here and you have to fix this problem.
void Book::displayId()
{ }
cout << id << endl;
} 28
Dynamic memory management for a single object
Write a function that allocates a Test object and returns it to the caller
class Test { Test returnObject( int oid ){
public: Test obj( oid ); // Local object declaration
Test( int i = 0 ); return obj; // This statement returns a
void setNext( Test* nval ); // copy of the local object to
Test* getNext( ); // the caller function
}
private: int main(){
int id; Test T = returnObject( 300 );
Test* next;
}; // Compile-time error: T is an object not an address
Test::Test( int i ) { // delete T;
id = i;
next = nullptr; return 0;
} }
void Test::setNext( Test* nval ){
next = nval;
} // BE CAREFUL: That is different than Java!!!
Test* Test::getNext() {
return next;
}
29
Dynamic memory management for a single object
Write a function that dynamically allocates a Test object and returns its address to the caller
class Test { Test* returnAddress( int oid ){
public: Test* A = new Test( oid );
Test( int i = 0 ); return A; // Returns address A, which
void setNext( Test* nval ); // keeps the address of the
Test* getNext( ); // dynamically allocated object
}
private: int main(){
int id; Test* first = new Test( 400 );
Test* next; first->setNext( returnAddress( 500 ) );
};
Test::Test( int i ) { // call the delete operator to avoid memory leak
id = i; delete first->getNext();
next = nullptr; delete first;
}
void Test::setNext( Test* nval ){ return 0;
next = nval; }
}
Test* Test::getNext() {
return next;
}
30
Dynamic memory management for a single object
Write a function that dynamically allocates a Test object and returns its address to the caller
class Test { // Logic error: Memory for object A is taken from the
public: // stack and is released when the function returns.
Test( int i = 0 ); // In general, do not return the address of any stack
void setNext( Test* nval ); // memory associated with a local variable.
Test* getNext( ); Test* incorrectReturnAddress( int oid ){
Test A( oid );
private: return &A;
int id; }
Test* next; int main(){
}; Test* ptr = incorrectReturnAddress( 600 );
Test::Test( int i ) {
id = i; delete ptr; // Program crash: ptr keeps the address of
next = nullptr; // a released stack memory (which was not
} // dynamically allocated from the heap).
void Test::setNext( Test* nval ){ // The delete operator should only be
next = nval; // called for the objects that are
} // dynamically allocated from the heap.
Test* Test::getNext() {
return next; return 0;
} }
31
Example: Write a global function that returns a double array with the size of 10
// createArray_1 returns the array as a return value int main() {
double* createArray_1( ) { double* D;
return new double[ 10 ];
} D = createArray_1();
// createArray_2 returns the array from the parameter list delete[] D;
// (using a reference parameter)
void createArray_2( double*& arr ) { createArray_2( D );
arr = new double[ 10 ]; delete[] D;
}
// createArray_3 returns the array from the parameter list createArray_3( &D );
// (without using a reference parameter but simulating delete[] D;
// pass-by-reference using a pointer)
void createArray_3( double** arr ) { return 0;
*arr = new double[ 10 ]; }
}
// What is wrong with the following two functions?
// void incorrectCreateArray_1( double* arr ) {
// arr = new double[ 10 ];
//}
// double* incorrectCreateArray_2( ) {
// double arr[ 10 ];
// return arr;
// } 32
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)
// shallowCopy_1 and shallowCopy_2 create a shallow copy of the input array
// What is wrong with the shallow copy operation?

void shallowCopy_1( double* arr, const int arrSize, double*& output ) {


output = arr;
}

void shallowCopy_2( double* arr, const int arrSize, double** output ) {


*output = arr;
}

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 ];

while ( first <= second ) { // Relational operator defined on pointers


// Meaningless if its operands do not point to
// the items of the same array
if ( *first >= pivot && *second < pivot ) {
int temp = *first;
*first = *second;
*second = temp;
first++;
second--;
}
else {
if ( *first < pivot )
first++;
if ( *second >= pivot )
second--;
}
}
} 35
Example: Write a global function that searches a Student object whose id is equal to
the given key in the input array and returns the address of this object
Student* findStudent( const Student* stuArr, const int stuSize, const int key ) {
for ( int i = 0; i < stuSize; i++ )
if ( stuArr[i].getId() == key )
return stuArr + i; // same as &(stuArr[i])
return nullptr;
}

int main() {
Student* arr = new Student[ 10 ];
// ...
if ( findStudent( arr, 10, 2000 ) == nullptr ) // Equality operator
cout << "Unsuccessful search" << endl;
else
cout << "Successful search" << endl;

delete[] arr; // Deallocates the student objects in the array


// When [] is put after delete, it calls the destructor
// for each object in the array. Otherwise, it calls the
// destructor only for the first object
// (we will see it again after discussing destructors)
return 0;
} 36
Example: Extend the GradeBook class such that it keeps the grades of
multiple students (use an automatically allocated array)
class GradeBook { static data members
public:
(also called class variables)
const static int studentNo = 10;
GradeBook( int, const int [] ); ● One copy is shared among all
// ... objects of the class
private:
● Each object does not have a
int courseNo;
int grades[ studentNo ];
separate copy
}; ● Can be accessed even when no
GradeBook::GradeBook( int no, const int S[] ) { object of the class exists using
courseNo = no; the class name followed by the
for ( int i = 0; i < studentNo; i++ )
binary scope resolution operator
grades[i] = S[i];
}
int main() {
int A[ GradeBook::studentNo ];
GradeBook G( 201, A );
return 0;
}
37
One final remark
● sizeof operator returns the size of its operand in bytes
○ Can be used with a variable name, a type name, or a constant value
○ Number of bytes to store a particular type may vary from one computer system to another

● It is used to make allocations in C int incorrectUse( int A[] ) {


int size = sizeof(A) / sizeof(int);
○ Some students may try to use it to get the size
return size;
of an array. However, this mostly causes an }
incorrect result. int main() {
int A[] = { 1, 2, 3, 4, 5} ;
// Use of sizeof in C style allocations int* B = new int[ 5 ];
int *A1, *A2, *A3;
double *D; cout << sizeof(A) / sizeof(int);
cout << sizeof(B) / sizeof(int);
A1 = (int* ) malloc( 20 * sizeof(int) ); cout << incorrectUse(A);
A2 = (int* ) calloc( 7, sizeof(int) ); cout << incorrectUse(B);
A3 = (int* ) realloc( A3, 10 * sizeof(int) ); return 0;
D = (double* ) calloc( 5, sizeof(double) ); } 38
C-style strings
● In C++, there is a string class int main() {
// char array declaration using an
● In C, character pointers (arrays) are // initializer list, equivalent to
// char a[] = {'H', 'i', '\0'};
used to store and manipulate strings char a[] = "Hi";
○ <cstring> header file contains many
cout << a << endl;
C-style string manipulation functions
a[ 1 ] = '\0';
○ cin >> and cout << work differently for cout << a << endl;
character pointers (different than how they cin >> a;
work on pointers of the other types) cout << a << endl;
○ They all assume that a C-style string ends
char *b = new char[ 10 ];
with a null character (otherwise they will not cin >> b;
work correctly) cout << b << endl;
delete[] b;
When used with cin >>, the array should be large
enough to store the string typed at the keyboard. return 0;
Otherwise, it may result in data loss (logic error) and }
may cause program crashes (run-time errors). 39
<cstring> functions

©1992-2014 by Pearson Education, Inc. All Rights Reserved.

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;

void fun( string& x, string y ) {


x.erase( 0, 2 );
x.insert( 2, y.substr( 1, 2 ) );
x.append( "***" );
cout << "x: " << x << endl;

y += x[0];
y[2] = 'e';
cout << "y: " << y << endl;
}
int main() {
string s1 = "python", s2 = "java";

cout << "s1: " << s1 << endl;


cout << "s2: " << s2 << endl;
fun( s1, s2 );
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
return 0;
} 44

You might also like