Advanced Introduction to C++, Scientific Computing and Machine Learning




Claudius Gros, SS 2024

Institut für theoretische Physik
Goethe-University Frankfurt a.M.

C++ : Object-Oriented Programming (OOP)


object-oriented programming

principles

collections of data

Copy Copy to clipboad
Downlaod Download
#include <iostream>    /* standard IO */
#include <cstring>     /* equvalent to string.h */
  
using namespace std;
 
struct A_Book              // my personal naming convention
{                          // A_Something  is alway a object
 char title[50];           
 char author[50];
 char subject[100];
 int  book_id;             // example of member variables
 void something(){}        // unused member function
};
 
struct humans              // another structure
{
 int age;
 int weight;
} Jim,Ane;                 // immediate declaration possible, but bad programming
 
 
int main( )
{
 A_Book firstBook;   // declare firstBook of type Book
 A_Book secondBook;  // declare secondBook of type Book
 
// book 1 specification
 strcpy( firstBook.title, "Learn C++ Programming");
 strcpy( firstBook.author, "Chand Miyan"); 
 strcpy( firstBook.subject, "C++ Programming");
 firstBook.book_id = 6495407;
 
// book 2 specification
 strcpy( secondBook.title, "Telecom Billing");
 strcpy( secondBook.author, "Yakit Singha");
 strcpy( secondBook.subject, "Telecom");
 secondBook.book_id = 6495700;
 
// print firstBook info
 cout << "book 1 title   : " << firstBook.title   << endl;
 cout << "book 1 author  : " << firstBook.author  << endl;
 cout << "book 1 subject : " << firstBook.subject << endl;
 cout << "book 1 id      : " << firstBook.book_id << endl;
 cout << endl;
 
// print secondBook info
 cout << "book 2 title   : " << secondBook.title   << endl;
 cout << "book 2 author  : " << secondBook.author  << endl;
 cout << "book 2 subject : " << secondBook.subject << endl;
 cout << "book 2 id      : " << secondBook.book_id << endl;
 
 return 1;
}

objects & pointers

expressioncan be read as
*xpointed to by x
&xaddress of x
x.ymember y of object x
x->ymember y of object pointed to by x ; equvalent to   (*x).y
x[0]first object pointed to by x
x[1]second object pointed to by x
x[n](n+1)th object pointed to by x
Copy Copy to clipboad
Downlaod Download
#include <iostream>    /* standard IO */
#include <cstring>     /* equvalent to string.h */
 
using namespace std;
 
struct A_Book              
{                         
 char  title[50];          
 char  author[50];
 char  subject[100];
 int   book_id;          
};
 
void printBookData(A_Book myBook)
{
 cout << "book title   : " << myBook.title   << endl;
 cout << "book author  : " << myBook.author  << endl;
 cout << "book subject : " << myBook.subject << endl;
 cout << "book id      : " << myBook.book_id << endl;
}
 
int main( )
{
 A_Book firstBook;                           // declare first
 A_Book secondBook;                          // and second Book
 A_Book* pointerToSecondBook = &secondBook;  // and a pointer
 
// book 1 specification
 strcpy( firstBook.title, "Learn C++ Programming");
 strcpy( firstBook.author, "Chand Miyan"); 
 strcpy( firstBook.subject, "C++ Programming");
 firstBook.book_id = 6495407;
 
// book 2 specification
 strcpy( secondBook.title, "Telecom Billing");
 strcpy( (*pointerToSecondBook).author, "Yakit Singha");
 strcpy( pointerToSecondBook->subject, "Telecom");
 secondBook.book_id = 6495700;
 
// print book info
 printBookData(firstBook);
 cout << endl;
 printBookData(secondBook);
 cout << endl;
 printBookData(*pointerToSecondBook);
 
 return 1;
}

C++ classes

Copy Copy to clipboad
Downlaod Download
#include <iostream>          /* standard IO */
#include <cstring>           /* equvalent to string.h */
 
using namespace std;

class A_Book                                 // the class definition
{                         
public:                                      // what comes now is public
 string title;           
 string author;
 string subject;
 
 void setID(int book_id)                     // private member variables need setter
  {
  this->book_id = book_id;                   // hey, I had two book_id !
  }
 
 int getID()                                 // and getters
  {
  return book_id;                            // no 'this', as their is no disambiguity
  }
 
 void printSubjectId()                       // of 'this' book
 {
  cout << "subject : " << subject << endl;
  cout << "Id      : " << book_id << endl;
 }
 
