More on Templates

Templates can be used with functions and with classes.

Templates are used because rewriting the same code over and over to handle different types is awful.

Suppose we want to write a function that returns the absolute value of two numbers.

We can write an absolute value function as a template, so it will work with any numerical type.

#include <iostream>

using namespace std;

template <class T>
T tAbs(T num) {
	return (num < 0) ? -num : num;
}

int main()
{
	long long lOne = -19191138L;
	long long lTwo = -20012010L;

	double dOne = 19.99;
	double dTwo = -8.32;


	std::cout << lOne << " " << tAbs(lOne) << std::endl;
	std::cout << dTwo << " " << tAbs(dTwo) << std::endl;

    return 0;
}

Function templates represent the data type used by the function not as a specific type, such as int, but by an identifier that can stand in for any type. The template keyword signifies to the compiler that we’re about to define a function template. The variable following the keyword class is called the template argument.

The compiler decides how to compile the function based entirely on the data type used in the function call’s argument.

Le’ts look at a function template with two arguments.

#include <iostream>

using namespace std;

//function returns index of item, or value < 0 if not found
template <class theType>
int find(theType arrayParam[], theType value, int size) {
	for (int j = 0; j < size; j++) {
		if (arrayParam[j] == value) {
			return j;
		}
	}
	return -1;
}

//outputs value if index >= 0;
template <class theType>
void printIt(theType arrayParam[], int indexParam) {
	if (indexParam >= 0) {
		std::cout << "At index " << indexParam << " " << arrayParam[indexParam] << std::endl;
	}
	else {
		std::cout << "Value not found." << std::endl;
	}
}


int main() {

	char chrArr[] = { 'h', 'U', 't', 'i', 'D', 'v', 's' };
	int intArr[] = { 42, 1138, 73, 47, 8086, 1999, 3 };

	int iLength = 7;
	int iIndex = -1;

	iIndex = find(chrArr, 'v', iLength);
	printIt(chrArr, iIndex);
	printIt(chrArr, find(intArr, 2001, iLength));

	return 0;

}

We can use more than one template argument in a function template.

#include <iostream>

using namespace std;



//outputs value if index >= 0;
template <class TOne, class TTwo>
void printBoth(TOne paramOne, TTwo paramTwo){
	std::cout << "Parameter one is " << paramOne << std::endl;
	std::cout << "Parameter two is " << paramTwo << std::endl;
}


int main() {

	printBoth('c', 1982);
	printBoth("If mice could swim, they would float with the tide and play with the fish down by the seaside. The cats on the shore would quickly agree.", 7);
	printBoth(19.99, 'X');

	return 0;

}

It is not always easy to know if we can instantiate a template function for a particular data type. One thing we can do is check if the operators used in the function can work on the data type.

STL Vectors

There are a few different ways to declare a vector.

vector vec;

Creates a zero-length int vector.

vector dVec;

Creates a zero-length double vector

vector cVec(128);

Creates a 128-element char vector

vector iVec(10, 42);

Creates a ten element vector initialized to 42.

vector iVec2(iVec);

Creates an int vector from an int vector.

The subscripting operator, [], is defined for a vector, which allows us to access the elements of a vector using standard array subscripting notation.

The boolean empty() property returns true if the vector contains no elements. The size() property returns the number of elements in the vector.

The insert() method inserts a value. We can specify the element before which we wish to insert the new value. The pop_back() method removes the last element in the vector. The push_back() method adds an element to the end of the vector.

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>

using namespace std;

int main(){
    
    //create a vector 
    vector<int> iVec(10);
    int iCounter, iLimit = 10;
    
    
    //display size
    cout << "Size is " << iVec.size() << endl;
    
    srand(time(NULL));
    
    //assign some values
    for(iCounter = 0; iCounter < iLimit; iCounter++){
        iVec[iCounter] = rand() % 100;
    }
    
    //display contents
    for(iCounter = 0; iCounter < iLimit; iCounter++){
        cout << iVec[iCounter] << " ";
    }
    
    cout << endl;
    
    //expand vector
    
    iLimit = rand() % 100 + 1;
    
    for(; iCounter < iLimit; iCounter++){
        iVec.push_back(rand() % 50);
    }
    
    cout << "Size is now " << iVec.size() << endl;
    
    cout << "Current countents" << endl;
    
    iLimit = iVec.size();
    for(iCounter = 0; iCounter < iLimit; iCounter++){
        cout << iVec[iCounter] << " ";
    }
    
    
    
}

In the above program, the header is included. The header must be included in any porgram that uses a vector.

In the second round of adding values, we use the push_back() function so as to cause the vector to grow in order to accommodate new elements. Vectors can grow when elements are added to them. Vectors will also shrink when elements are removed.

 

#include <iostream>
#include <vector>

using namespace std;

