C++ in a Nutshell: all you need to know to use ROOT

Welcome to the Monday afternoon hands-on session. We will focus today on introductory exercises about the C++ language. After the theoretical introduction you followed, you should be able to solve the exercises. Nevertheless two levels of help will be provided: a hint and a full solution. Try not to jump to the solution even if you experience some frustration. The help is organised as follow:

Here the hint is shown.

Here the solution is shown.

Some points linked to the exercises are optional and marked with a Pointing hand icon: they are useful to scrutinise inmore detail some aspects of C++. Try to solve them if you have the time.

C++ is a compiled language and in order to obtain executables the procedure would imply the usage of a compiler. We will take advantage of a powerful feature of ROOT, which allows to compile C++ code without bothering about all the complications linked to the direct usage of the compiler. Here follows a reminder about the way in which you can compile macros from within the cint interpreter.

Compiling macros with ROOT

Suppose you would like to compile this trivial macro:
void test(){ int a = 42;}
you would have to execute this command:
root [0] .L myMacro.cxx+
don't worry about the warning for now. To execute your code you then should type:
root [1] test()

The macros of the exercises together with the solutions can be found here: cppTutoriaROOT.tar.gz:

Exercise 1: Syntax error

The compilation of the C++ code is a way in which quite an amount of mistakes can be intercepted. Among the most common we can find the so-called "syntax error", i.e. an error in the way in which the language is used. Take for example the macro syntaxError.cxx (reported here) and compile it.

Can you inspect the output to spot the mistake and correct it?

What is the compiler suggesting about line 9?

Just add a ; after the float result = n1+n2 line.

Exercise 2: Hello world!

Now that you know how to interpret the suggestions that the compiler can give you, you could try to write down a simple program that prints on screen out the string "Hello World". Try to store the string in a variable and then print it.

The building blocks you would need are:
  • cout: the name of the stream linked to the display of the machine. The operator to "feed" cout is the <<.
  • The carriage return can be part of the string coded as a \n or expressed as endl.
  • The C++ type for strings is the string while you can express string constants enclosed between double quotes (e.g. "My String").

#include <iostream>

using namespace std;

void helloWorld(){
  string helloWorld ("This is an Hello World program!");
  cout << helloWorld << endl;
  
}

Pointing hand Can you try out the operator + with different strings? What would you expect from it? What happens using operator *?

Exercise 3: while loops and conditional statements

In this exercise we will put in place a while loop and use conditional statements. The goal is simple: sum up all numbers which are an exact multiple of three until 1000 is reached. How many of them are needed? Which ones?

These are some suggestions:
  • This is a use-case for an uncountable loop steered by the while control structure

#include <iostream>
using namespace std;
void sumMultiples() {
  unsigned int index=0;
  unsigned int addedNumberIndex=0;
  unsigned int sum = 0;
  while (sum<=1000){
    if (index%3 == 0){
      addedNumberIndex++;
      std::cout << addedNumberIndex << ") "<< index << " being added to the sum.\n";
      sum+=index;
    }
    index++;
  }
}

Pointing hand In this particular case, it is possible to use a for instead of a while. How? How do you judge the clarity of the two solutions?

Exercise 4: Mean and RMS calculation

This exercise is dedicated to the codification of an algorithm to calculate mean and RMS starting from a set of numbers. The macro has been started for you in the file forLoop.cpp. The idea is that you complete the code with what is missing to calculate and print the mean and RMS of the numbers collected in the vector.

The building blocks you would need are:
  • One way in which you can loop over a container like the vector is to use a running integer index in a for loop
  • You can access the elements of the vector using the operator []

#include <iostream>
#include <vector>

using namespace std;

void calculateMomenta () {
  
  unsigned int N=8;
  
  vector<float> myvector;
  myvector.reserve(N);
  
  // fill the vector
  myvector.push_back(1.2f);
  myvector.push_back(11.2f);
  myvector.push_back(1.32f);
  myvector.push_back(0.2f);
  myvector.push_back(1.906f);
  myvector.push_back(3.f);
  myvector.push_back(32.f);
  myvector.push_back(13.f);
  
  
  float sum=0;
  for (unsigned int index=0;index<N;++index){
    sum+=myvector[index];
  }
  
  float mean = sum/N;

  cout << "The mean is: " << mean << endl;  
  
  float RMS2=0;
  for (unsigned int index=0;index<N;++index){
    float element=myvector[index];
    float residual = element - mean;
    RMS2+=residual*residual;
  }
  
  RMS2/=(N-1);
  float RMS=sqrt(RMS2);
  
  cout << "The RMS is: " << RMS << endl;  
}

Pointing hand Can you calculate the mean and the RMS in one single loop?

Exercise 5: Functions

One powerful feature of the C++ language is the possibility to use functions. Actually, we used functions in each exercise up to now and called them from within the interpreter. This exercise will focus on the writing of a function that calculates the mean and the rms of a given vector in order for the caller to print these two values on screen. You can start from the code you developed for exercise 4.

