NETB201 Data Structures


Chapter 1: Basic C++ Programming

1.1 Basic C++ Programming Elements

1.1.1 A Simple C++ Program

#include <stdlib>
#include <iostream>
int main()
{ int x, y;
  std::cout << "Please enter two numbers: ";
  std::cin >> x >> y; // input stream
  int sum = x + y;
  std::cout << "Their sum is " << sum << std::endl;
return EXIT_SUCCESS;
}

Highlighting the C++ Elements

-- operator - as an element of a programming language (+, =, ==)
-- operation  - as an action or a state  (addition, assignment, equal)

1.1.2 Fundamental Types

bool
char
short
int
long
float
double
enum
true, false
character - 'A'
short integer
integer
long integer
single-precision floating-point number
double-precision floating-point number
enumeration - a set of discrete values

bool, char, int and enum are called integral types.
void indicates the absence of any type information (only for function type).

Characters

A char variable holds a single character - usually 8 bits. A literal is a constant value appearing in the program.
'a', 'Q', '+', '\n', '9' are literals.
Each character is associated with an integer code, usually ASCII code.
cout << 'a' << int('a') << static_cast<int>('a');

Integers

2 bytes <= sizeof(short) <= sizeof(int) <= sizeof(long) and
sizeof(long) >= 4 bytes

Enumerations

An enumeration is a user-defined type that can hold any of a set of discrete values.
enum Color { RED, GREEN, BLUE };  // default values are 0, 1, 2
enum Mood { HAPPY = 3, SAD = 1, ANXIOUS = 4, SLEEPY = 2 };
Color skyColor = BLUE;
Mood myMood = SLEEPY;

Floating Point

-- double constants: 1.5402, 1.2E8, -2.25e-3, 1e5 (12-14 decimal digits)
-- float constants: 1.23f, 6f, 1.234F, 23e-2F (6-8 decimal digits)

1.1.3 Pointers, Arrays, and Structures

Pointers

Each variable is stored in the computer's memory at some location, or address. A pointer holds the value of such an address.
The address-of operator,  & returns the address of a variable.
Accessing the object addressed by a pointer is called dereferencing, and is done using * operator.
char ch = 'Q';  
char* p = &ch; // p holds the address of ch
cout << *p; // outputs the character 'Q'
ch = 'Z'; // ch now holds 'Z'
cout << *p; // outputs the character 'Z'

Arrays

An array is a collection of elements of the same type.
double f[3];    // array of 3 doubles f[0], f[1], f[2]
double* p[10]; // array of 10 double pointers p[0]..p[9]
f[2] = 2.5;
p[4] = &f[2]; // p[4] points to f[2]
cout << *p[4]; // outputs 2.5
int a[] = { 10, 11, 12, 13 }; // initialization
int b[20] = { 0 }; // initialize all elements with value 0
C++ provides no build-in runtime checking for array subscripting out of bounds [common programming error]!

Pointers and Arrays

The name of an array can be used as a pointer to the array's initial element and vice versa.
char ch[] = {'c', 'a', 't'};
char* p = c; // p points to c[0]
char* q = &c[0]; // q also points to c[0]
cout << c[2] << p[2] << q[2]; // outputs "ttt"
Given two arrays c and d, the comparison c == d does not test whether the contents of the two arrays are equal.

Strings

#include<string>;
using std::string;
//...
string s = "to be";
string t = "not " + s; // t = "not to be"
string u = s + " or " + t; // u = "to be or not to be"
if (s > t) cout u;
//...
s = "John";
int i = s.size(); // i = 4
char c = s[3]; // c = 'n'
s += " Smith"; // s = "John Smith"
char *p = s.c_str(); // p is a C-style string

C-Style Structures

A structure is useful for storing an aggregation of elements. The elements (members or fields) may be of different types.
enum MealType { NO_PREF, REGULAR, LOW_FAT, VEGETARIAN };
struct Passenger {
string name; // "John Smith"
MealType mealPref; // LOW_FAT
bool isFreqFlyer; // true
string freqFlyerNo; // "293145"
};
This defines a new type called Passenger. The individual member of the structure variable (object) are accepted using the member selection operator, which has the form struct_variable.member.
Passenger pass = { "John Smith", LOW_FAT, true, "293145" };
pass.name = "Pocahontas"; // change name
pass.mealPref = VEGETARIAN; // change meal preference
This is C-style structure. In general, structures and classes in C++ provide a much wider range of capabilities than what is possible in C-style structures (member functions, access control, etc).