int main(){
    vector<char> cVec;
    int i;
    
    int iLimit = 26;
    
    for(i = 0; i < iLimit; i++){
        cVec.push_back(i + 'A');
    }
    
    for(i = 0; i < iLimit; i++){
        cout << cVec[i] << " ";
    }
    
    cout << endl;
    
    do{
        cVec.pop_back();
        for(i = 0, iLimit = cVec.size(); i < iLimit; i++){
            cout << cVec[i] << " ";
        }
        cout << endl;
            
        
    }while(cVec.empty() == false);
    
    
}

The pop_back() function is the opposite of the push_back() function.

Dynamically Allocated Character Arrays and the String class

Let’s take a look at allocating character arrays on the heap. When working with strings, ideally we would like to allocate only enough memory to store the given string. The way to do this is to allocate memory off of the heap and keep just a pointer to the first character of the allocated block.

We will see how to do this implementing a class for storing addresses; then, we will use the C++ String class instead.

#pragma once
class CAddress
{
public:
	//constructor
	CAddress();

	//destructor
	~CAddress();

	//copy constructor and assignment operator
	CAddress(const CAddress&);
	const CAddress& operator=(const CAddress&);

	//getters and setters
	const char* lastName() const { return _lastName; }
	void lastName(const char * nameParam);

	const char* firstName() const { return _firstName; }
	void firstName(const char * nameParam);

	const char* phone() const { return _phone; }
	void phone(const char* phoneParam);

	const char* address() const { return _address; }
	void address(const char*);

private:
	char * _lastName;
	char * _firstName;
	char * _phone;
	char * _address;

	char *dupStr(const char *s);
};

Variables of type char * usually represent a null-terminated string of characters. As the string can be of any length, we do not need to enumerate the maximum length of the strings.

The class destructor is very important, now that we are allocating memory from the heap. We use the destructor to ensure the memroy is returned to the heap when the object is destroyed.

A default constructor is a constructor that can be called with no arguments.

A copy constructor is a constructor that can be called with an object of the same class as its only argument.

An assignment operator is used to copy the value of one object to another of the same type.

#include "Address.h"

#include <cstring>

CAddress::CAddress()
{
	_lastName = new char[1];
	_firstName = new char[1];
	_address = new char[1];
	_phone = new char[1];

	_lastName[0] = '\0';
	_firstName[0] = '\0';
	_address[0] = '\0';
	_phone[0] = '\0';

}

//destructor
CAddress::~CAddress()
{
	//clean up time
	delete[] _lastName;
	delete[] _firstName;
	delete[] _phone;
	delete[] _address;
}

char *CAddress::dupStr(const char* sParam) {
	int len = strlen(sParam);
	char *rString = new char[len + 1];
	strncpy_s(rString, len+1, sParam, len);
	return rString;
}

//assignment operator
const CAddress& CAddress::operator=(const CAddress& addressParam) {
	if (this != &addressParam) {
		lastName(addressParam._lastName);
		firstName(addressParam._firstName);
		phone(addressParam._phone);
		address(addressParam._address);
	}
	return *this;
}

void CAddress::lastName(const char *sParam) {
	if (_lastName != sParam) {
		delete[] _lastName;
		_lastName = dupStr(sParam);
	}
}

void CAddress::firstName(const char *sParam) {
	if (_firstName != sParam) {
		delete[] _firstName;
		_firstName = dupStr(sParam);
	}
}

void CAddress::phone(const char *sParam) {
	if (_phone != sParam) {
		delete[] _phone;
		_phone = dupStr(sParam);
	}
}

void CAddress::address(const char* sParam) {
	if (_address != sParam) {
		delete[] _address;
		_address = dupStr(sParam);
	}
}

It is important to avoid orphaned memory, where memory exists on the heap that is not referenced by any pointer. We cannot free this memory back to the heap.

To avoid potential orphaned memory and resulting memory leaks, we created our own copy constructors and assignment operators. We check to make sure we are not assigning an object to itself, and then we perform a deep copy, where each field’s data is copied into the corresponding target object’s field. In this way two objects may contain the same data, but not share the same memory.

An easier version of this class can be implemented using the String class. The String class encapsulates all of the memory allocation, deallocation, and copying into a readymade class for us.

#pragma once

#include <string>

class CAddressString
{
public:
	CAddressString();
	~CAddressString();
	CAddressString(const CAddressString &addressObj);
	CAddressString& operator=(const CAddressString &addressObj);

	//getters and setters
	std::string lastName() const { return _lastName; }
	void lastName(const std::string & strParam);

	std::string firstName() const { return _firstName; }
	void firstName(const std::string& strParam);

	std::string phone() const {	return _phone; }
	void phone(const std::string & strParam);