The building blocks you would need are:
  • By default, C++ passes arguments to functions by value. It means that the parameter is "cloned" within the scope of the function starting from the one passed by the caller. It is therefore pointless to operate changes on "clones" as they will disappear as soon as the function terminates its execution.
  • A possible treatment of the parameters given the aforementioned behaviour is to pass values by reference (the signature of the function should be type functionName(type& parameterName))

#include <iostream>
#include <vector>

using namespace std;

void calculateMomenta (vector<float>& myvector, float& mean, float& rms) {

  const unsigned int N = myvector.size();

  float sum=0;
  for (unsigned int index=0;index<N;++index){
    sum+=myvector[index];
  }
  mean = sum/N;

  float rms2=0;
  for (unsigned int index=0;index<N;++index){
    float element=myvector[index];
    float residual = element - mean;
    rms2+=residual*residual;
  }

  rms2/=(N-1);
  rms=sqrt(rms2);

}

//---------------------

void  functions(){

  unsigned int N=8;

  float thisMean, thisRMS;

  vector<float> myvector;
  myvector.reserve(N);

  calculateMomenta(myvector, thisMean, thisRMS);
  std::cout << "Mean: " << thisMean << " --  RMS: " << thisRMS << endl;

  // fill the vector
  myvector.push_back(1.2f);
  myvector.push_back(11.2f);
  myvector.push_back(1.32f);
  myvector.push_back(0.2f);

  calculateMomenta(myvector, thisMean, thisRMS);
  std::cout << "Mean: " << thisMean << " --  RMS: " << thisRMS << endl;

  myvector.push_back(1.906f);
  myvector.push_back(3.f);
  myvector.push_back(32.f);
  myvector.push_back(13.f);

  calculateMomenta(myvector, thisMean, thisRMS);
  std::cout << "Mean: " << thisMean << " --  RMS: " << thisRMS << endl;
}

Pointing hand Could you list the principal advantage in the usage of functions?

Pointing hand A nan value is printed for the mean in the first case. The acronym nan stands for "Not A Number". Can you explain what is happening?

Exercise 6: Putting all together with classes

Imagine now an entity that describes the ensamble of the vector of values and the functions for calculating their mean and RMS: that would be a class. The entity described by this class would be something concretely holding the data and would be allowed to be present in multiple instances: these would be objects. You can find the class in the measurementsClass.cxx file. The goal of this exercise is to create an object of the type Measurements, fill it with the values written below and print out on screen the mean and rms of these values using the methods of the class. This last exercise acts as a final preparation step towards the usage of the ROOT classes. Values with which the Measurements object should be filled:
  • -7.31
  • 6.95
  • 5.28
  • -4.90
  • -0.09
  • -1.01
  • 3.03
  • 5.77
  • -8.12
  • -9.43

These are some suggestions:
  • The method to add a value to the object is addValue while in order to get mean and rms, they are getMean and getRMS respectively

for (unsigned int index=0;index<N;++index){
        sum+=m_values[index];
    }
    return sum/N;
  };

  float getRms(){
    const unsigned int N = m_values.size();
    float rms2=0;
    for (unsigned int index=0;index<N;++index){
        float element=m_values[index];
        float residual = element - getMean();
        rms2+=residual*residual;
    }
    return sqrt(rms2/(N-1));
  };

private:
  std::vector<float> m_values;

};

int main(){

  Measurements myMeasurements;

  myMeasurements.addValue(-7.31);
  myMeasurements.addValue(6.95);
  myMeasurements.addValue(5.28);
  myMeasurements.addValue(-4.90);
  myMeasurements.addValue(-0.09);
  myMeasurements.addValue(-1.01);
  myMeasurements.addValue(3.03);
  myMeasurements.addValue(5.77);
  myMeasurements.addValue(-8.12);
  myMeasurements.addValue(-9.43);

  std::cout << "Mean: " << myMeasurements.getMean() << ", RMS: " << myMeasurements.getRms() << std::endl;
  return 0;
}

Pointing hand The evaluation of mean and rms is less than optimal. For every call to them, calculations are started even if no new value has been added. Can you imagine a caching mechanism? A good way to start could be to add two private float members m_mean_cache and m_rms_cache together with a boolean flag, always private, which is used to track if a new value has been added to the collection since the last calculation of mean and rms took place. Then one should implement a simple if condition to select between a new calculation of mean and rms or to return the cached values.

Note: for performance reasons beyond the scope of these exercises, the calls to the push_back method of the standard vector should be always preceded by a call to the reserve() method. In this context, we will not go into this level of detail.

Topic attachments
I Attachment History Action Size Date Who Comment
Unknown file formatgz CppInANutshell.tar.gz r1 manage 1.5 K 2013-02-17 - 15:33 DaniloPiparo The source code for the exercises
Edit | Attach | Watch | Print version | History: r2 < r1 | Backlinks | Raw View | WYSIWYG | More topic actions
Topic revision: r2 - 2013-02-21 - LorenzoMoneta
 
    • Cern Search Icon Cern Search
    • TWiki Search Icon TWiki Search
    • Google Search Icon Google Search

    Main All webs login

This site is powered by the TWiki collaboration platform Powered by PerlCopyright &© 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
or Ideas, requests, problems regarding TWiki? use Discourse or Send feedback