Pointers, Dynamic Memory, and the new Operator

The C++ runtime system reserves a large block of memory called free memory (dynamic memory or heap memory).
The operator new dynamically allocates the correct amount of storage for an object of a given type from the free storage and returns a pointer to this object.
Passenger *p;
//...
p = new Passenger; // p points the new Passenger
p->name = "Pocahontas";
p->mealPref = VEGETARIAN; // change meal preference
p->isFreqFlyer = false;
p->freqFlyerNo = "NONE";
//...
delete p;
This new passenger object continuous to exist in the free store until it is explicitly deleted.

Memory Leaks

If an object is allocated with new, it should be eventually be deallocated with delete.
char* buffer = new char[500];
//...
delete buffer; // ERROR: delete only the fist element buffer[0]
Having inaccessible objects in dynamic memory is called memory leak.
char* buffer = new char[500];
//...
delete [] buffer; // delete all elements

1.1.4 Scope and Namespaces

Constants and typedef

Good programmers commonly like to associate names with constant quantities.
const double PI = 3.14159265;
const int CUT_OFF[] = {90, 80, 70, 60}
;
const int N_DAYS = 7;
const int N_HOURS = 24*N_DAYS;
int counter[N_HOURS];
It is often useful to associate a name with a type:
typedef char* BufferPtr;   // type BufferPtr is a pointer to char
typedef double Coordinate
; // type Coordinate is a double
//...
BufferPtr p; // p is a pointer to a char
Coordinate x, y; // x and y are of type double

Local and Global Scopes

The portion of a program, from which a given name is accessible are called its scope.
const int cat = 1;      // global cat
int main()

{ const int cat = 2; // local cat (to main)
cout << cat; // outputs 2
return 0;
}
int dog = cat; // dog = 1 (from global cat)

Namespaces

A namespace is a mechanism that allows a group of related names to be defined in one place.
namespace myglobals {
int cat;

string dog = "bow wow";
}

int d = myglobals::cat;
We can access an object x in namespace group, using the notation group::x, which is called fully qualified name.
namespace.cpp

The Using Statement

using std::cout;
using myglobals::cat;
or
using namespace std;
using namespace myglobals;

1.2 Expressions

An expression combines variables and literals with operators to create new values. Throughout, we use:

Member Selection and Indexing

Let
Operators:
class_name.member class/structure member selection
dot operator
pointer->member
class/structure member selection arrow operator
array[exp]
array subscripting
index operator

Arithmetic Operators

Binary arithmetic operators:

exp + exp
addition
exp - exp subtraction
exp * exp multiplication
exp / exp division
exp % exp modulo (remainder)

Unary operators: +x and -x

Increment and Decrement Operators

var ++
post increment
var --
post decrement
++ var pre increment
-- var pre decrement

int a[] = { 0, 1, 2, 3 };
int i = 2;
int j = i++; // j = 2 and then i = 3
int k = --i; // i = 2 and k = 2
cout << a[k++]; // a[2] (=2) is output; now k = 3

Relational and Logical Operators

Comparison operators:
exp < exp
less than
exp > exp greater than
exp <= exp less than or equal to
exp >= exp greater than or equal to
exp == exp equal to
exp != exp not equal to
These returns boolean value - true or false.
Logical operators:
! exp
logical not
exp && exp logical and
exp || exp logical or
exp != exp not equal to

Bitwise Operators

~ exp
bitwise complement
exp & exp bitwise and
exp ^ exp bitwise exclusive-or
exp | exp bitwise or
exp << exp shift left
exp >> exp shift right

Assignment Operators

Arithmetic binary operators (+, -, *,  /, % ) and bitwise binary operators (&, ^, |, <<, >>) combines with assignment.

int i = 10; 
int j = 5;
int k = 1;
i -= 4; // i = i - 4; result 6