	std::string address() const { return _address;  }
	void address(const std::string & strParam);


private:
	//fields
	std::string _lastName;
	std::string _firstName;
	std::string _phone;
	std::string _address;
};

One of the best parts of the standard library is it helps reduce our use of raw pointers, because raw pointers tend to introduce a lot of errors into C++ code.

Simple Sorting and Searching in C++

A sorting method arranges a set of elements into either an ascending or descending sequence.

A search method looks for a specified element in a set.

Sorting methods usually involve the two basic operations of comparing and swapping elements.

We will first implement an insertion sort of an array. Our insertion sort will go backwards through an array of n elements. Our method first compares the element array[n-1] and array[n-2]. If array[n-1] is less than array[n-2], then array[n-1] is inserted before array[n-2], thus leading to a sublist sorted in ascending order. We insert one element after another into this sorted sublist in an interative fashion until the whole array is sorted.

We’ll start with our basic sort class definition.

#pragma once

#include <iostream>

class Sort{
    private:
        int *iArray;
        int n;
    public:
        Sort(int size);
        copyList(int toCopy[], int size);
        void insertionSort();
}

Next, let’s implement these functions.

#include "sort.h"

Sort::Sort(int n){
    this->n = n;
    this->iArray = new int[n];
}

void Sort::copyList(int toCopy[], int length){
    //check for bounds
    if(length > n) { length = n; }
    if(length < 1) { return; }
    
    while(length-- > 0){
        this->iArray[length] = toCopy[length]; 
    }
}

void Sort::printArray(){
    int i = 0;
    for(; i < n; i++){
        std::cout << iArray[i] << " ";
    }
    std::cout << std::endl;
}

void Sort::insertionSort(){
    int j = n - 1;
    int i, swap;
    
    while(j > 0){
        i = j--;
        swap = iArray[j];    
        while((swap > iArray[i]) && (i < n)){
            iArray[i - 1] = iArray[i];
            i++;
        }
        iArray[i - 1] = swap;
        //show progress
        printArray();
    }
}

And let’s see if it all works.

#include "sort.h"

int main(){
   
    const int array_size = 14;
   
    int dataArray[array_size] = {1138, 42, 10538, 8, 389, 443, 1701, 73, 47, 451, 88, 3, 5, 17}; 
    
    Sort theSorter(array_size);
    
    theSorter.copyList(dataArray, array_size);
    
    theSorter.insertionSort();
    
    return 0;
    
}

So far, so good!

Insertion sort isn’t exactly the greatest sort out there. In the worst case scenario we get O(n^2) performance.

Next, let’s look at an implementation of bubble sort.

First, let’s add the method definition to sort.h

void bubbleSort();

Next, let’s implement the method.

void Sort::bubbleSort(){
    
    bool swapFlag = true;
    int iteration, i, j, temp;
    
    for(iteration = 0; iteration < n && swapFlag == true; iteration++){
        swapFlag = false;
        for(i = 0, j = n - iteration; i < j; i++){
            if(iArray[i] > iArray[i+1]){
                //swap the items 
                swapFlag = true;
                temp = iArray[i];
                iArray[i] = iArray[i+1];
                iArray[i+1] = temp;
                printArray();
            }
        }
    }
}

Bubble sort has some issues. For one, it depends on comparing and swapping only consecutive data elements. In each iteration, only one element actually ends up definitely where it is supposed to be in the sequence.

The quicksort algorithm is one of the most efficient sorting algorithms. Quicksort divides the set into smaller subsets, it then sorts the subsets, and the merges the sorted subsets into the final sequence. The divide-and-conquer strategy splits a big problem into smaller, easier to solve problems, then puts it back together again. Quicksort splits the data up by selecting a key and using it as a pivot value so that it divides the data into a left and right subset. Our implemenation of quicksort will involve recursive calls to the same function, which will eat up stack space. Also, there is the concern of the memory needed for copying into the new sorted array – although there is an in place version of quicksort available.

We will create two functions, a public function, and a private function called by the public function

#pragma once

#include <iostream>

class Sort{
    private:
        int *iArray;
        int n;
        void quickSortHelper(int first, int last);
    public:
        Sort(int size);
        void copyList(int toCopy[], int size);
        void insertionSort();
        void bubbleSort();
        void quickSort();
        void printArray();
};

Then we will implement the two functions.

void Sort::quickSort(){
    if(n > 0){ quickSortHelper(0, n-1); }
    printArray();
}