 static void printTitleAuthor(A_Book myBook) // of any book
 {
  cout << "title   : " << myBook.title   << endl;
  cout << "author  : " << myBook.author  << endl;
 }
 
private:                                     // what follows is privat
 int book_id;      
};                                           // end of class A_Book
 
int main( )
{
 A_Book firstBook;                           // declare first
 A_Book secondBook;                          // and second Book

// book 1 specification
 firstBook.title   = "Learn C++ Programming";
 firstBook.author  = "Chand Miyan"; 
 firstBook.subject = "C++ Programming";
 firstBook.setID(6495407);                   // only indirect access to private variables
 
// book 2 specification
 secondBook.title   = "Telecom Billing";
 secondBook.author  = "Yakit Singha";
 secondBook.subject = "Telecom";
 secondBook.setID(6495700);
 
// print book info
 A_Book::printTitleAuthor(firstBook);        // calling a static function
 firstBook.printSubjectId();                 // calling a normal functions
 cout << endl;
 A_Book::printTitleAuthor(secondBook);
 secondBook.printSubjectId();                      
 
 return 1;
}

class constructors

Copy Copy to clipboad
Downlaod Download
#include <iostream>
 
using namespace std;
 
class A_Line
{
 public:
   void setLength(double); // only forward declaration
   double getLength()      // full definition
    {
     return length;
    }
   A_Line()                // a constructor
    {
     cout << "an object of type A_Line is being created" << endl;
    }
   A_Line(int length)      // another constructor
    {
     cout << "an object of type A_Line is being created and initialized" << endl;
     this->length = length;
    }
 
 private:
   double length = 11.11;  // default initialization
};
 
void A_Line::setLength(double length)  // member functions can be defined anywhere
{
 this->length = length;
}
 
int main( )
{
  A_Line firstLine;          // calling the constructor without arguments
  A_Line secondLine(12.0);   // calling the constructor(double)
//
//
  cout << endl;
  cout << "length of the first  line : " <<  firstLine.getLength() << endl;
  firstLine.setLength(6.0); 
  cout << "length of the first  line : " <<  firstLine.getLength() << endl;
  cout << "length of the second line : " << secondLine.getLength() << endl;
  return 0;
}

constructor zoo

Copy Copy to clipboad
Downlaod Download
#include <iostream>
#include <stdio.h>       // for printf

using namespace std;
 
class Circle 
{
 double radius = 1.0;                      // everything which is not public is privat
 public:
  static double referenceRadius;           // see below
  double area() {return radius*radius*3.14159265;}
  Circle(double r) : radius(r) { }         // constructor definition.
                                           // ": radius(r)" is the initializer list
                                           // ": radius(r) {}" is the function body
  Circle(int rInt) {radius = 1.0*rInt;}    // better way to initilize  variables
  Circle() { }                             // default (necessary if others are defined)
  Circle(Circle &existingCircle)           // copy constructor
    {
    radius = existingCircle.radius/10;     // normally 1-1 copying
    }
};

double Circle::referenceRadius = 11.11;    // static variables cannot be intitialized
                                           // when a class is instantiated, they belong
                                           // to the namespace of the class, which is abstract
 
int main () 
{
 Circle firstCircle(10.0);                 // double argument
 Circle secondCircle(100);                 // int    argument
 Circle thirdCircle;                       // with default contructor
 Circle forthCircle(thirdCircle);          // with copy contructor
//
 printf("area of the first  circle : %12.6f\n",  firstCircle.area() );
 printf("area of the second circle : %12.6f\n", secondCircle.area() );
 printf("area of the third  circle : %12.6f\n",  thirdCircle.area() );
 printf("area of the forth  circle : %12.6f\n",  forthCircle.area() );
 printf("reference radius          : %12.6f\n", Circle::referenceRadius);
 return 1;
}

class destructor

Copy Copy to clipboad
Downlaod Download
#include <iostream>     // std::cout
#include <stdio.h>      // for printf
#include <stdlib.h>     // srand, rand 
 
using namespace std;
 
