Professional Documents
Culture Documents
List With A Counter Variable. The Counter Variable Keeps Track of The Top of The Stack
List With A Counter Variable. The Counter Variable Keeps Track of The Top of The Stack
A stack is an ADT that holds a collection of items where elements are always added to one end. It
is a LIFO data structure: the last item is pushed onto the stack is also that first item popped off
the stack. The stack can be implemented using either an array with a counter variable or a linked
list with a counter variable. The counter variable keeps track of the top of the stack.
class Stack
{
public:
Stack():m_top(0) {}
void push(int i)
{
if(m_top >= SIZE)
return;
m_stack[m_top] = val;
m_top++; // We post-increment our m_top variable.
}
int pop()
{
if(m_top == 0)
return;
m_top--; // We pre-decrement our m_top variable.
return m_stack[m_top];
}
bool is_empty();
int peek_top();
private:
int m_stack[100];
int m_top; // m_top represents the next open slot.
};
We can use the STL stack by including <stack> to the file. Common uses of stacks include
storing undo items for your word processor, evaluating mathematical expressions, converting
from infix expressions to postfix expressions, and solving mazes.
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> intStack;
intStack.push(10);
cout << intStack.top(); // Returns top value.
intStack.pop(); // Returns no values.
intStack.empty(); // Returns a boolean value.
intStack.size(); // Returns integer value.
}
When solving a maze, the stack solution visits the cells in a depth-first order: it continues along a
path until it hits a dead end, then backtracks to the most recently visited intersection that has
unexplored branches. Because we're using a stack, the next cell to be visited will be a neighbor
of the most recently visited cell with unexplored neighbors.
QUEUE
A queue is another ADT that functions in a FIFO manner: first in first out. You enqueue items at
the rear and dequeue people from the front. Queues are implemented using linked lists or arrays.
When implementing a queue using a linked list, every time you enqueue an item, add a new node
to the end of the linked list. Every time you dequeue an item, youre essentially removing the
head node. Alternatively, a queue can be implemented as a circular queue by using an array.
There is no need to shift items with the circular queue.
class Queue
{
public:
Queue():m_head(0),m_tail(0),m_count(0) {}
void enqueue (int val)
{
m_integers[m_tail] = val; // Place item in tail position.
m_count++; // Increment the tail value.
m_tail++; // Increment the count value.
if(m_tail > 5) m_tail = 0; // Set to 0 if it passes the end.
}
int dequeue()
{
int temp_head = m_integers[m_head];
m_count--; // Decrement the count value.
m_head++; // Increment the head value.
if(m_head > 5) m_head = 0; // Set to 0 if it passes the end.
return temp_head;
}
private:
int m_integers[6];
int m_head;
int m_tail;
int m_count;
};
Like the stack, the queue also has its own STL class implementation.
#include <queue>
#include <iostream>
using namespace std;
int main()
{
queue<int> intQueue;
intQueue.push(10);
cout << intQueue.front(); // Returns front value.
intQueue.pop(); // Returns no values.
intQueue.empty(); // Returns a boolean value.
intQueue.size(); // Returns integer value.
}
When solving a maze, the queue solution visits the cells in a breadth-first order: it visits all the
cells at distance 1 from the start cell, then all those at distance 2, then all those at distance 3, etc.
Because we're using a queue, the next cell to be visited will be a neighbor of the least recently
visited cell with unexplored neighbors.
INHERITANCE
Whenever we have a relationship A is a type of B, we can apply inheritance to the situation.
Terminology includes the superclass (base class) from which subclasses (derived classes) are
derived. Subclasses can also function as superclasses for subclasses.
class Person
{
public:
Person(string nm, int age)
:m_name(nm),m_age(age){}
virtual void doSomething()
{
cout << Hello World!;
}
private:
string m_name;
int m_age;
};
/* Only use the virtual keyword
for functions you intend to
override for your subclasses.
*/
INHERITANCE RULES:
1. If you define a function in the base class and the derived class, then the derived version of
the function will hide the base version of the function.
2. Any public functions defined in the base class, but not redefined in a derived class can be
called normally by all parts of your program.
3. If your code wants to call a function in the base class thats been redefined in the derived
class, it must use the class:: prefix. For example, a Student with variable name Yoshi
can call the original function through Yoshi.Person::doSomething().
4. Classes are constructed from superclass to subclass.
5. Classes are destructed from subclass to superclass.
6. A derived class may not access private members of the base class.
The idea of polymorphism is invoked when C++ allows us to use a base pointer or a base
reference to access a derived object. Dynamic binding enables the appropriate version of
functions to be called during runtime when the keyword virtual is used before both base class
and derived class function declarations.
POLYMORPHISM
Use the virtual keyword in both base and derived classes when redefining a function or
writing the destructor. This, however, is unnecessary for constructors.
void PrintPrice(Shape& x)
{
cout << Cost is: $;
cout << x.getArea()*3.25;
x.setSide(10); // This function is specific to a square and
// will produce a compiler error if it is called.
}
/* As far as this function is concerned, every variable passed is
simply a Shape: it has no idea which derived class it is working
with. It follows that you can only pass member functions of Shape. */
POLYMORPHISM RULES:
1. You may never point a derived class pointer or reference to a base class variable.
2. C++ always calls the most-derived version of a function associated with a variable as long
as it is marked with the keyword virtual.
3. You cant access private members of the base class from the derived class.
4. You should always make sure that you use the virtual keyword in front of destructors.
5. Make a base class function pure virtual if you realize that the base class version of your
function doesnt or cant do anything useful.
class Person
{
public:
Person(string nm, int age)
:m_name(nm),m_age(age){}
virtual doWork = 0;
private:
string m_name;
int m_age;
};
/* If you define at least one
pure virtual function in a base
class, then the class is called
an abstract base class. */
Writing pure virtual functions and abstract base classes force users to prevent common mistakes,
such as forgetting to define relevant functions in derived classes.
10
11
12
POINTER TIDBITS
int main()
{
/* Behavior of an array of pointers to integers */
int* intPtrArray[10]; // Array of pointers to integers.
intPtrArray[0] = new int; // Use new to store an integer
// in dynamically allocated memory.
(**intPtrArray) = 3; // Access location pointer to by pointer.
int k = 3;
(*(integerArray+1)) = &k; // Determine address of k.
/* Pointer arithmetic */
int intArray[10];
// intArray is a pointer to the first position in the array.
// &intArray[3] is equivalent to (intArray+3).
int* p = intArray + 2;
// p[0] now refers to the value at intArray[2].
}
13
14
CLASS TEMPLATES
General
1. Put this in front of the class declaration:
B: class foo {...};
A: template <typename ItemType> class foo {...};
2. Update appropriate types in the class to the new ItemType
Updating internally-defined methods
1. For normal methods, just update all types to ItemType:
B: int bar(int a) {...}
A: ItemType bar(ItemType a) {...}
2. Assignment operator:
B: foo &operator=(const foo &other)
A: foo<ItemType>& operator=(const foo<ItemType>& other)
3. Copy constructor:
B: foo(const foo &other)
A: foo(const foo<ItemType>& other)
Updating externally-defined methods
1. For non-inline methods:
B: int foo::bar(int a)
A: template <typename ItemType>
ItemType foo<ItemType>::bar(ItemType a)
2. For inline methods:
B: inline int foo::bar(int a)
A: template <typename ItemType>
inline ItemType foo<ItemType>::bar(ItemType a)
3. Copy constructor:
B: foo &foo::operator=(const foo &other)
A: foo<ItemType>& foo<ItemType>::operator=(const foo<ItemType>& rhs)
4. Assignment operator:
B: foo::foo(const foo& other)
A: foo<ItemType>::foo(const foo<ItemType>& other)
15
16
class Nerd
{
public:
void beNerdy() const;
...
};
int main()
{
vector<int> v;
... // Adding some integers into the vector.
vector<int>::iterator it = v.begin(); // Sets to first item in v.
cout << (*it); // Values can be accessed using star operator.
it++; // Iterator now points to the next item.
it--; // Iterator now points to the previous item.
while(it != v.end()) // While the iterator doesnt point to the
// position just after the last item.
{
cout << (*it) << endl;
it++;
}
vector<Nerd> n;
... // Adding some nerds into the vector.
vector<Nerd>::const_iterator c_it = n.begin();
/* Use a const_iterator when you want to either guarantee that
items traversed using the const_iterator will not be modified, or
traverse through a const container. */
n->beNerdy(); // Arrow operator works too!
}
17
ITERATOR QUIRKS
int main()
{
vector<int> v;
v.push_back(3);
vector<int>::iterator it = v.begin();
v.erase(it); // Erasing from a vector invalidates other iterators
// The same behavior applies when you add a new item.
// This doesnt affect sets, lists, or maps.
// As with pointers, you should not access iterators
// that dont point to items.
v.push_back(4);
v.push_back(5);
it = v.begin();
it = v.erase(it); // Returns the iterator to the next element.
v.erase(v.begin(),v.end()); // Erases all elements between the
// first iterator and the position
// before the second iterator.
}
The STL also provides useful some useful functions under the <algorithm> library.
bool is_even(int n)
{...}
int main()
{
vector<int> v;
v.push_back(3);
vector<int>::iterator it = find(v.begin(),v.end(),3);
/* As is the case with the erase function, the first argument is
the first element to search, and the second argument is the
element after the last element to be searched. */
int array[5] = {0, 1, 2, 3, 4};
int* ptr = find(&array[0], &array[5], 5);
if(ptr == &array[5]) // Returns the second argument if not found.
cout << Nope, not found. << endl
int* ptr2 = find_if(&array[0], &array[5], is_even);
/* find_if takes the range and a boolean function as a pointer
that takes values of the same type as those in the container.
Callback function in the form of bool(*p1)(int). */
}
18
STL CONTAINERS
Sequence containers maintain the original ordering of inserted elements. This allows you to
specify where to insert the element in the container.
1. The list containers allows for fast insertions and deletions anywhere in the container, but you
cannot randomly access an element in the container.
2. The vector behaves like an array, but will automatically grow as required.
The defining characteristics of associative containers is that elements are inserted in a predefined order, such as sorted ascending. The associative containers can be grouped into two
subsets: maps and sets. A map consists of a key/value pair. The key is used to order the
sequence, and the value is somehow associated with that key. A set is simply an ascending
container of unique elements.
Both map and set only allow one instance of a key or element to be inserted into the container.
#include <vector>
#include <list>
int main()
{
vector<string> v;
v.push_back(Yoshi);
v.push_back(Steve);
string i = v.front();
string j = v.back();
unsigned long k = v.size();
bool v = s.empty();
v[0] = Wallace;
v.pop_back();
v.clear();
}
int main()
{
list<string> l;
l.push_front(Yoshi);
l.push_back(Steve);
string a = l.front();
string b = l.back();
unsigned long c = l.size();
bool d = l.empty();
l.pop_front();
l.pop_back();
l.clear();
}
/* In lists, you cant access
elements using square brackets.
You want to use vectors for
fast random access and lists
for fast insertion or deletion
at the head and tail of the
container. */
19
#include <map>
#include <set>
int main()
{
map<string, int> n_p;
n_p[Arnold] = 9293819;
n_p[Steve] = 9238104;
// [ key ]
value
int main()
{
set<int> s;
s.insert(2);
s.insert(3);
s.insert(4);
s.insert(2); // Ignored.
set<int>::iterator = it;
it = s.find(3);
cout << s.size();
s.erase(2);
s.clear();
}
map<string, int>::iterator
it = n_p.find(Steve);
cout << it->first;
cout << it->second;
n_p.clear();
}
/* This works because maps work
much like alphabetically
ordered structs with first and
second as member variables. */
int main()
{
unordered_map <string,int> hm;
hm["Carey"] = 10;
unordered_map <string,int>::iterator it = hm.find("Carey");
if (it != hm.end())
cout << it->first << endl;
cout << it->second << endl;
}
20
CHOOSING A SORT
The bubble sort, selection sort, insertion sort, and shell sort.
1. The first three of these sorts have a Big-O of O(n2). The shell sort has a Big-O that is at best
O(n(log2n)2) but is typically fond to be O(n3/2).
2. All of these sorts operate on constant memory.
3. Unlike the selection sort, the bubble and insertion sort is efficient on pre-sorted containers.
MISCELLANIES
1. We can preprocess arrays into hash tables for faster future searching.
2. We can merge items using an unordered set or a regular set.
3. If we want to provide a method to search, we should use a hash table.
4. If we were to choose between a sorted vector or linked list to search for an item, we should opt
for the vector where binary search can be performed.
21