void Sort::quickSortHelper(int first, int last){
    std::cout << "first: " << first << " " << "last: " << last << std::endl;
    //check that first is before last
    if(first < last){
        int temp;
        int thePivot = iArray[first];
        int leftIndex = first;
        int rightIndex = last;
        
        std::cout << "The pivot value: " << thePivot << std::endl;
        
        while(leftIndex < rightIndex){
            while(iArray[leftIndex] <= thePivot && leftIndex < last){
                //move the left index to the right
                //until it points to an element greater in value 
                //than the element in the first element
                leftIndex++;
            }
            while(iArray[rightIndex] >= thePivot && rightIndex > first){
                //move the right index to the left
                //until it points to an element less than in value
                //in the first element
                rightIndex--;
            }
            //if the left index is less than the right index
            //swap the two elements
            if(leftIndex < rightIndex){
                temp = iArray[rightIndex];
                iArray[rightIndex] = iArray[leftIndex];
                iArray[leftIndex] = temp;
            }
        }
            
        //switch theArray[lastIndex] with theArray[firstIndex]
        temp = iArray[rightIndex];
        iArray[rightIndex] = iArray[first];
        iArray[first] = temp;
        
        printArray();
        //sort the section to the left of the new pivot
        quickSortHelper(first, rightIndex - 1);
        //sort the section to the right of the new pivot
        quickSortHelper(rightIndex + 1, last);
        
    }
}

 

Class Templates in C++

Class templates enable us to parameterize a type. The standard library, including standard I/O streams, strings, vectors, and maps, relies heavily on class templates.

Just as with function templates, class templates are marked with the template keyword. We supply the template arguments when instantiating the class. Whenever we use a different template argument, the compiler generates a new class for us with the appropriate type.

Let’s create a templated point class that accepts a type parameter that we will simply call Type.

#pragma once
#include 
#include 

template<class Type>
class Point{
    private:
        Type _x, _y;
    public:
        Point(Type x, Type y) : _x(x), _y(y) {}
        Type x() const { return _x; }
        Type y() const { return _y; }
        void moveTo(Type x, Type y){
            _x = x;
            _y = y;
        }
        void moveBy(Type x, Type y){
            _x += x;
            _y += y;
        }
        std::string toString(){
            std::stringstream ss;
            ss << "(" << _x << ", " << _y << ")";
            return ss.str();
        }
        
};

Note that the implementation for the template class is in the definition. Note as well that member functions of a class template are themselves function templates, using the same template parameters. However, we do not need to pass template parameters when using template class methods. We simply pass the template parameter or paremeters into the class during instantiation.

When we use a template, it is called instantiating the template. A template instance is thus a a concrete class that the compiler creates by using the template arguments to the template definition. Template initialization is the realization of a template for a specific set of template arguments.

#include <iostream>
#include "pointclass.h"


int main(){
    
    Point a(42, 47);
    Point b(19.19, 3.14159);
    
    std::cout << a.toString() << std::endl;
    std::cout << b.toString() << std::endl;
    
    a.moveTo(73, 1701);
    b.moveBy(2.51, 3.3);
    
    std::cout << a.toString() << std::endl;
    std::cout << b.toString() << std::endl;
    
    return 0;
}

A template instantiation that is created for us by the compiler is called an implicit specialization. It is also possible to hand code specific instatiations for particular types, but that’s something to talk about some other time.

It’s straightforward enough to create a template class for a point. Now, let’s try something a bit more complicated; let’s create a class to handle rational numbers.

#pragma once
#include <string>
#include <sstream>
#include <stdexcept>

template<class Type>
class Rational{
    private:
        Type _numerator;
        Type _denominator;
    public:
        //constructors
        Rational() : _numerator(0), _denominator(1) {}
        Rational(Type numerator) : _numerator(numerator), _denominator(1) {}
        Rational(Type numerator, Type denominator);
        
        template<class ReturnType>
        ReturnType convert() { 
            return static_cast<ReturnType>(_numerator) / static_cast<ReturnType>(_denominator);
        }
        
        Type numerator() const { return _numerator; }
        Type denominator() const { return _denominator; }
        
        std::string toString();
        
        //overloaded assignment
        Rational<Type>& operator=(const Rational<Type> &a);
    
};


template<class Type>
Rational<Type>::Rational(Type numerator, Type denominator){
        if(denominator == 0){
            //throw exception b/c denominator is 0
            throw std::invalid_argument("received 0 value for denominator");
        } else {
            _numerator = numerator;
            _denominator = denominator;
        }
}

template<class Type>
std::string Rational<Type>::toString(){
    std::stringstream ss;
    ss << _numerator << "/" << _denominator; return ss.str(); } //overloaded functions template<class Type>
bool operator==(Rational<Type> const& a, Rational<Type> const& b){
    return (a.numerator() == b.numerator()) && (a.denominator() == b.denominator());
}

template<class Type>
Rational<Type>& Rational<Type>::operator=(const Rational<Type> &a){
    _numerator = a.numerator();
    _denominator = a.denominator();
    return *this;
}

Programming with templates can be frustrating at first, but it adds a lot of power and flexibility. A template alets us write a function or class once, and lets teh compiler generate actual functions and classes for different template arguments.