struct MyClass
{
 MyClass()          // overwriting default constructor
 {
 myId = rand();
 printf("# MyClass %10d constructed\n",myId);
 }
//
 ~MyClass()         // overwriting default destructor
 {
 printf("# MyClass %10d destroyed\n",myId);
 }
//
private: 
 int myId;
};
 
 
int main () 
{
 MyClass *pt2 = new MyClass[2];
 printf("after the construction of 2 instances; deleting now\n");
 delete[] pt2;                    // delete all instances
//
 {
 printf("\n");
 printf("creating a class in scope -anInstance- \n");
 MyClass anInstance = MyClass();  // class allocated on stack
 MyClass anArray[3];              // idem
 }
 printf("scope -anInstance- left\n");
//
 {
 printf("\n");
 printf("creating a pointer to a class in scope -pt0-\n");
 MyClass *pt0 = new MyClass;      // class allocated on heap
 MyClass *pt1 = new MyClass[2];   // pointer on stack
 pt2 = pt0;                       // what happens for  pt2=pt1  ?
 }
 printf("scope -pt0- left:: memory leak!\n");
 delete pt2;
 printf("pt2 delted\n");
//
 printf("\n");
 MyClass *pt3 = new MyClass[3];
 printf("after the construction of 3 instances; deleting now\n");
// delete pt3;     // error:  *pt3 is an array
 delete[] pt3;     // correct
//
 return 1;
}

constructors and new

Copy Copy to clipboad
Downlaod Download
#include <iostream>  // std IO
#include <stdio.h>   // for printf
#include <cstdlib>   // for atof
#define VARIABLE_NAME(x) #x  // a C++ macro definition
 
using namespace std;
 
// --- ----------------
// --- class definition
// --- ----------------
 
class A_Class
{
private:
 int data;                 
public:
 int getData() { return data; }                  // getter
 void setData(int data) { (*this).data = data; } // setter
 A_Class()                  // default constructor
   { }
 A_Class(int data)          // another constructor
   { (*this).data = data; }
};
 
// --- ----
// --- main
// --- ----
 
int main(int argLength, char* argValues[])  
{                          
 if (argLength==1)
   {
   printf("please run with an integer argument\n");
   return 0;
   }
 int arrayLength = atof(argValues[1]);          // casting string to int
//
//
 A_Class *oldArray = new A_Class[arrayLength];  // with default constructor
 for (int i=0; i<arrayLength; i++)
    oldArray[i].setData(i*2);
//
 for (int i=0; i<arrayLength; i++)
   printf("%s[%d].getData() : %d\n",VARIABLE_NAME(oldArray),i,
                                    oldArray[i].getData()); // note the . operator
//
//
 printf("\n");
 A_Class ** newArray = new A_Class*[arrayLength]; // array of pointers to A_Class
 for (int i=0; i<arrayLength; i++)
    newArray[i] = new A_Class(i*3);               // individual instantiation on heap
//
 for (int i=0; i<arrayLength; i++)
   printf("%s[%d].getData() : %d\n",VARIABLE_NAME(newArray),i,
                                    newArray[i]->getData()); // note the -> operator
//
 return 1;
}

constant member functions

Copy Copy to clipboad
Downlaod Download
#include <iostream>
#include <stdio.h>  
 
using namespace std;
 
class A_Class
{
 int data;                  // private per defintion
public:
 int getData() const        // may not change member variables
   { return data; }
 void setData(int data)     // cannot be 'const'
   { (*this).data = data; }
 A_Class()                  // default constructor
   { }
 A_Class(int data)          // a constructor
   { (*this).data = data; }
};
 
int main()
{
 A_Class nonConstantInstantiation;
 const A_Class constantInstantiation(15);  // .setData() not allowed
 nonConstantInstantiation.setData(10);
//
 printf("data in nonConstantInstantiation : %d\n",nonConstantInstantiation.getData());
 printf("\n");
 printf("data in    constantInstantiation : %d\n",constantInstantiation.getData());
//
 return 1;
}

friends

Copy Copy to clipboad
Downlaod Download
#include <iostream>      // standard IO
 
using namespace std;
 
class A_Box
{
 double width;                     // private
public:
 void setWidth(double);            // a member functions
 friend void printWidth(A_Box);    // not (!) a member function, but a friend
 friend class ClassTwo;            // declaring all member functions of ClassTwo as friends
 friend int main();                // friends ad absurdum
};
 
 inline int plus4(int a)
  {
  return a += 4;
  }
void A_Box::setWidth(double width) // member function definition
{
 (*this).width = width;
}
 
void printWidth(A_Box inBox)       // friend definition
{
 cout << "in " << __FUNCTION__ << ": width of box : "
      << inBox.width               // direct access to private data!
      << endl; 
}
 