j *= -2; // j = j * (-2); result -10
k <<= 1; // k = k << 1; result 2

Other Operators

class_name :: member
class scope resolution
namespace_name :: member namespace scope resolution
exp ? exp : exp
conditional expression
stream >> var
input stream
stream << exp
output stream

Operator Precedence

Operators in C++ are assigned a precedence, which determines the order in which operations are performed in the absence of parentheses.
Operator Description Associativity
:: scope Left
() [ ] -> . sizeof   Left
++ -- increment, decrement Right
~ bitwise complement
! (not)
unary NOT
& * reference and dereference
..._cast, (type)
type casting
+ - unary + and -
* / % arithmetic operators Left
+ - addition, subtraction Left
<< >> bitwise shifts
Left
< <= > >= relational operators Left
== != relational operators Left
&
bitwise and
Left
^ bitwise exclusive-or Left
| bitwise or
Left
&& (and)
logical operator and
Left
|| (or)
logical operator or
Left
?: conditional operator
Left
= += -= *= /= %=
>>= <<= &= ^= |=
assignment operators
Right
, comma, separator Left

int a = 10, b, c, d;  
// right associativity
d = c = b = a; // is equivalent to d = (c = (b = a));
// left associativity
d - c + b - a; // is equivalent to ((d - c) + b) - a;
d > c && -b <= a // is equivalent to (d>c) && ((-b)<=a)

1.2.1 Casting in Expressions

Casting is an operation that allows us to change the type of a variable.

Traditional C-Style Casting

int cat = 14;  
double dog = (double)cat;
double pig = double(cat);

Explicit Cast Operators

Casting operations can vary from harmless to dangerous, depending on how similar the two types are and whether the information is lost.

The standard of C++ supports 4 casting versions (operators):

using the notation: some_cast<T>(exp).

Static Casting

static_cast<T>(exp)

int cat = 14;  
double dog = static_cast<double>(cat); // dog = 14.0
double lion = 155.99;
int pig = static_cast<int>(lion); // pig = 155

Implicit Casting

There are many instances where the programmer has not requested an explicit case, but a change of types is required. In many of these cases, C++ will perform an implicit cast. That is, the compiler will automatically insert a cast into the machine-generated code - the compiler automatically casts to the stronger type (without loss of information).
char - short - int - long -? float - double

implicit_cast.cpp


1.3 Control Flow


1.4 Functions

1.4.1 Argument Passing

 1.4.2 Overloading

Function Overloading

Function overloading occurs when two or more functions are defined with the same name but different argument lists.
void print(int x)
{ cout << x; }
void print(const Passenger &pass)
{ cout << pass.name << " " << pass.mealPref;
if (pass.isFreqFlyer) cout << pass.freqFlyerNo;
}

Operator Overloading
The equality test for two Passenger objects:
bool operator==(const Passenger &x, const Passenger &y)
{ return x.name == y.name &&
x.mealPref == y.mealPref &&
x.isFreqFlyer == y.isFreqFlyer &&
x.freqFlyerNo == y.freqFlyerNo;
}

Using Overloading

ostream& operator<<(ostream &out, const Passenger &pass)
{ out << pass.name << " "
<< pass.mealPref;
if (pass.isFreqFlyer) out << pass.freqFlyerNo;

return out;
}
//...
Passenger pass1 = // details omitted
Passenger pass2 = // details omitted
cout << pass1 << pass2 << endl;


1.5 Classes

1.5.1 Class Structure

A class consists of members. Members that are variable or constants are data members (member variables) and members that are functions are called member functions (methods).

Access Control

Members may be declared to be public, which means that they are accessible form  outside the class, or private, which means that they are accessible only from within the class. The keywords public and private are access specifiers, the default is private.

Member Functions