Recursive Definitions in C++

Recursive definitions help us to define large finite and infinite sets. Recursive definitions are frequently used to define functions and sequences of numbers.

Recursive definitions have two parts. The first part, the ground or base case, contains the basic elements that are the building blocks for the other elements in the defined set. The second part allows for the construction of new objects using the elements defined in the first part. A trivial example is a list of the natural numbers, which begins with 0 and adds one from there, such that any number is the previous number plus one, going all the way back to 0.

#include 

using namespace std;

int main(){
    
    int iNumber;
    
    cout  iNumber;
    
    while(iNumber > 0){
        cout << iNumber << " = ";
        cout << --iNumber << " + 1" << endl;
    }
    
    cout << "Base Case: " << iNumber << endl;
    
    return 0;
    
}

Recursive definitions have two purposes: creating new elements, and testing whether or not an element belongs to a set. With testing, we reduce a complex problem to a simpler problem, and if that isn’t simple enough, we reduce it to a still simpler problem, again and again as needed, until we reach the base case. Being able to decompose big problems into smaller, more manageable problems is the essence of constructing algorithms, recursive or not.

The most famous basic example of recursion is the factorial, represented as !. A factorial function defines a base case of N equaling 0, which returns the value of 1. For each value N greater than 0, the value returned is N * (N – 1) . Thus, 5! = 5  * 4 * 3 * 2 * 1.

#include 

using namespace std;

int fact(int n);

int main(){
    
    int iNum;

    while(true){
        cout  iNum;
        if(iNum < 0){
            break;
        } else {
            cout << fact(iNum) << endl;
        }
        
    }    
    
    return 0;
    
}

Functions are allocated on the run-time stack in a stack frame. Each function contains its own state, including the contents of all automatic variables, the values of the function’s parameters, and by the return address indicating where to restart its caller.  Stack frames typically have a short lifespan as they are allocated when the function is called and deallocated when the function exits.

Because the system creates a new stack frame whenever a function is called it can handle recursion, as each function invocation is represented internally by a different stack frame storing a different state.

A good example of a recursive function is a function that raises a number x to an integer power. The function is defined as 1, if n equals 0, and x * x ^ (n – 1) if n is greater than 0. The base case produces a value of 1, which is passed back to the previous recursive call that has been waiting for this result. This previous recursive call then calculates its own return value, which is passed onto its previous recursive call, which has been waiting for the result, and so on.

#include 

using namespace std;

int exp(int iNum, int iExp){
	if(iExp == 0) { 
		cout << "1" << endl; 
		return 1;  
	} else {
		cout << iNum << " * ";		
		return iNum * exp(iNum, --iExp);
	}

}

int main(){

	int iNum, iExp;
	cout << iNum;
	cout << iExp;
	cout << exp(iNum, iExp) << endl;
	
        return 0;

}

A fun example of something that can be easily implemented using recursion is printing an input line in reverse order. It’s actually very simple to do in C++.

#include 

using namespace std;

void reverse(){
    char ch;
    cin.get(ch);
    if(ch != '\n'){
        reverse();
        cout.put(ch);
    }
}

int main(){
   
   cout << "Enter a line to have it written backwards: ";
    
    reverse();
    
    return 0;
    
}

The reverse() function up above works because each call to reverse() has its own local ch variable.

Pointers, New, and Delete

A data type has a set of values and a set of operations that can be performed on these values. Data types also typically have a name, such as double or int. The pointer data type, however, does not have a name; instead, it is represented by an asterisk. The values that belong to a pointer data type are the memory addresses of the computer. A pointer variable is thus a variable whose content is a memory address.

In C++, we declare a pointer variable by placing the asterisk symbol between the data type and the variable name. The value of a pointer variable is an address of another memory space; when we declare a pointer variable, we also specify the data type of the value stored in this other memory space.

int main()
{
	//pointer to an address storing an int
	int * iPtr;
	//pointer to an address storing a char
	char* chPtr;
	//pointer to an address storing a double
	double *dPtr;

    return 0;
}

Note that the asterisk can appear anywhere between the data type name and the variable name.

The asterisk symbol does double duty as both the stand-in for the pointer data type name, as well as the dereferencing operator (it is also the symbol for multiplication as well). The address of operator is represented by the & symbol.

The address of operator, &, is used to get the memory address of a variable. The dereferencing operator, *, is used to retrieve the value stored at the memory address that the pointer points to. The derefencing operator can also be used to store a new value in the memory address that is pointed to by the pointer.

using namespace std;

int main()
{
	int i = 42;
	int *iPtr = &i;

	cout << i << " is at " << &i << endl;
	cout << iPtr << " is at " << &iPtr << endl;

	cout << "The pointer stored at " << &iPtr << " stores the memory address " << iPtr << endl;
	cout << " which stores the value " << *iPtr << endl;

    return 0;
}

