This weekās section exercise delve deep into the details of pointers and memory management in C++. This is an opportunity to get down and dirty in the nitty gritty of things, and to get closer towards gaining ultimate power over your computer! The topics covered in section this week will show up on the next assignment.
Remember that every week we will also be releasing a Qt Creator project containing starter code and testing infrastructure for that week's section problems. When a problem name is followed by the name of a .cpp
file, that means you can practice writing the code for that problem in the named file of the Qt Creator project. Here is the zip of the section starter code:
š¦ Starter code
1) A Series of Unfortunate References
Topics: Pointers and references
What is the output of the following code snippet?
void VFD(int *duncan, int isadora, int& quigley) {
isadora += *duncan;
quigley *= isadora;
(*duncan) = 1;
isadora = *duncan;
}
int main() {
int sunny = -6;
int klaus = 21;
int violet = 2;
VFD(&sunny, violet, klaus);
cout << sunny << " " << klaus << " " << violet << " " << endl;
return 0;
}
1Ā -84Ā 2
2) Pointed Points about Pointers
Topics: Arrays and pointers
Pointers to arrays are different in many ways from ADTs in how they interact with pass by-value and the = operator. To better understand how they work, trace through the following program. What is its output?
void print(int* first, int* second) {
for (int i = 0; i < 5; i++) {
cout << i << ": " << first[i] << ", " << second[i] << endl;
}
}
void transmogrify(int* first, int* second) {
for (int i = 0; i < 5; i++) {
first[i] = 137;
}
}
void mutate(int* first, int* second) {
first = second;
second[0] = first[0];
}
void change(int* first, int* second) {
first = new int[5];
second = new int[5];
for (int i = 0; i < 5; i++) {
first[i] = second[i] = 271;
}
}
int main() {
int* one = new int[5];
int* two = new int[5];
for (int i = 0; i < 5; i++) {
one[i] = i;
two[i] = 10 * i;
}
transmogrify(one, two);
print(one, two);
mutate(one, two);
print(one, two);
change(one, two);
print(one, two);
delete[] one;
delete[] two;
return 0;
}
The output of the program is shown here:
0: 137, 0
1: 137, 10
2: 137, 20
3: 137, 30
4: 137, 40
0: 137, 0
1: 137, 10
2: 137, 20
3: 137, 30
4: 137, 40
0: 137, 0
1: 137, 10
2: 137, 20
3: 137, 30
4: 137, 40
Remember that when passing a pointer to a function, the pointer is passed by value! This means that you can change the contents of the array being pointed at, because those elements aren't copied when the function is called. On the other hand, if you change which array is pointed at, the change does not persist outside the function because you have only changed the copy of the pointer, not the original pointer itself.
3) The Hogwarts School of Pointers and Memory
Topics: Pointer tracing and memory diagrams
Trace through the following function and draw the programās memory at the designated spot. Indicate which variables are on the stack and which are on the heap, and indicate orphaned memory. Indicate with a question mark (?) memory that we donāt know the values of.
struct Quidditch {
int quaffle;
int *snitch;
int bludger[2];
};
struct Hogwarts {
int wizard;
Quidditch harry;
Quidditch *potter;
};
Quidditch * hufflepuff(Hogwarts * cedric) {
Quidditch *seeker = &(cedric->harry);
seeker->snitch = new int;
*(seeker->snitch) = 2;
cedric = new Hogwarts;
cedric->harry.quaffle = 6;
cedric->potter = seeker;
cedric->potter->quaffle = 8;
cedric->potter->snitch =
&(cedric->potter->bludger[1]);
seeker->bludger[0] = 4;
return seeker;
}
void gryffindor() {
Hogwarts *triwizard = new Hogwarts[3];
triwizard[1].wizard = 3;
triwizard[1].potter = NULL;
triwizard[0] = triwizard[1];
triwizard[2].potter =
hufflepuff(triwizard);
triwizard[2].potter->quaffle = 4;
// DRAW THE MEMORY AS IT LOOKS HERE
}
Orphaned memory is represented with dotted lines. For a full walkthrough of the solution, check out: http://tinyurl.com/HogwartsPointers.
4) The Notorious RBQ, Revisited (RingBufferQueue.h/.cpp
)
Topics: Classes, dynamic memory allocation, pointers
Remember our good friend the RingBufferQueue
from last section? Check out the problem definition from last week if you want a quick refresher. Last time we visited the RBQ it had fixed capacity āĀ that is, it couldn't grow in size after it was initially created. How limiting! With our newfound pointer and dynamic allocation skills, we can remove this limitation on the RBQ and make it fully functional!
Add functionality to the class RingBufferQueue
from the previous section problem so that the queue resizes to an array twice as large when it runs out of space. In other words, if asked to enqueue when the queue is full, it will enlarge the array to give it enough capacity.
For example, say our queue can hold 5 elements and we enqueue the five values 10, 20, 30, 40, and 50. Our queue would look like this:
index 0 1 2 3 4
+----+----+----+----+----+
value | 10 | 20 | 30 | 40 | 50 |
+----+----+----+----+----+
^ ^
| |
head tail
If the client tries to enqueue a sixth element of 60, your improved queue class should grow to an array twice as large:
index 0 1 2 3 4 5 6 7 8 9
+----+----+----+----+----+----+----+----+----+----+
value | 10 | 20 | 30 | 40 | 50 | 60 | | | | |
+----+----+----+----+----+----+----+----+----+----+
^ ^
| |
head tail
The preceding is the simpler case to handle. But what about if the queue has wrapped around via a series of enqueues and dequeues? For example, if the queue stores the following five elements:
index 0 1 2 3 4
+----+----+----+----+----+
value | 40 | 50 | 10 | 20 | 30 |
+----+----+----+----+----+
^ ^
| |
tail head
If the client tries to add a sixth element of 60, you cannot simply copy the array as it is shown. If you do so, the head and wrapping will be broken. Instead, copy into the new array so that index 0 always becomes the new head of the queue. The picture will look the same as the previous one with the value 60 at index 5.
Write up the implementation of the new and improved RingBufferQueue
. It should have the same members as in the previous problem, but with the new resizing behavior added. You may add new member functions to your class, but you should make them private.
RingBufferQueue.h
#pragma once
#include <iostream>
using namespace std;
class RingBufferQueue {
public:
RingBufferQueue();
~RingBufferQueue();
bool isEmpty() const;
bool isFull() const;
int size() const;
void enqueue(int elem);
int dequeue();
int peek() const;
private:
int* _elements;
int _capacity;
int _size;
int _head;
friend ostream& operator <<(ostream& out, const RingBufferQueue& queue);
void enlarge();
};
RingBufferQueue.cpp
#include "RingBufferQueue.h"
static int kDefaultCapacity = 5;
RingBufferQueue::RingBufferQueue() {
_capacity = kDefaultCapacity;
_elements = new int[_capacity];
_head = 0;
_size = 0;
}
RingBufferQueue::~RingBufferQueue() {
delete[] _elements;
}
void RingBufferQueue::enqueue(int elem) {
if (isFull()) {
enlarge();
}
int tail = (_head + _size) % _capacity;
_elements[tail] = elem;
_size++;
}
int RingBufferQueue::dequeue() {
int front = peek();
_head = (_head + 1) % _capacity;
_size--;
return front;
}
int RingBufferQueue::peek() const {
if (isEmpty()) {
throw "Can't peek at an empty queue!";
}
return _elements[_head];
}
bool RingBufferQueue::isEmpty() const {
return _size == 0;
}
bool RingBufferQueue::isFull() const {
return _size == _capacity;
}
int RingBufferQueue::size() const {
return _size;
}
void RingBufferQueue::enlarge() {
int *larger = new int[_capacity * 2];
for (int i = 0; i < _size; i++) {
larger[i] = _elements[(_head + i) % _capacity];
}
delete[] _elements;
_elements = larger;
_capacity *= 2;
_head = 0;
}
ostream& operator <<(ostream& out, const RingBufferQueue& queue) {
out << "{";
if (!queue.isEmpty()) {
// We can access the inner ā_elementsā member variables because
// this operator is declared as a friend of the RingBufferQueue class
out << queue._elements[queue._head];
for (int i = 1; i < queue.size(); i++) {
int index = (queue._head + i) % queue._capacity;
out << ", " << queue._elements[index];
}
}
out << "}";
return out;
}
5) Min Heap
Topics: Heaps
We have implemented the Priority Queue ADT using a binary min-heap. Draw a diagram of the heapās tree structure that results from inserting the following priority values in the order given: 25, 37, 28, 12, 30, 3
6) Max Heap
Topics: Heaps
You have a PriorityQueue class which treats higher (larger integer value) priority elements as frontmost. The internal implementation of the class is a binary max-heap stored in an unfilled array. The initial allocation of the array is for capacity 5 and the array capacity is doubled when asked to enqueue to a queue which is full. You are going to trace the operation of enqueuing and dequeuing elements from the priority queue. You can sketch as a tree if you prefer when working it out, but your final answer should be based on a representation of the underlying array that supports the heap.
(a) Show the contents of the internal array after these elements are enqueued to an empty priority queue in this order. Each element has a string value and a priority in parenthesis.
Red(8), Blue(33), Green(29), Purple(42), Orange(20), Yellow(22), Indigo(10), Teal(21)
(b) Dequeue is called twice on the priority queue. Which two values are removed?
(c) Show the contents of the internal array after the above two elements have been dequeued
(a) Color names are abbreviated
elements: | P(42) | B(33) | G(29) | T(21) | O(20) | Y(22) | I(10) | R(8) | ? | ? |
index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
(b) Purple(42) and Blue(33)
(c)
elements: | G(29) | T(21) | Y(22) | R(8) | O(20) | I(10) | ? | ? | ? | ? |
index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |