2. Indirection
For declarations like
double doubleVar; // initial value
unknown
char charVar = 'A';
int intVar = 1234;
the compiler constructs the object being declared
1. Allocates memory needed for that type of object
2. Associates the object's name with that memory
3. and, possibly, initializes that memory
3. Addresses
& is the address operator in C++
&variable is the address (actual memory location) of variable
To make addresses more useful, C++ provides pointer variables.
Definition: A pointer variable (or simply pointer) is a variable
whose value is a memory address.
Declarations:
Type * pointerVariable
declares a variable named pointerVariable
that can store the address of an object of type Type.
4. Pointers
#include <iostream>
using namespace std;
int main()
{
int i = 11, j = 22;
double d = 3.3, e = 4.4;
// pointer variables that:
int *iptr, *jptr; // store addresses of ints
double *dptr, *eptr; // store addresses of doubles
iptr = &i; // value of iptr is address of i
jptr = &j; // value of jptr is address of j
dptr = &d; // value of dptr is address of d
eptr = &e; // value of eptr is address of e
cout << "&i = " << (void*)iptr << endl
<< "&j = " << (void*)jptr << endl
<< "&d = " << (void*)dptr << endl
<< "&e = " << (void*)eptr << endl;
}
5. Pointers
#include <iostream>
using namespace std;
int main()
{
int i = 11, j = 22;
double d = 3.3, e = 4.4;
// pointer variables that:
int *iptr, *jptr; // store addresses of ints
double *dptr, *eptr; // store addresses of doubles
iptr = &i; // value of iptr is address of i
jptr = &j; // value of jptr is address of j
dptr = &d; // value of dptr is address of d
eptr = &e; // value of eptr is address of e
cout << "&i = " << (void*)iptr << endl
<< "&j = " << (void*)jptr << endl
<< "&d = " << (void*)dptr << endl
<< "&e = " << (void*)eptr << endl;
}
6. Dereferencing
dereferencing (or indirection) operator *
used to access value stored in a location.
For an expression of the form
*pointerVariable
the value produced is the
value stored in the address held in pointerVariable
int value = 101;
int *valueptr = &value;
(*valueptr)++; // what’s value now?
7. Anonymous variables
A variable is a memory location
A named variable has a name associated with its memory location,
so that this memory location can be accessed conveniently.
int count = 0;
An anonymous variable has no name associated with its memory
if the address of that memory location is stored in a pointer variable,
then the variable can be accessed indirectly using the pointer.
int *intptr = new int(12); // pointer variable
//is a named variable
//the integer whose value (12) it holds is not
8. new operator
new Type
when executed, this expression:
i. allocates a block of memory big enough for object of type Type,
and
ii. returns the starting address of that block of memory.
int *ptr1 = new int; // integer allocated
int *ptr2; // no integer allocated
*ptr1 = 103; // anonymous integer initialized
ptr2 = new int(104); // anonymous integer allocated
// and initialized!
9. new operator
new must be used each time a memory allocation is needed.
double *dptr, *eptr;
dptr = eptr = new double; // one double allocated
eptr = new double allocates memory for a double
and assigns its address to eptr,
dptr = eptr simply assigns this same address to dptr
does not allocate new memory
*dptr = 3.14159; // initializes double whose
*eptr = 0.1423; // address held in dptr & eptr
cout << *dptr << *eptr << endl;
What’s output? (0.1423 0.1423)
10. new operator
new receives its memory allocation from pool of available memory
called the heap or free store
usually located between a program and its run-time stack
run-time stack grows each time a function is called,
possible for it to overun the heap
usually due to infinite or too deeply nested recursion
also possible for the heap to overun the run-time stack
due to too many (or too large) new operations
Run-Time
Stack
Heap
Program
statements
11. new operator
When the heap has been exhausted, new returns the value 0
(called the null address or null pointer).
Common to picture a null pointer variable
using the electrical engineering ground symbol:
dptr
double *dptr = new double;
…
if (dptr == 0) // check if pointer has null value
{
cerr << "n*** No more memory!n";
exit(-1);
}// dereferencing a null pointer
// produces a segmentation fault!!
12. delete operator
Run-time stack grows each time a function is called
shrinks again when that function terminates.
An analogous method needed to reclaim memory allocated by new,
i.e. shrink the heap when an anonymous variable is no longer needed.
Otherwise a memory leak (memory allocated but not usable) results.
For this, C++ provides the delete operation:
delete pointerVariable
which deallocates the block of memory
whose address is stored in pointerVariable
13. Run-time arrays
double a[50]; // capacity fixed at compile time
// declares an array with exactly 50 elements.
adequate if a fixed-capacity array can be used to store all needed data
often is not true because the size of needed data sets vary.
So, either:
— Make array's capacity large enough to handle biggest data set
— an obvious waste of memory for smaller data sets.
or
— Change capacity in array's declaration in source code and
recompile (yuck!).
14. Run-time arrays
specify capacity of the array at run time
array of that capacity then allocated and used.
new Type[N]
where N is an integer expression,
allocates an array with N elements, each of type Type;
returns the base address of that array.
0 1 2 3 4 . . . N-1
0x#####
Returned by new
This allocation occurs when this expression is executed,
i.e. at run-time, not at compile-time.
user can input a capacity
program can allocate an array with exactly that many elements!
15. Run-time arrays
declaration of a run-time-allocated array is simply a pointer declaration:
Type * arrayPtr;
int numItems;
double dub[20]; // fixed-size compile-time array
double *dubPtr; // a pointer to a (run-time)array
cout << "How many numbers do you have to process? ";
cin >> numItems;
dubPtr = new double[numItems]; // dynamic array
address returned by new must be assigned to a pointer of type Type
int *intptr;
intptr = new double[numItems]; //error!!
16. Run-time arrays
use the delete operation in the form
delete[] arrayPtr;
to return to the heap memory allocated to array pointed to by arrayPtr.
IMPORTANT!!
memory leaks involving arrays can result in considerable loss of memory
for(;;)
{
int n;
cout << "Size of array (0 to stop): ";
cin >> n;
if (n == 0) break;
double * arrayPtr = new double[n];
// process arrayPtr
. . .
}
Each new allocation of memory to arrayPtr maroons old memory block
17. Run-time allocation in classes
Classes that use run-time allocated storage require
some new members functions and
modifications of others:
1. Destructors: To "tear down" the storage structure and
deallocate its memory.
2. Copy constructors: To make copies of objects (e.g., value parameters)
3. Assignment: To assign one storage structure to another.
18. Stack with run-time array
#ifndef RTSTACK
#define RTSTACK
#include <iostream>
using namespace std;
template <typename StackElement>
class Stack
{ /***** Function Members *****/
public:
. . .
/***** Data Members*****/
private:
StackElement *myArrayPtr;
// run-time allocated array (not fixed-sized array)
int myCapacity_, // capacity of stack - variable
myTop_; // top of stack
};
#endif
19. Stack constructors
Want to permit declarations such as
Stack<int> s1, s2(n);
construct s1 as a stack with some default capacity,
construct s2 as a stack with capacity n.
declare a constructor with a default argument
/* --- Class constructor ---
Precondition: A stack has been defined.
Receive: Integer numElements > 0;
(default = 128)
Postcondition: stack constructed with
capacity numElements.
*/
Stack(int numElements = 128);
20. Stack constructors
constructor must really construct something
not just initialize data members
template <class StackElement>
Stack<StackElement>::Stack(int numElements)
{
assert (numElements > 0); // check precondition
myCapacity_ = numElements; // set stack capacity
// allocate array of this capacity
myArrayPtr = new StackElement[myCapacity_];
if (myArrayPtr == 0) // memory available?
{
cerr << "Inadequate memory to allocate stack n";
exit(-1);
} // or assert(myArrayPtr != 0);
myTop_ = -1;
}
21. Stack with run-time array
push(), top(), pop(), and operator<<()
access elements of the array data member.
Use the subscript operator [] as with fixed-sized arrays
except for name changes (myArray to myArrayPtr)
the definitions of these functions are the same as before
template <class StackElement>
void Stack<StackElement>::push(const StackElement & value)
{
if (myTop_ < myCapacity_ - 1)
{
++myTop_;
myArrayPtr[myTop_] = value;
} // or simply, myArrayPtr[++myTop_] = value;
else
cerr << "Stack full -- can't add new value ***n";
}
22. Destructors
Stack<double> st(num);
. . .
compiler knows size of data members
myCapacity_, myTop_, and myArrayPtr of st
allocates memory for them:
myCapacity_
myTop_
myArrayPtr
st
The array to store stack elements is created by the constructor
memory for it isn't allocated until run-time:
0 1 2 3 4 . . . num-1
myCapacity_
myTop_
myArrayPtr
st
23. Destructors
When the lifetime of st ends,
memory allocated to myCapacity_, myTop_, and myArrayPtr
automatically reclaimed, but not for the run-time allocated array:
0 1 2 3 4 . . . num-1
add a destructor member function to avoid this memory leak.
• Destructor's role: Deallocate memory allocated at run-time
(the opposite of the constructor's role).
• At any point in a program where an object goes out of scope,
the compiler inserts a call to this destructor.
When an object's lifetime is over, its destructor is called first.
24. Destructors
• Name is the class name preceded by a tilde (~).
• It has no arguments or return type
~ClassName()
template <class StackElement>
Stack<StackElement>::~Stack()
{
delete[] myArrayPtr;
}
// note the [] must be used with delete
// to deallocate the entire array
25. Copy constructor
Is needed whenever a copy of a class object must be built
• When a class object is passed as a value parameter
• When a function returns a class object
• If temporary storage of a class object is needed
• In initializations
If a class has no copy constructor,
the compiler uses a default copy constructor
byte-by-byte copy of the object.
adequate for classes without dynamically allocated data
not adequate for classes containing
pointers to run-time allocated arrays (or other structures)
such bitwise copying would result in
aliases to the dynamically allocated data
26. Copy constructor
a byte-by-byte copying of st to produce a copy stCopy gives
0 1 2 3 4
a b c
stCopy
myCapacity_
myTop_
myArrayPtr
st
5
2
myCapacity_ 5
myTop_
myArrayPtr
2
not correct!!
copies of myCapacity_, myTop_, and myArrayPtr made
but not a copy of the run-time allocated array.
Modifying stCopy.myArrayPtr[i]
will also modify st.myArrayPtr[i]!!!!
27. Copy constructor
Need to create a distinct (deep) copy of st,
in which the array in stCopy has exactly
the same elements as the array in st:
stCopy
myCapacity_
myTop_
myArrayPtr
st
5
2
myCapacity_ 5
myTop_
myArrayPtr
2
0 1 2 3 4
a b c
0 1 2 3 4
a b c
The copy constructor must be designed to do this.
28. Copy constructor
• like other constructors, it is a member function
its name is the class name
it has no return type.
• It needs a single parameter (source of copy) of same type
this parameter must be a reference parameter
and should be const
Why? The copy constructor does not change this "source" parameter
or pass information back through it.
Stack(const Stack<StackElement> & original);
29. Copy constructor
template <class StackElement>
Stack<StackElement>::Stack(const Stack<StackElement> &
original)
{
myCapacity_ = original.myCapacity_;
// copy myCapacity_ member
myArrayPtr = new StackElement[myCapacity_];
// allocate array in copy
if (myArrayPtr == 0) // check if memory available
{
cout << "*Inadequate memory to allocate stack ***n";
exit(-1);
}
for (int pos = 0; pos < myCapacity_; pos++)
// copy array member
myArrayPtr[pos] = original.myArrayPtr[pos];
myTop_ = original.myTop_ ;
// copy myTop_ member
}
30. Overloaded assignment operator
Like the default copy constructor,
the default assignment operation does byte-by-byte copying.
With bitwise copying, the assignment statement
s2Copy = s2;
will yield aliases as before, i.e.
the myArrayPtr data members of both s2 and s2Copy
would both point to the same anonymous array.
Need to overload the assignment operator (operator=)
so that it creates a distinct copy of the stack being assigned.
operator= must be a member function. So an assignment
stLeft = stRight;
will be translated by the compiler as
stLeft.operator=(stRight);
31. Overloaded assignment operator
Stack<StackElement>&
operator=(const Stack<StackElement> & original);
return type is a reference to a Stack
since operator=() must return object on the left side of the
assignment and not a copy of it (to make chaining possible).
Differences from copy constructor:
1. Object on the left side of the assignment may already have a value.
Must destroy old object —deallocate the old so no memory leak
and allocate a new one
2. Assignment must be concerned with self-assignments: st = st;
Can't destroy the right old value in this case.
3. operator=() must return the Stack containing this function.
32. this pointer
Every member function of a class has access to
a (hidden) pointer constant this
The value of this is
the address of the object containing the member function.
The expression *this refers to the object itself
33. Overloaded assignment operator
template <class StackElement>
Stack<StackElement>& Stack<StackElement>::operator=
(const Stack<StackElement>& original)
{
if (this != &original) // check that not st = st
{
delete[] myArrayPtr; // destroy previous array
myArrayPtr = new StackElement[myCapacity_];
if (myArrayPtr == 0) // check if memory available
{
cerr << "*** Inadequate memory to allocate stack ***n";
exit(-1);
}
myCapacity_ = original.myCapacity_; // copy myCapacity_
for (int pos = 0; pos < myCapacity_; pos++)
// copy array member
myArrayPtr[pos] = original.myArrayPtr[pos];
myTop_ = original.myTop_ ; // copy myTop_ member
}
return *this; // return reference to this object
}
34. Linked Lists
As an abstract data type,
a list is a finite sequence (possibly empty) of elements with basic operations
that vary from one application to another.
Basic operations commonly include:
Construction: Allocate and initialize a list object (usually empty)
Empty: Check if list is empty
Traverse: Visit each element of the list in order stored
Insert: Add an item to the list (at any point)
Delete: Remove an item from the list (at any point)
35. Linked Lists
Minimal requirements:
1. Locate the first element.
2. Given the location of any list element, find its successor.
3. Determine if at the end of the list.
For the array/vector-based implementation:
1. first element is at location 0
2. Successor of item at location i is at location i + 1
3. At location size – 1
A gross inefficiency is caused by #2 -- insertions and deletions requires one
to shift elements;
Remove requirement that list elements be stored in consecutive location
need a "link" that connects each element to its successor
linked lists
36. Linked Lists
A linked list is an ordered collection of elements called nodes each of
which has two parts:
(1) Data part: Stores an element of the list;
(2) Next part: Stores a link (pointer) to the next list element.
If there is no next element, a special null value is used.
Additionally, an external link must be maintained:
that to the location of the node storing the first list element,
This value will be null, if the list is empty.
Example: A linked list storing 9, 17, 22, 26, 34:
data
first 9 17 22 26 34
next
37. Linked List Basic Operations
Construction: first = null_value;
Empty: return (first == null_value)
Traverse: ptr = first;
while (ptr != null_value)
{
Process data part of
node pointed to by ptr;
ptr = address of next element;
// contained in next part of
// node pointed to by ptr;
}
Insert & Delete: Both operations necessitate the changing of links.
The address of the predecessor of the node to be added or removed
needs to be known.
38. Linked List Traversal
first 9 17 22 26 34
ptr
first 9 17 22 26 34
ptr
...
first 9 17 22 26 34
ptr
first 9 17 22 26 34
ptr
39. Linked List Insert
To insert 20 after 17 in the preceding linked list
Need address of item before point of insertion
predptr points to the node containing 17
(1) Get a new node pointed to by newptr and store 20 in it
9 17 22 29 34
20
predptr
newptr
first
(2) Set the next pointer of this new node equal to the next pointer in predptr
thus making it point to its successor.
9 17 22 29 34
20
predptr
newptr
first
40. Linked List Insert
(3) Reset the next pointer of predptr to point to newptr
9 17 22 29 34
20
predptr
newptr
first
Same process works at the end of the list, e.g. insert 55.
(1) as before
(2) as before — sets next link to null pointer
(3) as before
first 9 17 20 22 29 34
55
predptr
newptr
41. Linked List Insert
Inserting at the beginning of the list requires a modification of step 3:
Example: Insert a node containing 5 at the beginning of the list.
(1) as before
(2) sets next link to first node in the list
(3) set first to point to new node.
first 9 17 20 22 29 55
5
predptr
newptr
34
42. Linked List Delete
Delete 22: Need predecessor in linked list
(1) Do a bypass operation: Set the next pointer in the predecessor
to point to the successor of the node to be deleted
predptr ptr
first 5 9 17 20 22 29 34
(2) Deallocate the node being deleted.
free store
first 5 9
17 20 22 29 34
predptr ptr
43. Linked List Delete
Same process works for deleting the end of the list, e.g. delete 34.
(1) as before — sets next link to null pointer
(2) as before
predptr ptr
first 5 9 17 22 29
34
free store
Deleting at the beginning of the list requires a modification of step 1:
(1) reset first
(2) as before
Example: Delete 5 from the previous list
predptr ptr
first 5 9 17 22 29
44. Linked List
Advantanges of linked lists:
· store items "sequentially" without restrictions on location
· access any item as long as external link to first item maintained
· insert new item without shifting
· delete existing item without shifting
· size can expand/contract throughout use
Disadvantages
· overhead of links: used only internally, pure overhead
· if dynamic, must provide destructor, copy constructor
· no longer have direct access to each element of the list
· O(1) access becomes O(n) access since we must go through
first element, and then second, and then third, etc.
List-processing algorithms that require fast access to each element
cannot (usually) be done as efficiently with linked lists, e.g.:
· Binary search
· Sorting
· Append item (add to end of list)
45. Linked List Implementation
//node definition
class Node
{
public:
DataType data;
Node *next;
};
Node is a recursive (or self-referential) definition because it uses the name
Node in its definition:
the next member is defined as a pointer to a Node.
// link definition
Node *ptr; or typedef Node *NodePointer;
NodePointer ptr;
//Allocate // Deallocate:
ptr = new Node; delete ptr;
46. Linked List Implementation
To access the data and next part of node:
(*ptr).data and (*ptr).next
or better, use the -> operator
ptr->data and ptr->next
Why make data members public in class Node? (Node could also be a struct)
This class declaration will be placed inside class declaration for LinkedList.
Data members data and next of struct Node will be public inside LL class
and thus will accessible to the member and friend functions of the class,
but they will be private outside the class.
47. Linked List Implementation
#ifndef LINKEDLIST
#define LINKEDLIST
typedef int DataType;
class LinkedList
{
private:
class Node
{
public:
DataType data;
Node * next;
};
typedef Node * NodePointer;
. . .
};
#endif
// could also define a template taking DataType as its type
// parameter and thus avoid the recompilation required when
// typedef changes