// Main function for the program
int main( )
{
 A_Box box;
 box.setWidth(11.1);    // calling member function
 printWidth(box);       // direct access to printWidth()
//
 box.width = 22.2;      // direct access to private data to all friends
 cout << "in main      : width of box : " 
      << box.width             
      <<endl; 
 return 1;
}

derived classes and inheritance



class base 
{
        public:
                int x;
        protected:
                int y;
        private:
                int z;
};

class publicDerived: public base
{
        // x is public
        // y is protected
        // z is not accessible from publicDerived
};

class protectedDerived: protected base
{
        // x is protected
        // y is protected
        // z is not accessible from protectedDerived
};

class privateDerived: private base
{
        // x is private
        // y is private
        // z is not accessible from privateDerived
};
Copy Copy to clipboad
Downlaod Download
#include <iostream>      // standard IO
#include <stdio.h>       // for printf
#include <math.h>        // math
#include <assert.h>      // (`assert()' calls abort if arg not true)
#include <cstdlib>       // for atof
 
using namespace std;
 
// *** *************
// *** vehicle class
// *** *************
 
class vehicle {
 int childTakeAwayYourFinger;                // only for vehicle class (is privat)
protected:                                   // also for child classes
 int wheels;
 double weight;
public:
 void initialize(int in_wheels, double in_weight);
 int get_wheels(void) {return wheels;}
 double get_weight(void) {return weight;}
 double wheel_loading(void) {return weight/wheels;}
};
 
// *** *********
// *** car class
// *** *********
 
class car : public vehicle {        // public inheritance
 int passenger_load;
public:
 void initialize(int in_wheels, double in_weight, int people = 4);
 int passengers(void) {return passenger_load;}
};
 
// *** ***********
// *** truck class
// *** ***********
 
class truck : public vehicle {      // public inheritance
 int passenger_load;
 double payload;
public:
 void init_truck(int how_many = 2, double max_load = 24000.0);
 double efficiency(void);
 int passengers(void) {return passenger_load;}
};
 
// *** ****
// *** main
// *** ****
 
int main()
{
 cout << endl;
 
 vehicle unicycle;
 unicycle.initialize(1, 12.5);
 cout << "The unicycle has                " << unicycle.get_wheels() 
      << " wheel.\n";
 cout << "The unicycle's wheel loading is " << unicycle.wheel_loading() 
      << " pounds on the single tire.\n";
 cout << "The unicycle weighs             " << unicycle.get_weight() 
      << " pounds.\n\n";
 
 car sedan;
 sedan.initialize(4, 3500.0, 5);
 cout << "The sedan carries            " << sedan.passengers() 
      <<  " passengers.\n";
 cout << "The sedan weighs             " << sedan.get_weight() 
      <<  " pounds.\n";
 cout << "The sedan's wheel loading is " << sedan.wheel_loading() 
      << " pounds per tire.\n\n";
 
 truck semi;
 semi.initialize(18, 12500.0);
 semi.init_truck(1, 33675.0);
 cout << "The semi weighs          " << semi.get_weight() 
      << " pounds.\n";
 cout << "The semi's efficiency is " << 100.0*semi.efficiency() 
      << " percent.\n";
 
 return 1;
}  // end of main()
 
// *** ********************************
// *** class member function defintions
// *** ********************************
 
void vehicle::initialize(int in_wheels, double in_weight)
{
 wheels = in_wheels;
 weight = in_weight;
 cout << "in vehicle::initialize \n";
}
 
void car::initialize(int in_wheels, double in_weight, int people)
{
 passenger_load = people;
 wheels = in_wheels;
 weight = in_weight;
 cout << "in     car::initialize \n";
}
 
void truck::init_truck(int how_many, double max_load)
{
 passenger_load = how_many;
 payload = max_load;
}
 
double truck::efficiency(void)
{
// childTakeAwayYourFinger = 1;              // error: protected
 return payload / (payload + weight);
}

derived classes, pointers & polymorphism

Copy Copy to clipboad
Downlaod Download
#include <iostream>
#include <stdio.h>
using namespace std;
 
class baseClass 
{
public:
 int myData;
 void setData(int inData)   // this instance is called when overwritten
   { myData = inData; printf("in: baseClass.setData()\n");}
 virtual int getData()      // overwritten instance called when accessed
   { return 0;}
};
 
class firstDerivedClass: public baseClass 
{
public:
 int firstData;
};
 