It is of course possible to have multiple pointer variables point to the same memory location. Likewise, we can even have pointers that point to other pointers!

using namespace std;

int main()
{

	double dOne = 19.99;
	
	double *dPtrOne = &dOne;
	double *dPtrTwo = &dOne;

	double **doubledPtr = &dPtrOne;

	cout << "dPtrOne is at " << &dPtrOne << " and stores the memory address " << dPtrOne << " which stores the value " << *dPtrOne << endl;
	cout << "dPtrTwo is at " << &dPtrTwo << " and stores the memory address " << dPtrTwo << " which stores the value " << *dPtrTwo << endl << endl;

	//we can change the value pointed to using either pointer
	*dPtrOne = 3.14159;
	//derefencing either pointer yields the changed value
	cout << "dPtrTwo points to the value " << *dPtrTwo << endl;

	cout << "doubledPtr is at " << &doubledPtr << " and stores the value " << doubledPtr << " which pointers to the value " << *doubledPtr <<  endl << endl;

	if (*doubledPtr == dPtrOne) {
		cout << "*doubledPtr and dPtrOne are the same" << endl;
	}

	if (*doubledPtr == &dOne) {
		cout << "*doubledPtr and &dOne are the same" << endl;
	}

	if (**doubledPtr == *dPtrOne) {
		cout << "**doubledPtr and *dPtrOne are the same" << endl;
	}
    return 0;
}

We can also create pointers that point to other data types like structures or classes.

int main()
{

	struct exampleStruct {
		int a;
		char b;
		double c;
	};

	struct exampleStruct exmp;
	struct exampleStruct *exmpPtr = &exmp;

	exmp.a = 73;
	exmp.b = 'z';
	exmp.c = 25.806975;

	cout << (*exmpPtr).a << endl;
	cout << (*exmpPtr).b << endl;
	cout << (*exmpPtr).c << endl;

    return 0;
}

Note that in C++ the dot operator has a higher precedence than the dereferencing operator. Thus, when dereferencing pointers to structs or class objects, we need to use parentheses to ensure that the pointer is dereferenced before the dot operator, or we can use the alternative arrow operator, ->, officially called the member access operator arrow.

We can allocate  and deallocate memory during the program’s execution using pointers. C++ provides two operators, new and delete, to create and destroy dynamically allocated variables. In C++, new and delete are reserved words.

The operator new can both allocate single variables as well as arrays of variables. The operator new allocates the memory for the designated type and returns a pointer to this memory.  Because dynamic variables can’t be accessed directly as they are not named, they must instead be accessed via the pointer that references the memory location where the dynamic variable is.

using namespace std;

int main()
{

	int *iPtr;
	double *dPtr;
	char *chPtr;

	iPtr = new int;
	dPtr = new double;
	chPtr = new char[64];

	*iPtr = 404;
	*dPtr = 169.254;

	strcpy_s(chPtr, 64, "Saluton Mundo!");

	cout << chPtr << endl;
	//note: not the same as
	cout << *chPtr << endl;

	cout << *iPtr << " " << *dPtr << endl;


    return 0;
}

The delete operator is used to free up memory allocated with new. It’s very important to free up the memory we use after we are done with it. Why? Because once the pointer to a block of memory goes out of scope, or is assigned a new block of memory, we no longer have a means of accessing the previously allocated memory. But it’s still there on the heap, taking up space. Such a situation is called a memory leak.

int main()
{
	double *dPtr = new double;

	*dPtr = 1.645;

	cout << *dPtr << "\t(" << dPtr << ")" << endl;
	
	//assign it new memory
	dPtr = new double;
	*dPtr = 1.96;

	//previously allocated memory is now lost
	//note the new address
	cout << *dPtr << "\t(" << dPtr << ")" << endl;

    return 0;
}

This and Operator Overloading in C++

Member functions can directly access the class’s member variables for a given object,  including a hidden pointer to itself, called this.

#include 
#include 

class Circle {
private:
	double radius;
	const double cPi = 3.141593;
public:
	double diameter();
	double area();
	Circle& setRadius(double r);
};

Circle& Circle::setRadius(double r) {
	this->radius = r;
	return *this;
}

double Circle::diameter() {
	return this->radius * 2;
}

double Circle::area() {
	return cPi * (radius * radius);
}


int main()
{

	Circle myCircle;

	std::cout << std::fixed << std::showpoint << std::setprecision(2);

	myCircle.setRadius(5);

	std::cout << "Circle's area is " << myCircle.area() << std::endl;

	//associativity of the dot operator is left or right
	std::cout << "Circle's area now is " << myCircle.setRadius(11).area() << std::endl;


    return 0;
}

