In Lab 5 you were introduced how to write your first C++ class (concrete) and your first C++ template class. See also live recording at the corresponding Campusewire post Lab 5 Thursday 16-04-2020. Then, you applied the newly learned features to make a doubly-linked list.

This note will walk you through the same concepts by additional examples that will also introduce new interesting concepts and features in C++. To download and prepare the example files, at any location in your disk create new directory to contain the examples files:

mkdir -p section06/part-a
cd section06/part-a
wget -i https://raw.githubusercontent.com/sbme-tutorials/sbme-tutorials.github.io/master/2020/data-structures/snippets/section06/part-a/download.txt

After that, you can also build all the examples in an isolated folder as following:

mkdir build
cd build
cmake ..
make

Before the examples, let’s take the following note on the difference between struct and class keywords.

Example: Euclidean Distance

  1. Input: 4 doubles (x1, y1, x2, y2)
  2. Output: Euclidead distance between point (x1,y1) and point (x2,y2).

Procedural solution

File Name: euclidean_v1.cpp

Testing:

./distance1
Enter the two points coordinates as following: x1 y1 x2 y2 [ENTER]

Now the application waits us to enter the values of the coordinates. Let’s try the following:

1.5 1 4.5 5
5

OOP solution

File Name: euclidean_v2.cpp

Testing:

./distance2
Enter the two points coordinates as following: x1 y1 x2 y2 [ENTER]

Now the application waits us to enter the values of the coordinates. Let’s try the following:

1.5 1 4.5 5
5

OOP+Template solution

File Name: euclidean_v3.cpp

This solution is useful in case we need to use Point to wrap numbers of other types like int. In such case, we construct the template object Point as Point<int>. For example, in image processing we usually use integer coordinates to refer to the pixels in the images.

Testing:

./distance3
Enter the two points coordinates as following: x1 y1 x2 y2 [ENTER]

Example: DNA Analysis

Consider an application that counts the different bases in the DNA. But before diving into the DNA analysis example, let’s explore different ways again to iterate on C++ data structures.

Counting routines

We can count how many times a value occurs in a container using several ways.

A) For-loop with integer iterator

int count1( const std::string &sequence , char q )
{
    int counter = 0;
    for( int i = 0; i < sequence.size(); ++i )
        if( sequence[i] == q )
            ++counter;
    return counter;
}
  • This approach works on any container that we can access its elements using the operator[] (e.g sequence[0], sequence[1], and sequence[i], etc.). These types of containers are called Random Access Containers, because we can access any element directly using the operator[]. Example containers are std::vector, std::string, std::array, and raw arrays.
  • We passed the sequence by reference to avoid making copies (time and space $O(n)$).
  • We passed also sequence as a constant reference to avoid unintentional modification on the elements. In that case, if your function mistakenly modifies the array, the compiler will raise an error.
1
2
3
4
5
6
7
8
int count1_buggy( const std::string &sequence , char q )
{
    int counter = 0;
    for( int i = 0; i < sequence.size(); ++i )
        if( sequence[i] = q )
            ++counter;
    return counter;
}
  • Realize the common mistake in line #5. Instead of comparing using == operator, the function instead modifies all elements to equal q. Luckily, since you informed the compiler that sequence is a constant, then it will raise an error to draw your attention to that mistake.

B) For-loop with STL iterator

int count2( const std::string &sequence , char q )
{
    int counter = 0;
    for( auto it = sequence.cbegin(); it != sequence.cend(); ++it )
        if( *it == q )
            ++counter;
    return counter;
}
  • This approach works on any STL container, such as std::vector, std::string, std::array, std::list, std::set, and not raw arrays.

C) Range-based for loop

int count3( const std::string &sequence , char q )
{
    int counter = 0;
    for( auto c : sequence )
        if( c == q )
            ++counter;
    return counter;
}
  • This approach works on any STL container, such as std::vector, std::string, std::array, std::list, std::set, and not raw arrays.
  • count3 is more elegant that count2, preferred when possible.

D) Range-based count

If your purpose is to count a particular value in a container. You can just simply use the function std::count (available via #include <algorithm>).

int count4( const std::string &sequence , char q )
{
    return std::count( sequence.begin(), sequence.end(), q );
}
  • This approach works on any STL container, such as std::vector, std::string, std::array, std::list, std::set, and not raw arrays.
  • count4 is more elegant that count3, preferred if your purpose is counting.

DNA Analysis: Procedural solution

Testing:

cat ../covid19.fasta | ./dna1

DNA Analysis: OOP solution

  • In line #9, we declared and defined a constructor that constructs DNA object given an input string. Constructors are special functions in the class that enable us to make necessary initializations.
  • In line #14, we declared the count method as constant method by adding const at the end of the line. This avoids modifying the contents of sequence by mistake.
  • In line #20, we declared the fromStream method as static method. Which means it is not dependent on the data members. The reason we moved the fromStream from a free function into the class as static method is to add more organization. Now the DNA acts as a namespace for the fromStream function, hence we can only call this function from outside as DNA::fromStream.
  • In line #31-#32, we declared the std::string sequence under the private access. This means that sequence cannot be seen or accessed directly outside the DNA; only the DNA methods can access it.

Testing:

cat ../covid19.fasta | ./dna2

Summary

What you have learned from previous examples:

  1. Use of struct and class.
  2. Use of templates.
  3. More loop routines:
    1. Using STL iterators,
    2. Range-based for loops.
  4. std::count for value counting.
  5. Const-correctness, as applied to:
    • references (e.g const std::string &)
    • methods (e.g int count( char q ) const)
  6. Static methods. Which are functions we decid to put inside the class because they are related to the class. Static methods are not bound to objects, which means:
    • They are like regular free functions.
    • No this pointer is accessible inside a static method.
    • The class name acts as a namespace for static methods. For example, calling a static method fromStream inside DNA class can be done via DNA::fromStream.
  7. private and public access modifier. This is very important feature to employ. It helps to avoid abusing the objects from outside the class functions.