class secondDerivedClass: public baseClass 
{
public:
 void setData(int inData)   // overloading a non virtual function
   { myData = 11; printf("in: secondDerivedClass.setData()\n");}
 int getData()              // overloading a virtual function
   { return myData;}
};
 
int main () 
{
 baseClass            baseInstantiation;
 firstDerivedClass   firstInstantiation;
 secondDerivedClass secondInstantiation;
//
 baseClass * basePointer_1 = &baseInstantiation;
 baseClass * basePointer_2 = &firstInstantiation;   // pointer type!
 baseClass * basePointer_3 = &secondInstantiation;  // pointer type!
//
 printf("\n");
 printf("using .setData(7) for everybody\n");
 printf("\n");
 baseInstantiation.setData(7);
 firstInstantiation.setData(7);
 secondInstantiation.setData(7);
 printf("\n");
 printf("  baseInstantiation.myData : %d\n",  baseInstantiation.myData);
 printf(" firstInstantiation.myData : %d\n", firstInstantiation.myData);
 printf("secondInstantiation.myData : %d\n",secondInstantiation.myData);
//
 printf("\n");
 printf("  baseInstantiation.getData() : %d\n",  baseInstantiation.getData());
 printf(" firstInstantiation.getData() : %d\n", firstInstantiation.getData());
 printf("secondInstantiation.getData() : %d\n",secondInstantiation.getData());
//
//
 printf("\n\n");
 printf("using basePointer_x->setData(9) for everybody\n");
 printf("\n");
 basePointer_1->setData(9);
 basePointer_2->setData(9);
 basePointer_3->setData(9);
 printf("\n");
 printf("  baseInstantiation.myData : %d\n",  baseInstantiation.myData);
 printf(" firstInstantiation.myData : %d\n", firstInstantiation.myData);
 printf("secondInstantiation.myData : %d\n",secondInstantiation.myData);
//
 printf("\n");
 printf("basePointer_1->getData() : %d\n",basePointer_1->getData());
 printf("basePointer_2->getData() : %d\n",basePointer_2->getData());
 printf("basePointer_3->getData() : %d\n",basePointer_3->getData());
//
// --- mixing base and derived classes in arrays
//
  baseClass ** arrayPointersBase = new baseClass*[3];
  arrayPointersBase[0] = new baseClass();            // base class instantiation
  arrayPointersBase[1] = new firstDerivedClass();    // derived class
  arrayPointersBase[2] = new secondDerivedClass();   // instantiation
//
 return 1;
}

class templates and operator overloading

Copy Copy to clipboad
Downlaod Download
#include <iostream>
#include <stdio.h>  
#include <math.h>
 
using namespace std;
 
// -- ------
// -- vector
// -- ------
 
template <typename T,int dim>          // T can be: int, double, ...
struct A_Vector                        // dim: dimension 
{
 T elements[dim];                      // the elements of my vector
//
 int size()                            // return size of vector
   {return dim;}
//
 double norm()                         // just a double-valued norm
  {
  T result = (T)0.0;                   // cast double-0.0 to T
  for (int i=0;i<dim;i++)
     result += elements[i]*elements[i];
  return sqrt((double)result);         // cast T to double
  }
 
 A_Vector<T,dim> operator+(const A_Vector &b)  // overloading the "+" sign
      {
      A_Vector<T,dim> sumVector; // the result is a vector too
      for (int i=0;i<dim;i++)
        sumVector.elements[i] = this->elements[i] + b.elements[i];
      return sumVector;
      }
 
};   // end of struct A_Vector 
 
// -- ----
// -- main
// -- ----
 
// Main function for the program
int main( )
{
 const int mainDim = 4;
 A_Vector<int,mainDim> firstVector;
 A_Vector<int,mainDim> secondVector;
 A_Vector<int,mainDim> thirdVector;
//
 for (int i=0;i<mainDim;i++)
   {
   firstVector.elements[i] = 1.0;
   secondVector.elements[i] = 2.0;
   }
//
  thirdVector = firstVector + secondVector;   // just like that!
/* 
  thirdVector = firstVector.operator+(secondVector);  
 * same result by calling the respective member function
 */
//
 printf("\n");
 printf("the vector [%d",firstVector.elements[0]);
 for (int i=1;i<mainDim;i++)
   printf(",%d",firstVector.elements[i]);
 printf("] has the norm: %f\n",firstVector.norm());
//
 printf("\n");
 printf("the vector [%d",secondVector.elements[0]);
 for (int i=1;i<mainDim;i++)
   printf(",%d",secondVector.elements[i]);
 printf("] has the norm: %f\n",secondVector.norm());
//
 printf("\n");
 printf("the vector [%d",thirdVector.elements[0]);
 for (int i=1;i<mainDim;i++)
   printf(",%d",thirdVector.elements[i]);
 printf("] has the norm: %f\n",thirdVector.norm());
//
 
 return 1;
}

