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
struct
andclass
generally do the same thing: they declare new types. You can have aRectangle
type with two memberswidth
andheight
by either:
struct Rectangle
{
double width;
double height;
};
or:
class Rectangle
{
public:
double width;
double height;
};
- By default, the members declared in
class
are accessible only within that class (for example, you cannot access these members directly in themain
function), so if you need to access them from outside you just addpublic:
line just before the members. In contrast,struct
members by default can directly be accessed outside the class. You will learn more interesting examples wherepublic
andprivate
access 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
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 equalq
. Luckily, since you informed the compiler thatsequence
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 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. count4
is 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
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 addingconst
at the end of the line. This avoids modifying the contents ofsequence
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 thefromStream
from a free function into the class as static method is to add more organization. Now theDNA
acts as a namespace for thefromStream
function, hence we can only call this function from outside asDNA::fromStream
. - In line #31-#32, we declared the
std::string sequence
under theprivate
access. This means thatsequence
cannot be seen or accessed directly outside theDNA
; only theDNA
methods can access it.
Testing:
cat ../covid19.fasta | ./dna2
Summary
What you have learned from previous examples:
- Use of
struct
andclass
. - Use of templates.
- More loop routines:
- Using STL iterators,
- Range-based for loops.
std::count
for 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
this
pointer is accessible inside a static method. - The class name acts as a
namespace
for static methods. For example, calling a static methodfromStream
insideDNA
class can be done viaDNA::fromStream
.
private
andpublic
access modifier. This is very important feature to employ. It helps to avoid abusing the objects from outside the class functions.