C++ Basics for ROOT User
This is not a C++ class. It is intended to introduce basic concepts of
C++ in a way that you will be able to follow the ROOT 102 class even
if you have no prior knowledege of C or C++, but are fluent in Fortran.
Why C++ Basics
-
C++ is the implementation language of ROOT
-
C++ knowledge is a prerequisite to understand most of the ROOT documentation.
-
It is used for:
-
the Command Line (ROOT commands are interpreted by CINT, a C++ interpreter)
-
Macros (interpreted by CINT)
-
to collect commands
-
to extend ROOT with new functions
-
to extend ROOT with new classes
-
C++ Shared Libraries (compiled by a C++ compiler)
-
to extend ROOT with new functions
-
to extend ROOT with new classes
-
C++ Applications (compiled by a C++ compiler)
-
to use ROOT to built applications
ROOT as pocket calculator:
Variables, Arithmetic and Assignment Expressions, Simple Output
Example :
root [0] Int_t a;
root [1] a = 3 * 5;
root [2] cout << a << endl;
15
Explanation:
[0]
Declare a variable a of type Int_t
[1]
Compute 3*5 and set the value of variable a to the result
[2]
Print the value of variable a to the terminal. cout is the name of
the standard output stream (in our case the terminal). The operator <<
passes the value of variable a converted to a string to cout. After that
the endline character endl is passed to cout to enforce the output.
Comments:
-
C++ is a strongly typed language, i.e. variables must be declared before
usage
-
Int_t is the ROOT type for integers
-
Most C++ statements have a closing semicolon
-
C++ formatted output looks very strange in the first place, but is easy
to use (shift everything you want to output to cout).
-
A variable name consists of a sequence of letters, digits and underscores
but may not start with a digit. C++ keywords are not allowed as variable
name.
-
We use simple one-letter names to keep the examples short and readable.
In a program consisting of more than a few lines, names should be chosen
to reflect their useage. In this way they serve as documentation, making
the program more readable.
Example:
root [0] Int_t a;
root [1] a = 5.1;
root [2] cout << "a = " << a << endl;
a = 5
root [3] Double_t b;
root [4] b = 5.1;
root [5] cout << "b = " << b << endl;
b = 5.1
Explanation:
[1]
Convert the floating point value 5.1 to integer and set the value of
a to the result
[2]
Send first the string "a = ", second the value of a, third the
endline character to cout
[3]
Declare a floating point variable b;
[4]
Set b to 5.1
Comments:
-
The most important data types for computation s are integer and floating
point variables, in ROOT integers are repesented by the type Int_t, floating
point numbers by Double_t
-
CINT uses the usual rules to convert between integers and floating point
numbers
-
It is easy to send strings to the output stream
Example :
root [0] a = 4*5;
Warning: Automatic variable a allocated in global scope ...
root [1] a
(int)20
root [2] 4*5
(int)20
Explanation:
[0]
Compute 4*5 and set variable a to the result. CINT recognizes that
the variable a is not defined. It prints a warning and declares a according
to the type on the righthand side.
[1] [2]
If the semicolon after a statement is omitted, CINT prints the result
to the terminal in the form (type) result. In our case the plain C integer
type int is used instead of the ROOT type Int_t.
Comments:
-
It is easy to use CINT as pocket calculator by omitting the semicolon after
an expression
-
CINT declares variables according to the righthand side of an expression.
A compiler would flag undeclared variables as errors
Arithmetic and Assignment Operators
| C++ |
Purpose |
FORTRAN |
| x++ |
Postincrement |
|
| ++x |
Preincrement |
|
| x-- |
Postdecrement |
|
| --x |
Predecrement |
|
| +x |
Unary plus |
+X |
| -x |
Unary minus |
-X |
| x*y |
Multiply |
X*Y |
| x/y |
Divide |
X/Y |
| x%y |
Modulus |
MOD(X,Y) |
| x+y |
Add |
X+Y |
| x-y |
Subtract |
X-Y |
| pow(x,y) or TMath::Power(x,y) |
Exponation |
X**Y (FORTRAN and CINT) |
| x = y |
Assignment |
X = Y |
| x += y (-=,*=,/=,%=,...) |
Updating assignment |
X = X+Y (X=X-Y,X=X*Y,...) |
Comments:
-
With the exception of the increment/decrement and the updating assignment
operators the arithmetic is pretty much like in Fortran
-
Preincrement/decrement means 1. add/subtract one 2. use the variable
-
Postincrement/decrement means 1. use the variable 2. add/subtract one
try
int i;
i = 0;
cout << i++ << endl;
cout << i << end;
cout << ++i << endl;
-
Updating assignment i=i+x; could be written as
-
There is no exponentiation operator in C++, you have to use a function
(CINT knows the Fortran exponentiation)
Example:
root [0] Double_t x=1.3, y;
root [1] y = TMath::Erfc(x)
(Double_t)6.59920503287388940e-02
root [2] TMath::Sin(TMath::Pi())
(Double_t)1.22464679914735320e-16
Explanation:
[0]
Declare floating point variables x and y, initialize x to 1.3
[1]
Set y to the result of the function TMath::Erfc(x). There is no semicolon,
therefore print the result to the terminal
[2]
Compute the value of sin(pi), no semicolon, output to terminal
Comments:
-
It is possible to declare more than one variable in a declaration.
-
It is possible to initialize a variable in the declaration.
-
ROOT provides a rich set of mathematical functions. See class TMath
-
The notation TMath::function() will be explained later.
Program flow constructs:
Functions, Logical Values and Operators, Relational Operators, Control
statements
Example:
file mult.C
Double_t mult(Double_t a, Double_t b) {
// multiply a and b
return a*b;
}
root [0] .L mult.C
root [1] mult(3,4)
(Double_t)1.20000000000000000e+01
Explanation:
[file mult.C]
Define a function with name mult and two parameters (a and b) of type
Double_t returning a Double_t value.
Inline comments in C++ start with // and end at the end of the current
line.
The function body (here only one statement) is in curly brackets.
[0]
Load file mult.C
[1]
Execute function mult
Example:
file multimult.C
Double_t mult(Double_t a, Double_t b = 2) {
// multiply a and b
return a*b;
}
Double_t mult() {
// prompt for two numbers and return the product
Double_t a, b;
cout << "enter a number:";
// output prompt string
cin >> a;
// read input
cout << "enter a second number:"; // output
prompt
cin >> b;
// read input
return a*b;
// return a*b
}
root [0] .L multimult.C
root [1] mult(3,4)
(Double_t)1.20000000000000000e+01
root [2] mult(3)
(Double_t)6.00000000000000000e+00
root [3] mult()
enter a number:4
enter a second number:8
(Double_t)3.20000000000000000e+01
Explanation:
[file multimult]
Define two functions with name mult, one with two Double_t parameters
another without parameters. cin is the standard input stream (in our case
the terminal).
The second parameter of the the first mult has a default value.
The operator >> reads a value from the standard input to the variable
on the right.
[1]
Calls the mult function with two parameters
[2]
Calls the mult function with two parameters uses the default value
[3]
Calls the mult function without parameters
Comments:
-
Overloaded functions are an important feature of C++. It is possible to
define functions with the same name but different parameters.
-
At least one parameter must be of different type. Two functions with the
same parameter list but different return type are not possible.
Example:
file: hello.C
void hello(char *name="") {
// print hello name
cout << "hello " << name << endl;
}
root [0] .L hello.C
root [1] hello()
hello
root [2] hello("Peter")
hello Peter
Explanation:
[hello.C]
Define a function with one parameter - a string named name with a default
value - returning nothing.
The strange form char *name will be explained later.
Functions returning nothing must be declared as void functions. They
do not need a return statement.
Comments:
-
If a function does not return a value it have to be declared with return
value void.
-
If a function has no parameters it have to be declared with empty parameter
list.
Example:
root [0] TMath::Erf(.1)
(Double_t)1.12462928770574950e-01
Explanation:
[0]
Compute the value of the error function erf(x) for x=0.1
Comments:
-
ROOT has a rich set of mathematical functions, see the documentation for
class TMATH
-
These functions must be called whith the string TMath:: in front, the reason
for that will become clear later (they are static member functions).
Logical Values and Operators, Relational Operators
| C++ |
ROOT extension |
Purpose |
FORTRAN |
| false or 0 |
kFALSE |
False value |
.FALSE. |
| true or nonzero |
kTRUE |
True value |
.TRUE. |
| !x |
|
Logical negation |
.NOT.X |
| x && y |
|
Logical and |
X .AND. Y |
| x || y |
|
Logical or |
X .OR. Y |
| x < y |
|
Less than |
X. LT. Y |
| x <= y |
|
Less than or equal |
X. LE. Y |
| x > y |
|
Greater than |
X. GT. Y |
| x >= y |
|
Greater than or equal |
X .GE. Y |
| x == y |
|
Equal |
X. EQ. Y |
| x != y |
|
Not equal |
X. NE. Y |
Remarks:
-
true and false are relatively new constructs in C++.
For backward compatibility 0 means false and every nonzero
value is treated as true
-
every expression in C++ returns a value, even x = y (the value of x). This
leads to a difficult to find bug if one writes if(x=y) instead
of if(x==y)
Example:
root [0] 3 < 4
(int)1
root [1] 4 < 3
(int)0
root [2] !(3<4)
(int)0
root [3] kTRUE
(Bool_t)1
root [4] kFALSE
(Bool_t)0
root [5] true
(enum bool)1
root [6] false
(enum bool)0
Explanation:
[0]
The relation expression 3<4 returns 1, i.e true
[1]
The relation expression 4<3 returns 0, i.e. false
[2]
The negation of 3<4 is of course false
[3][4]
kTRUE and kFALSE are ROOT constants to be used for logical values
[4][6]
in most C++ implementations true and false are constants for logical
values
Comments:
-
Logical and relational operators are needed to create conditions for the
use in control statements.
-
0 means false.
-
Nonzero means true.
-
C++ evaluates conditions to 0 or 1 ( false or true ).
-
Programs are more readable if kTRUE/kFALSE (or true/false) are used instead
of 1/0.
Control statements
{ statement1 statement2 ... }
if(condition) statement
if(condition) statement1
else statement2
while(condition) statement
do statement while(condition);
for(init_expression; cont_expression; incr_expression) statement
break;
continue;
Comments:
-
Every time you need more than one statement where the syntax of C++ requires
exactly one statement you can use a compound statement, i.e. a list of
statements enclosed in curly brackets.
-
Remember that every statement has a closing semicolon.
-
Use compound statements for the bodies of control statements. The form
if(condition) statement should always be coded in one line.
-
Select a code formatting style and use it consistently (throughout your
collaboration)
-
Use a while-loop when it is possible that the body should not be executed.
-
Use a do-while loop when the body should be executed at least once.
-
Use for-loops as counting loops.
-
break ends the innermost loop
-
continue ends the current iteration of the innermost loop
Example:
root [0] Int_t x;
root [1] ifstream asciiFile("bevington_chapter9.data")
root [2] while(asciiFile>>x) cout << "x = " << x <<
endl;
x = 6
x = 1
x = 10
...
Explanation:
[1]
Open the file bevington_chapter9.data
Name its associated inputstream asciiFile.
[2]
The expression asciiFile>>x reads an integer (because thats the type
of x) from the inputstream asciiFile into x. It returns 0, i.e. false if
it finds the end of the corresponding file. As long as the condition returns
nonzero the result is printed to the terminal.
Comments:
-
This is a very easy form to read an ascii file, but it works only if the
data in the file matches the data you try to read. If the format does not
match you may run into an endless loop.
-
The syntax of the line root[1] will become clear later.
-
while(kTRUE) ... is the while form of an endless loop. You need
a break statement in the loop body to end it.
Example:
file: minimize.C
Double_t f(Double_t x) { return x*x; }
Double_t fprime(Double_t x) {return 2*x;}
Double_t minimize(Double_t initialGuess, Double_t desiredAccurancy)
{
Double_t x = initialGuess;
Double_t dx;
do {
dx = f(x) / fprime(x);
x -= dx;
} while(TMath::Abs(dx) > desiredAccurancy);
return x;
}
root [0] .L minimize.C
root [1] minimize(1,1e-10)
(Double_t)5.82076609134674070e-11
root [2] minimize(1,1e-100)
(Double_t)5.71493695641137490e-101
Explanation:
Minimize a function with the newton method
Comments:
-
Later we will be able to provide the functions f(x) and fprime(x) as arguments
to minimize.
Example:
root [0] Int_t sum = 0;
root [1] for(Int_t i=0; i<10; i++) sum +=i;
root [2] sum
(Int_t)45
Explanation:
[1]
The classical counting loop
init_expression: declare i and initialize it to zero
cont_expression: continue if i is less than 10
incr_expression: increment i by one
Comments:
Bitwise Operators
| C++ |
Purpose |
FORTRAN |
| ~i |
Bitwise complement |
NOT(I) |
| i & j |
Bitwise and |
IAND(I) |
| i ^ j |
Bitwise exclusive or |
IEOR(I) |
| i | j |
Bitwise or |
IOR(I) |
| i << n |
Bitwise left shift |
ISHFT(I,N) |
| i >> n |
Bitwise right shift |
ISHFT(I,-N) |
Comments:
-
We will not use these operators in this introduction. They are included
here just for reference.
Variables (Objects) and Types (Classes) in more detail
A computer program is a procedure for altering the values of objects.
An object is an area of the computer's memory that has
-
a value (a state), a particular bit pattern, stored in it
-
and a type (a class), which specifies how to interpret and manipulate the
object
The C++ type system knows the following fundamental data types and programmer
defined types named classes
C++ Fundamental Types
| C++ type |
Size (bytes) |
ROOT types |
Size (bytes) |
FORTRAN analog |
| (unsigned)char |
1 |
(U)Char_t |
1 |
CHARACTER*1 |
| (unsigned)short (int) |
2 |
(U)Short_t |
2 |
INTEGER*2 |
| (unsigned)int |
2 or 4 |
(U)Int_t |
4 |
INTEGER*4 |
| (unsigned)long (int) |
4 or 8 |
(U)Long_t |
8 |
INTEGER*8 |
| float |
4 |
Float_t |
4 |
REAL*4 |
| double |
8 (>=4) |
Double_t |
8 |
REAL*8 |
| long double |
16 (>= double) |
|
|
REAL*16 |
Usage hints:
-
Use the ROOT types
-
Use Int_t and Double_t unless you have good reasons to use another type
-
Do not use the unsigned types unless you have good reasons
see http://root.cern.ch/root/html/ListOfTypes.html
for a complete list of ROOT data types
Classes
A strength of C++ is the possibility to define own data types with associated
operations, socalled classes. The usage of such programmer defined types
is similar to the usage of the fundamental types.
In the following example we use the ROOT class TDatime. A short excerpt
from the documentation:
TDatime()
Create a TDatime and set it to the current time.
Int_t GetDate()
Return date in form of 19971224 (i.e. 24/12/1997)
Int_t GetTime()
Return time in form of 123623 (i.e. 12:36:23)
void Print(Option_t *)
Print date and time.
void Set(Int_t date, Int_t time)
Set date and time. Data must be in format 980418 or 19980418 and time
in
224512 (second precision). The date must
be >= 950101.
For years >= 2000, date can be given in the form 20001127 or 1001127
internally the date will be converted to 1001127
void Set(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t min,
Int_t sec)
Set date and time. Year may be xx where 95 <= xx <= 99.
The year must be >= 1995.
Example:
root [0] TDatime now;
root [1] now.Print();
Date/Time = Wed Oct 20 14:00:29 1999
root [2] TDatime yesterday;
root [3] yesterday.Print();
Date/Time = Wed Oct 20 14:01:15 1999
root [4] yesterday.Set(now.GetDate()-1,0);
root [5] yesterday.Print();
Date/Time = Tue Oct 19 00:00:00 1999
root [6] TDatime firstApril99;
root [7] firstApril99.Set(99,4,1,0,0,0);
root [8] firstApril99.Print();
Date/Time = Thu Apr 1 00:00:00 1999
Explanation:
[0]
Declare a variable (an object) of type (class) TDatime and name now
[1]
Call the Print() method of class TDatime for object now
[2][3]
Same as [0][1] but for an object yesterday. Of course the Print methods
prints the date of today.
[4]
Call the Set() method of class TDatime with two parameters for object
yesterday. Set the first parameter to the result of the call to the GetDate()
method of class TDatime for object now minus one, the second parameter
to 0
[5]
Call the Print() method for object yesterday.
[6]-[8]
Declare a variable of type TDatime with name firstApril99, set the
correct date, print the result.
Comments:
-
An object is an encapsulation of a state (internal data) and behavior (methods).
-
The behavior of objects is dictated by the objects class.
-
An object will exhibit its behavior by invoking a method (similar to executing
a function).
-
A declaration of an object calls its constructor, i.e. a method with the
same name as the corresponding class.
-
Method calls can be overloaded in the same way as ordinary functions. The
function to call follows from the number and type of the parameters.
Example:
file: timeForLoop.C
void timeForLoop(Int_t nMax = 1000000) {
// timing of nMax iterations of a for loop doing nothing
TStopwatch timer;
timer.Start();
for(Int_t i=0; i<nMax; i++);
timer.Stop();
cout << " Cpu time for "<< nMax << " iterations
" << timer.CpuTime() << endl;
}
root [0] .X timeForLoop.C(10000000)
Cpu time for 10000000 iterations 7.9
Explanation:
In the function timeForLoop an object timer of class TStopwatch is created
and used to time a for loop doing nothing.
We use the methods Start(), Stop() and CpuTime of class TStopwatch
Comments:
-
TStopwatch is a more interesting class than TDatime in the previous example.
The TDatime objects can be created and set to some date, but they are very
static.
-
TStopwatch objects change the internal state, they count the wallclock
time and cpu time between Start() and Stop().
Derived Types
For every type (fundamental as well as for classes) there are some derived
types: references, pointer and array types:
-
A variable is an association between a name and an object.
-
A reference is an alias for an object.
-
A pointer is an object that refers to another object.
-
An array is a linear sequence of objects of the same type
The type (class) of a variable or a pointer specifies the type of objects
that they can be associated with.
Example:
root [0] TDatime now;
root [1] now.Print();
Date/Time = Wed Oct 20 16:57:40 1999
root [2] TDatime & date = now;
root [3] now.Print()
Date/Time = Wed Oct 20 16:57:40 1999
root [4] now.Set(now.GetDate()-1,0)
root [5] now.Print()
Date/Time = Tue Oct 19 00:00:00 1999
root [6] date.Print();
Date/Time = Tue Oct 19 00:00:00 1999
root [7] TDatime & test;
Error: reference type date with no initialization ...
Explanation:
[0][1]
Create an object of type TDatime, call its Print() method.
[2][3]
Create an alias name date for object now, use its Print() methods.
It prints the same result.
[4][5]
Change object now, Print()
[6]
date.Print() gives the same result, date and now are two names for
the same object
[7]
A declaration of a reference(i.e. an alias) must specify the object
for which an alias should be created, otherwise the declaration is in error.
Comments:
-
The most important use of reference types is in function arguments and
will be explained later.
Example:
root [0] TDatime now;
root [1] TDatime *p;
root [2] p = &now;
root [3] p->Print();
Date/Time = Wed Oct 20 17:13:42 1999
root [4] (*p).Print();
Date/Time = Wed Oct 20 17:13:42 1999
Explanation:
[1]
Declare a pointer to a TDatime object, name it p
[2]
Set the pointer p to the address of object now
[3]
Call the Print() method of the object p is pointing to
[4]
*p is the object p is pointing to, so you can call the Print() method
via (*p).Print()
Comments:
-
Pointers are objects that refer to other objects, i.e. hold the address
of other objects.
-
If you need an object a pointer is pointing to, you have to use the dereferencing
operator *, *p means the object p is pointing to.
-
If you need the the address of an object e.g. to initialize an pointer,
you have to use the address operator &, &o means the address of
object o.
-
If you have a pointer to an object, you can use its methods via the ->
operator.
-
In CINT you may use the . or -> operator
to select members, from object variables or from pointers - one of the
CINT shortcuts. C++ requires the form object.member()
and objectpointer->member().
-
During the inheritance section we will discuss an important difference
between objects accessed by name or by pointer (. or ->)
Example:
root [0] Double_t x[10];
root [1] for(Int_t i=0; i<10; i++) x[i] = i;
root [2] Double_t y[10] = {0,1,2,3,4,5,6,7,8,9};
Explanation:
[0]
Declare an array of 10 Double_t objects with name x
[1]
Loop over the array elements
[2]
Declare and initialize an array of 10 Double_t objects with name y
Comments:
-
Arrays elements in C++ are addressed via [ index]
-
The first index of an array is 0.
-
The last index of an arry with n elements is n-1
Example:
root [0] Double_t bevingtonData[60];
root [1] ifstream file("bevington_chapter9.data");
root [2] for(Int_t i=0; i<60; i++) file >> bevingtonData[i];
root [3] bevingtonData[0]
(Double_t)6.00000000000000000e+00
root [4] bevingtonData[59]
(Double_t)1.50000000000000000e+01
Explanation:
[0]
Declare an Double_t array with name bevingtonData and 60 elements
[1]
Declare an object of class istream with name file, call the constructor
with one character string as parameter
[2]
Read 60 values from the stream file into consecutive elements of array
bevingtonData
[3][4]
check the first and last value
Comments:
-
Now we understand the syntax of connecting a file, its just creating an
object of class istream.
-
istream is part of the C++ library, therfore we can not lookup its documentation
on the ROOT page.
-
Multidimensional arrays are arrays of arrays, the way to declare e.g. a
two dimensional array is
Double_t x[10][20];
Example:
root [0] const Double_t pi = TMath::Pi();
root [1] pi
(Double_t)3.14159265358979310e+00
root [2] pi = 4;
Warning: Re-initialization ignored const pi ...
Explanation:
[0]
Declares pi a constant of type Double_t and initializes is to the result
of the function TMath::Pi();
[1]
Uses the constant pi
[2]
Tries to change the value of constant pi
Comments:
-
It is possible to declare constants, just put the keyword const in front
of the declaration.
-
Constants must be initialized in the declaration, it is not possible to
set them later.
-
Read declarations from the identfier name to the left:
const Int_t *p = &i; // p is pointer to Int_t which is
const
Int_t * const p = &i; // p is a const pointer to Int_t
const Int_i * const p = &i; // p is const pointer to Int_t
which is const
Int_t **p; // p is a pointer to a pointer to Int_t
Scope and lifetime
Scope: Where is an object known?
-
Inside the block it is declared.
-
Objects declared in an unnamed macro have global scope.
-
Objects declared in a function have function scope.
Lifetime: During which time is an object alive?
-
automatic:
from the execution of their declaration until the end of the block
in which they are declared
-
static:
from the execution of their declaration until the end of the program
-
dynamic:
allocated and freed in arbitary order under programmer's control
-
all objects we used up to now are automatic
Example:
file countCalls.C:
Int_t countCalls() {
// return how often this function has been called
static Int_t calls = 0;
return ++calls;
}
root [0] .L countCalls.C
root [1] countCalls()
(Int_t)1
root [2] countCalls()
(Int_t)2
root [3] countCalls()
(Int_t)3
root [4] calls
Error: No symbol calls in current scope ...
Explanation:
The variable calls in function countCalls is declared static, it remains
allocated from the first execution of that function until the end of the
program. It can be used to save information from function call to function
call. Without the static keyword it would be allocated and initialized
to zero every time the function is called.
[4]
calls is known only in the function where it is declared. Scope: function
countCalls, Lifetime: static
Example:
root [0] TDatime *now = new TDatime();
root [1] now->Print();
Date/Time = Fri Oct 22 09:43:39 1999
root [2] delete now;
root [3] now->Print();
Error: illegal pointer to class object now 0x0 ...
Explanation:
[0]
Declare a pointer named now to a TDatime object, initialize it with
the address of a newly created TDatime object
[1]
Use the object now is pointing to
[2]
Delete the object now is pointing to
[3]
Further usage of pointer now leads to an error
Comments:
-
new constructor(parameters); creates an anonymous object and returns
a pointer to that object. Such objects are called dynamic because their
lifetime in in the hand of the programmer. They are alive until they are
deleted with a delete statement.
-
delete pointer; deletes a dynamic objects the pointer points to
-
An allocation (new) must be paired with one and only one deallocation (delete).
-
Deallocating twice almost always causes your program to crash (CINT protects
you agaist that, but if you compile your macro ...)
-
Never deallocating can cause your program to use more and more memory if
its run (that may be tolerable in a short ROOT session)
Example:
file: fillArray.C
Double_t * fillArray(Int_t n) {
// create a double array of length n,
// fill it with random numbers
// return a pointer to it
Double_t *x = new Double_t[n];
TRandom generator(0);
for(Int_t i=0; i<n; i++) x[i] = generator.Rndm();
return x;
}
root [0] .L fillArray.C
root [1] Double_t *y;
root [2] y = fillArray(5);
root [3] for(Int_t i=0; i<5; i++) cout << y[i] <<
endl;
0.663086
0.669237
0.563476
0.741082
0.788386
root [4] delete[] y;
root [5] y = fillArray(5);
root [6] for(Int_t i=0; i<5; i++) cout << y[i] <<
endl;
0.664211
Explanation:
[fillArray.C]
x is a pointer to an array of n Double_t elements
genearator is an object of class TRandom
the constructor is called with the argument 0
the array x is filled with the result of calls to the Rndm() method
of object generator
fillArray returns a pointer to the newly created array
[1]
Declare a pointer y to a Double_t array
[2]
Set the pointer y to the result of a call to fillArray(5)
[3]
Output the 5 elements of y
[4]
delete the array y is pointing to
[5][6]
see [2][3]
Comments:
-
generator in file function fillArray is an automatic object, it will be
deleted when the function returns
-
x is a dynamic variable its lifetime is not bound to the function.
-
The name x goes out of scope when the function returns, to use the array
x its value has to be communicated
-
Notice that an array has to be deleted by the operator delete[]
instaed of delete as in the previous example
-
Without the delete[] x; statement, the above command sequence
would have created a memory leak.
Function Arguments
The arguments that appear in the parameter list of a function are called
formal arguments, the arguments passed to the function are called actual
arguments. Each formal argument is a local variable of the function, and
each formal argument is initialized with its corresponding actual argument.
I.e. the arguments are passed by value instead of by reference as in Fortran.
Example:
file swap.C
void swap0(Double_t x, Double_t y) {
Double_t temp = x;
x = y;
y = temp;
}
void swap1(Double_t &x, Double_t &y) {
Double_t temp = x;
x = y;
y = temp;
}
void swap2(Double_t *x, Double_t *y) {
Double_t temp = *x;
*x = *y;
*y = temp;
}
root [0] .L swap.C
root [1] Double_t a = 1;
root [2] Double_t b = 2;
root [3] swap0(a,b);
root [4] cout << "a=" << a << " b="<< b
<< endl;
a=1 b=2
root [5] swap1(a,b);
root [6] cout << "a=" << a << " b="<< b
<< endl;
a=2 b=1
root [7] swap2(&a,&b);
root [8] cout << "a=" << a << " b="<< b
<< endl;
a=1 b=2
Explanation:
[swap0]
x is a Double_t initialized with the value of the first actual argument
y is a Double_t initialized with the value of the second actual
argument
the values of x and y are swapped
[swap1]
x is an alias for the first actual argument
y is an alias for the second actual argument
the values of x and y are swapped
[swap2]
x is a pointer to Double_t initialized with the value of the first
actual argument
y is a pointer to Double_t initialized with the value of the second
actual argument
the values of the Double_t objects x and y are pointing to are swapped
[3][4]
the function swap0 has no effect on its actual parameters
[5][6]
the function swap1 changes the value of a and b
[7][8]
the function swap2 is called with the addresses of a and b, it does
not change its arguments (the addresses)but it changes the values of a
and b
Comments:
-
C++ passes arguments by value.
-
To change the objects passed to a function they must be passed by reference
or by pointer.
-
Passing large objects by values introduces performance problems because
large objects havt to be created as local variables. Passing arguments
as reference or pointer does not introduce this performance penalty.
-
Passing arguments by const reference cannot change the value of an argument
but avoids the performance penalty introduced by creating large local objects.
Classes:
To see how to use a class browse its header file:
MyClass.h:
class MyClass {
public:
MyClass();
MyClass(int x);
MyClass(char* text);
int GetX()
virtual void Draw();
protected:
SetX(int x);
private:
int x;
};
Inheritance
YourClass.h:
#include "MyClass.h"
class YourClass : public MyClass {
public:
YourClass();
YourClass(int x, int y);
int GetY();
virtual void Draw();
private:
int y;
};
Remarks:
There are two major advantages of inheritance:
-
Common implementation: It is easy to extend a class, to add some functionality
without touching or copying the code of the base class. You need not to
replicate the code of the base class. A lot of ROOT classes inherit from
TNamed. Objects of all of these classes can have names and titles.
-
Common behavior: Inheritance creates a "is-a" or better "is-usable-as"
relationship. Objects of the derived class can be used everywhere where
objects of the base class can be used. One can write functions with base
class objects as arguments. Caution: this way of using inheritance works
only
-
if used via reference or pointer
Shape *shape = new Shape[n];
shape[0] = new Triangle(...);
shape[1] = new Rectangle(...);
...
for(int i=0; i<n; i++) shape[i]->Draw();
Version 0.2: 10/22/99 Comments to Peter
Malzacher