example: binary trees

$\displaystyle\qquad\quad \mathrm{number\ nodes} = \sum_{d=0}^{\rm depth-1} 2^d =\frac{1-2^{\rm depth}}{1-2} $


Copy Copy to clipboad
Downlaod Download
/** sample program for a fixed-depth binary tree
  */
#include <iostream>
#include <math.h>
using namespace std;

/**
  * class definition for a single node
  */
struct Node 
{
 Node* left;              // left child (pointer to)
 Node* right;             // right child
 int   myDepth;           // layer/depth
 int   myData;            // any data
//
 static int nInstances;   // # of class instances created
//
 Node()                   // overwriting default constructor
   {
   myData  = 0.0;
   left    = NULL;        // set them later
   right   = NULL;
   myDepth = -1;          // needs to be set 
   nInstances++;          // one more instance
   }
};
int Node::nInstances = 0; // initialization of class variables
                          // is done using the namespace operator
/**
  * generating the entire binary tree
  */
void generateTree(int depth, int nNodes, Node myTree[])
{
 int iNode = 0;                        // node counter
 int lastLayerStart = 0;               // starting of lastLayer
 myTree[0].myDepth  = 0;               // define root
//
 for (int iLayer=0; iLayer<(depth-1); iLayer++)
   {
   int lastLayerEnd = iNode + 1;
   printf("# %5d | %5d %5d\n", iLayer, lastLayerStart, lastLayerEnd);
   for (int iLast = lastLayerStart;       
            iLast < lastLayerEnd; iLast++)  // loop over last layer nodes
     {
     iNode++;                      
     myTree[iLast].left  = myTree + iNode;  // pointer to new left child
     iNode++;                     
     myTree[iLast].right = myTree + iNode;  // using pointer arithmetics
     myTree[iLast].left->myDepth  = iLayer + 1;   // set layer(depth)
     myTree[iLast].right->myDepth = iLayer + 1;   // of new modes
     }
   lastLayerStart = lastLayerEnd;
   } // end of loop over layers
// 
// output
//
 printf("# in generateTree() - number of instances generated\n");
 printf("# %5d %5d\n", Node::nInstances, nNodes);
// 
// testing
/*
 for (int iNodes=0; iNodes<nNodes; iNodes++)
   printf("%5d", myTree[iNodes].myDepth);
 printf("\n");
*/
}

/**
  * print the tree
  */
void printTree(int depth, int nNodes, Node myTree[])
{
 myTree[0].myDepth  = 0;               // define root
 printf("\n");
 printf("# the binary tree \n");
 printf("# layer depths \n");
 for (int iLayer=0; iLayer<depth; iLayer++)
   {
   printf("# %5d | ", iLayer);
   for (int iNodes=0; iNodes<nNodes; iNodes++)
     if (myTree[iNodes].myDepth==iLayer)
       printf("%5d", iNodes);
   printf("\n");
   }
//
}

/**
  *  entry point
  */
int main()
{
/** using lambda expression for total number of nodes */
 const int depth = 3;          
 const int nNodes = [depth](){int number = 0;
                              for (int i=0; i<depth; i++)
                                 number += pow(2,i);
                              return number;
                              }();           // executed once
//
 printf("#  depth, nNodes : %8d %8d\n", depth, nNodes);
//
 auto allNodes = new Node[nNodes];       // with default constructor
 generateTree(depth, nNodes, allNodes);  // generate links (pointers)
 printTree(depth, nNodes, allNodes);     // print tree
//
// set data (example)
//
 for (int iNodes=0; iNodes<nNodes; iNodes++)
   allNodes[iNodes].myData = iNodes;
//
// transversing the tree (example)
//
 Node* currentNode = allNodes;    // start with root
 for (int iDepth = 0; iDepth < depth; iDepth++)
   {
   printf("%5d | %5d\n", iDepth, currentNode->myData);
   currentNode = (iDepth%2==0) ? currentNode->left
                               : currentNode->right;
   }
 return 1;
}