enum MealType { NO_PREF, REGULAR, LOW_FAT, VEGETARIAN };
class Passenger {
private:
string name; // "John Smith"
MealType mealPref; // LOW_FAT
bool isFreqFlyer; // true
string freqFlyerNo; // "293145"
public:
Passenger(); // default constructor
bool isFreqFlyer() const; // accessor function
void makeFreqFlyer(const string &newFreqFlyerNo);
// update (mutator) function
};
// ...
bool Passenger::isFreqFlyer() const
{ return isFreqFlyer; }
void Passenger::makeFreqFlyer(const string &newFreqFlyerNo)
{ isFreqFlyer = true;
freqFlyerNo = newFreqFlyerNo;
}
//...
Passenger pass;
//...
if (!pass.isFreqFlyer()) // OK
pass.makeFreqFlyer("999999"); // OK
pass.name = "Joe Blow"; // ERROR: private member

In-Class Function Definition

enum MealType { NO_PREF, REGULAR, LOW_FAT, VEGETARIAN };
class Passenger {
private:
string name; // "John Smith"
MealType mealPref; // LOW_FAT
bool isFreqFlyer; // true
string freqFlyerNo; // "293145"
public:
Passenger();
bool isFreqFlyer() const
{ return isFreqFlyer; }  
void makeFreqFlyer(const string &newFreqFlyerNo)
{ isFreqFlyer = true;
freqFlyerNo = newFreqFlyerNo;
}
};

1.5.2 Constructors and Destructors

1.5.3 Classes and Memory Allocation

1.5.4 Class Friends and Class Members

class Matrix;              // Matrix definition comes later
class Vector {             // a 3-element vector
private:
  double coord[3];
public:
  // ...
  friend class Matrix;     // allow Matrix access to coord
};
class Matrix {             // a 3x3 array
private:
  double a[3][3];
public:
  // ...
  Vector multiplyBy(const Vector& v) {  // multiply (a * v)
    Vector w(0, 0, 0);
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
        w.coord[i] += a[i][j] * v.coord[j]; // access private data
    return w;
  }
};

Nesting Classes and Types within Classes

Classes may contain member variables, member functions and types. We can nest a class definition within another class.
class Complex {
private:
class Node {
// ...
};
//...
};

1.5.5 The Standard Template Library

Templates and the STL Vector Class

The Standard Template Library (STL) is a collection of useful classes for common data structures.
Standard containers:
stack
Container with last-in, first-out access
queue
Container with first-in, first-out access
deque
Double-ended queue
vector
Resizeable array
list
Doubly linked list
priority_queue
Queue ordered by value
set, multiset
Set
map, multimap
Associative array (dictionary)

Each STL-class object can store objects of any type.
vector<int> scores(100);  
vector<char> buffer(500);
vector<Passenger> passList(20);
vector<int> newScores = scores;
vector.cpp

Chapter 2: Object-Oriented Design

2.1 Goals and Principles

2.1.1 Object-Oriented Design Goals

Robustness, Adaptability, Reusability

2.1.2 Object-Oriented Design Principles

Abstraction, Encapsulation, Modularity


2.2 Inheritance and Polymorphism

2.2.1 Inheritance in C++

Inheritance allows the design of generic classes that can be specialized to more particular classes, with the specialized classes reusing the code from the generic class.
class Person {       // base class  
private:
string name; // name
string ssn; // social security number
public:
//...
void print(); // print data
string getName(); // retrieve name
};
class Student : public Person { // derived class
private:
string major; // major subject
int gradYear; // graduate year
public:
//...
void print(); // print data
void changeMajor(string newMajor); // change major
 };
Member Functions
Person person(...);       // define a person 
Student student(...); // define a student
cout << student.getName();// invokes Person::getName()
person.print(); // invokes Person::print();
student.print(); // invokes Student::print();
person.changeMajor("Math"); // ERROR!
student.changeMajor("Math"); // OK
void Person::print() // definition of Person print
{ cout << name << " " << ssn; }
void Student::print() // definition of Student print
{ Person::print(); // first print Person data
cout << major << gradYear;
}

Illustrating Class Protection

class Base {
private:
int priv;
protected:
int prot;
public:
int publ;
};
class Derived: public Base {
void someMemberFunction()
{ cout << priv; // ERROR: private member
cout << prot; // OK
cout << publ; // OK
}
};
class Unrelated {
Base X;
void anotherMemberFunction()
{ cout << X.priv; // ERROR: private member
cout << X.prot; // ERROR: protected member
cout << X.publ // OK
}
};

Constructors and Destructors