A friend function of a class is a nonmember function of the class that still has access to the private members of the class. We can make a function be a friend to a class by preceding the function prototype with the reserved word friend. As the function is not a member of the class, we do not include the scope resolution operator or the name of the class in the function’s definition.

Let’s add a friend function to the Circle class.

class Circle {
private:
	double radius;
	const double cPi = 3.141593;
	friend void circleExpand(Circle &cObject);
	friend void circleContract(Circle &cObject);
public:
	double diameter();
	double area();
	Circle& setRadius(double r);
};

//no need for friend keyword in definition
void circleExpand(Circle &cObject) {
	cObject.radius++;
}

void circleContract(Circle &cObject) {
	cObject.radius--;
}

int main()
{
	int i = 1;
	Circle myCircle;

	std::cout << std::fixed << std::showpoint << std::setprecision(2);

	myCircle.setRadius(i);

	while (i++ < 10) {
		std::cout << " The diameter is " << myCircle.diameter() << " and the area is " << myCircle.area() << std::endl;
		circleExpand(myCircle);
	}

    return 0;
}

C++ comes with some operators that are already overloaded. For example, the arithmetic operators are able to handle both integers and floating point numbers, and the addition and subtraction operators can also handle pointers. The right and left shift operators are likewise used to insert and extract data from the input and output streams.

While the only built-in operations on classes are the assignment operator and the member selection operator, it is possible to extend the definitions of most of the operators that can be applied to classes.

In order to write functions to overload operators, we use the reserved operator keyword followed by the operator we want to overload.

C++ operator functions can be either member functions or friend functions of a class. Let’s take a quick look at operator overloading with member functions.

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cmath>

class Point {
private:
	double x;
	double y;
public:
	void setX(double xValue);
	void setY(double yValue);
	double getX() const;
	double getY() const;
	std::string toString() const;
	double getDistance(Point p) const;
	
	//overloaded operators
	Point operator+(const Point& p) const;
	Point operator-(const Point& p) const;
	bool operator==(const Point& p) const;
	
};


void Point::setX(double xValue) {
	this->x = xValue;
}
void Point::setY(double yValue) {
	this->y = yValue;
}

double Point::getX() const {
	return this->x;
}

double Point::getY() const {
	return this->y;
}

std::string Point::toString() const {
	std::ostringstream oStringStream;
	oStringStream << "(" << this->x << ", " << this->y << ")"; 	
       return oStringStream.str(); 
} 

double Point::getDistance(Point p) const {
 	return sqrt(pow(this->x - p.x, 2) + pow(this->y - p.y, 2));
}


//overloaded operator functions------
Point Point::operator+(const Point& p) const {
	Point tempPoint;
	tempPoint.x = this->x + p.x;
	tempPoint.y = this->y + p.y;
	return tempPoint;
}

Point Point::operator-(const Point& p) const {
	Point tempPoint;
	tempPoint.x = this->x - p.x;
	tempPoint.y = this->y - p.y;
	return tempPoint;
}

bool Point::operator==(const Point& p) const {
	return (this->x == p.x && this->y == p.y);
}
//end overloaded operator functions------------------

int main()
{
	Point pointA;
	pointA.setX(42.73);
	pointA.setY(538);

	Point pointB;
	pointB.setX(14);
	pointB.setY(10.11);

	Point pointC;


	if (pointA == pointB) {
		std::cout << "Point A and Point B equal each other." << std::endl;
	}
	else {
		std::cout << "Point A and Point B do not equal each other." << std::endl;
	}

	std::cout << "Point A is at " << pointA.toString() << " and Point B is at " << pointB.toString() << std::endl;
	std::cout << "The distance between Point A and Point B is " << pointA.getDistance(pointB) << std::endl;

	pointC = pointA - pointB;

	std::cout << "Point C is at " << pointC.toString() << std::endl;


    return 0;
}

Objects and Functions in C++

Let’s take a look at constructor overloading, objects as function arguments, and returning objects from functions.

CDistance.h

#ifndef CDISTANCE_H
#define CDISTANCE_H

class CDistance
{
    public:
        CDistance() : _meters(0), _centimeters(0.0) {}
        CDistance(int meters, double centimeters) : _meters(meters), _centimeters(centimeters) {}

        void getDist();
        void showDist();
        voidd addDist(CDistance a, CDistance b);

    private:
        int _meters;
        double _centimeters;
};

#endif // CDISTANCE_H

Our overloaded constructors use initialization lists to specify what we want to set the member variables to.

The scope resolution operator, ::, is used to specify what class something is associated with. We use the scope resolution operator when defining member functions outside the class declaration.

CDistance.cpp

#include "CDistance.h"
#include <iostream>

void CDistance::getDist() {
    std::cout << "Please enter meters: ";     
    std::cin >> _meters;
    std::cout << "Please enter centimeters: ";     
    std::cin >> _centimeters;
}

