Week 6 - Part A: Revision on C++ classes and templates
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.
Notes: struct vs. class
- In C++, the
structandclassgenerally do the same thing: they declare new types. You can have aRectangletype with two memberswidthandheightby either:
struct Rectangle
{
double width;
double height;
};
or:
class Rectangle
{
public:
double width;
double height;
};
- By default, the members declared in
classare accessible only within that class (for example, you cannot access these members directly in themainfunction), so if you need to access them from outside you just addpublic:line just before the members. In contrast,structmembers by default can directly be accessed outside the class. You will learn more interesting examples wherepublicandprivateaccess modifiers play an essential role in the design.
Example: Euclidean Distance
- Input: 4 doubles (x1, y1, x2, y2)
- 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.gsequence[0],sequence[1], andsequence[i], etc.). These types of containers are called Random Access Containers, because we can access any element directly using theoperator[]. Example containers arestd::vector,std::string,std::array, and raw arrays. - We passed the
sequenceby reference to avoid making copies (time and space $O(n)$). - We passed also
sequenceas 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 equalq. Luckily, since you informed the compiler thatsequenceis 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. count3is more elegant thatcount2, 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. count4is more elegant thatcount3, preferred if your purpose is counting.
DNA Analysis: Procedural solution
Testing:
cat ../covid19.fasta | ./dna1
Notes: Linux | to redirect the output
The command cat ../covid19.fasta | ./dna1 executes two programs. First one is cat which prints the file contents to the terminal, while the second one executes our compiled program. The linux has utilities that redirect the programs outputs/inputs. Using the pipe | means to use the output of the first program as input to the second program.
DNA Analysis: OOP solution
- In line #9, we declared and defined a constructor that constructs
DNAobject given an input string. Constructors are special functions in the class that enable us to make necessary initializations. - In line #14, we declared the
countmethod as constant method by addingconstat the end of the line. This avoids modifying the contents ofsequenceby mistake. - In line #20, we declared the
fromStreammethod as static method. Which means it is not dependent on the data members. The reason we moved thefromStreamfrom a free function into the class as static method is to add more organization. Now theDNAacts as a namespace for thefromStreamfunction, hence we can only call this function from outside asDNA::fromStream. - In line #31-#32, we declared the
std::string sequenceunder theprivateaccess. This means thatsequencecannot be seen or accessed directly outside theDNA; only theDNAmethods can access it.
Testing:
cat ../covid19.fasta | ./dna2
Summary
What you have learned from previous examples:
- Use of
structandclass. - Use of templates.
- More loop routines:
- Using STL iterators,
- Range-based for loops.
std::countfor value counting.- Const-correctness, as applied to:
- references (e.g
const std::string &) - methods (e.g
int count( char q ) const)
- references (e.g
- 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
thispointer is accessible inside a static method. - The class name acts as a
namespacefor static methods. For example, calling a static methodfromStreaminsideDNAclass can be done viaDNA::fromStream.
privateandpublicaccess modifier. This is very important feature to employ. It helps to avoid abusing the objects from outside the class functions.