Person::Person(const string &nm, const string &ss)  
: name(nm), ssn(ss) {} // initializer list
Student::Student(const string &nm, const string &ss,
const string &maj, int year)
: Person(nm, ss), // initialize Person data members
major(maj), // initialize member
gradYear(year) {} // initialize gradYear
Person::~Person() // Person destructor
{ ... }
Student::~Student() // Student destructor
{ ... }
Student* s = new Student(...);
//...
delete s; // calls ~Student() then ~Person()
Static Binding
Person* pp[100];  
pp[0] = new Person(...);
pp[1] = new Student(...);

cout << pp[1]->getName(); // OK
pp[0]->print(); // calls Person::print()
pp[1]->print(); // calls Person::print()
pp[1]->changeMajor(...); // ERROR!

Dynamic Binding and Virtual Functions

class Person {       // base class  
virtual void print(); // print data
//...
};
class Student : public Person { // derived class
virtual void print(); // print data
//...
 };
Person* pp[100];
pp[0] = new Person(...);
pp[1] = new Student(...);

pp[0]->print(); // calls Person::print()
pp[1]->print(); // calls Student::print()
pp[1]->changeMajor(...); // OK

Virtual Destructors

If a class defines any virtual functions, it should define a virtual destructor, even if it is empty.
delete [] pp;


2.3 Templates

2.3.1 Function Templates

Minimum of two integers:
int min(int a, int b)  
{ return (a < b ? a : b); }
Minimum of doubles:
double min(double a, double b)  
{ return (a < b ? a : b); }
Generic function for an arbitrary type T, called function template:
template<typename T>      // parameter list
T min(T a, T b) // returns the minimum of a and b
{ return (a < b ? a : b); }
We can invoke our templated function to compute the minimum of objects of many different types. We could use any type, provided that the less than operator (<) is defined for this type. The compiler looks at the argument types, and determine which form of the function to instantiate:
cout << min(3, 4);      // invokes min<int>(3,4)
cout << min(6.9, 3.5); // invokes min<double>(6.9, 3.5)
cout << min('t', 'g'); // invokes min<char>('t', 'g')

2.3.2 Class Templates

template <typename Object>
class BasicVector {
Object* a; // array storing an element
int capacity; // length of array a
public:
BasicVector(int cap = 10) // constructor
{ capacity = cap;
a = new Object[capacity];// allocate array storage
}
Object& elemAtRank(int r) // access element at index r
{ return a[r]; }
// ...
};
BasicVector<int> iv(5);     // vector of 5 integers
BasicVector<double> dv(20); // vector of 20 doubles
BasicVector<string> sv(10); // vector of 10 strings
//...
iv.elemAtRank(3) = 8; // iv[3] = 8;
dv.elemAtRank(14) = 2.5; // dv[14] = 2.5
sv.elemAtRank(7) = "hello"; // sv[7] = "hello"


2.4 Exceptions

Exceptions are unexpected events that occur during the execution of a program.

2.4.1 Exception Objects

Using Inheritance to Define New Exception Types

class MathException {  // generic math exception
private:
string errMsg; // error message
public:
MathException(const string& err) { errMsg = err; }
};
class ZeroDivisionException : public MathException {
public:
ZeroDivisionException(const string& err)
: MathException(err) {}
};
class NegativeRootException : public MathException {
public:
NegativeRootException(const string& err)
: MathException(err) {}
};

2.4.2 Throwing and Catching Exceptions

try {
// ...
if (divisor == 0) throw ZeroDivisionException("Division by zero");
//...
}
catch (ZeroDivisionException& zde)
{ // handle division by zero
}
catch (MathException& me)
{ // handle any math exception other than division by zero
}

2.4.3 Exception Specification

void calculator() throw(ZeroDivisionException, NegativeRootException)
{
//...
}

void funct1(); // can throw any exception
void funct2() throw(); // can throw no exception

Generic Exception Class

class RuntimeException {  // generic run-time exception
private:
string errorMsg;
public:
RuntimeException(const string& err) { errorMsg = err; }
string getMessage() const { return errorMsg; }
};
inline std::ostream& operator<<(std::ostream& out, const RuntimeException& e)
{ return out << e.getMessage(); }