void CDistance::showDist(){
    std::cout << _meters << " m " << _centimeters << " cm "; 
} 

void CDistance::addDist(CDistance a, CDistance b){     
        _centimeters = a._centimeters + b._centimeters;     
        _meters = 0;    
     if(_centimeters >= 100){
        _centimeters -= 100;
        _meters++;
    }
    _meters += a._meters + b._meters;
}

The syntax for function parameters that are objects is the same as that for arguments are are simple times such as int or char.

A special example of passing in an object as an argument is the default copy constructor. The default copy constructor performs a member-wise copy of one object’s values to another.

#include "CDistance.h"

using namespace std;

int main()
{
    CDistance dOne;
    CDistance dTwo(5, 50);
    CDistance dThree;


    cout << "First Distance: " ;
    dOne.showDist();
    cout << endl;

    cout << "Second Distance: " ;
    dTwo.showDist();
    cout << endl;

    //default copy constructor
    dOne = dTwo;

    dThree.addDist(dOne, dTwo);

    dTwo.addDist(dOne, dThree);

    cout << "First Distance: " ;
    dOne.showDist();
    cout << endl;

    cout << "Second Distance: " ;
    dTwo.showDist();
    cout << endl;

    cout << "Third Distance: " ;
    dThree.showDist();
    cout << endl;


    return 0;
}

 

C++ Classes Review

Classes differ from structures in that they allow for data hiding, abstraction and encapsulation, inheritance,  and polymorphism. With a class definition, the class name is a type of name. A class is an abstract data type; a class specifies how objects of its type behave, are created and deleted, and are accessed.

The keywords private, protected, and public are used to specify three levels of access protection for data hiding and utility methods. private means that the member can only be accessed by the member functions and friends of the class. The private member is not directly accessible to the outside world. public means that member can be accessed by any function in the external environment. Public methods are interfaces to the outside world so that any other function can interact with it. protected means that the member can only be accessed by the class and member functions and classes derived from this class.

#include <iostream>

class CClock
{
    public:

        //constructors
        CClock();
        CClock(int h, int m, int s);

        //getters - setters
        void setHour(int h);
        void setMinute(int m);
        void setSecond(int s);

        int getHour() const;
        int getMinute() const;
        int getSecond() const;

        void printTime();

    private:

        int _hour;
        int _min;
        int _sec;
};

Member functions of a class are typically defined outside the class header file. The scope resolution operator, ::, is used to assign the function to a class.

The first member functions we can look at are the constructors. Constructors are special methods that are called automatically when an object of a class type is created. The constructor or constructors job is to initialize the object’s state – it’s member variables. If no constructor is specified then the compiler provides one by default.

Since C++ does not have zero initialization of member variables such as ints, it is a good idea to modify the default constructor so that it sets a default value for each member.

//default constructor
CClock::CClock()
{
    _min = 0;
    _hour = 0;
    _sec = 0;
}

//initialize the member variables to specific values
CClock::CClock(int h, int m, int s){
    _min = m;
    _sec = s;
    _hour = h;
}

Getters and setters are used to assign values to data members. Getters are usually declared to be const so as to ensure that the values aren’t accidentally modified during retrieval. Setters often do value checking to ensure that the new data is in the proper range.

//setters
void CClock::setHour(int h){
    //only set hour if it falls in the range 0 - 23
    if(0 <= h && h < 24){
        _hour = h;
    }
}

void CClock::setMinute(int m){
    if(0 <= m && m < 60){
        _min = m;
    }
}

void CClock::setSecond(int s){
    if(0 <= s && s < 60){
        _sec = s;
    }
}

//getters
int CClock::getHour() const {
    return _hour;
}

int CClock::getMinute() const {
    return _min;
}

int CClock::getSecond() const {
    return _sec;
}

Finally, we will set up a method to print the data in a formatted manner.

void CClock::printTime(){
    if(_hour < 10){
        cout << "0";
    }
    cout << _hour << ":";

    if(_min < 10){
        cout << "0";
    }
    cout << _min << ":";

    if(_sec < 10){
        cout << "0";
    }
    cout << _sec;
}

There are two ways to access a member of a class. We typically access a data or function member of the class object using ., the dot operator.When a pointer to an object of a class is used, we use the -> operator to access data or function members of the class.

#include "CClock.h"

using namespace std;

int main()
{
    CClock clockOne;
    CClock clockTwo(22, 15, 00);

    cout << "Clock One after default initialization: ";
    clockOne.printTime();
    cout << endl;

    cout << "Clock Two after being initialized to a specific time: ";
    clockTwo.printTime();
    cout << endl;

    clockOne.setHour(3);
    clockOne.setMinute(33);
    clockOne.setSecond(15);

    cout << endl;
    clockOne.printTime();

    return 0;
}