Rys historyczny
ROOT składa się z dwóch elementów - zestawu klas/bibliotek, oraz interpretera. W ROOT 5.x interpreterem był CINT. CINT interpretował program linia po linii. Miało to wiele zalet - min. kod się wykonywał poprawnie do momentu pierwszego błędu (CINT dopuszczał uruchomienie kodu z błędem składni), można było ładować biblioteki oraz klasy w locie w prosty sposób, kod uruchamiał się błyskawicznie, gdyż nie był w żaden sposób weryfikowany pod względem poprawności. Niestety CINT miał również wady: dwie główne to była wydajność oraz komunikaty o błędach. Ze względu na to, że CINT interpretował kod wykonanie większych operacji trwało o kilka rzędów wielkości dłużej, niż wykonanie dokładnie tego samego kodu przy pomocy skompilowanego kodu. Nie było to problemem w przypadku prostych operacji - np. otwierania pliku i wyświetlenia histogramu, ale już analiza danych zamiast trwać kilka minut, mogła trwać kilka godzin. Pewnym obejściem tego problemu było pisanie "najcięższych" instrukcji w formie biblioteki, którą się kompilowało i ładowało do makra. Stąd też "starsze" eksperymenty często wykorzystywały makra typu:
void macro(){ gSystem->Load("someLib.so"); SomeLib *s = new SomeLib(); s->Configure(); s->DoStuff(); }
Inny problemem dotyczącym CINTa było wyświetlanie błędów, które często wręcz utrudniało debuggowanie, pokazując błąd nie w tej linii w której on naprawdę istnieje.
ROOT 6
Główną różnicą w ROOT 6 jest wprowadzenie nowego interpretera - CLING. W CLING makra są kompilowane. Oznacza to, że czas uruchomienia makra jest dłuższy, z drugiej strony sam czas jego wykonywania jest krótszy. Kompilacja oznacza również, że kod nie może zabierać błędów składniowych - CINT te błędy tolerował w pewnej mierze (np. nie wymagał rzutowania klas), a jeśli nie był w stanie ich tolerować to wykonywał kod do wystąpienia błędu. I w zasadzie na tym można skończyć dywagacje na temat różnić między piątką a szóstką.
Jak wspomniałem wyżej ROOT to również biblioteki. Bibliotek tych można używać bezpośrednio w CLING oraz w programie. Zacznijmy więc pracę z ROOT'em.
Jeśli ROOT jest zainstalowany poprawnie, to powinniśmy mieć dostępną komendę root. Po wpisaniu tej komendy pojawi się okienko interpretera. I tutaj ważna uwaga ROOTa można uruchamiać z kilkoma opcjami, najważniejsze to:
- -l czyli nie rysuj ekranu powitalnego (tutaj polecam po zainstalowaniu roota w ~/.bashrc dodać linię alias root='root -l' dzięki czemu root będzie zawsze uruchamiany z tą opcją - ekran powitalny jest bowiem dosyć irytujący
- -b uruchamia ROOT w trybie "bezokienkowym", co oznacza że nie będą rysowane żadne histogramy itp.
- -q kończy sesję ROOT po wykonaniu makra tzn. makro jest wykonywane i root zostaje zamknięty
- nazwa_makra w tym wypadku root wykona dane makro - jeśli dodaliśmy -q to automatycznie się zamknie po jego wykonaniu
Teraz spróbujmy zrobić "hello worlda" . Zaczniemy od bezpośredniego użycia interpretera, a więc najpierw uruchamiany sesję ROOT'a (root), a następnie wpisujemy
cout<<"hello world"<<endl;
Trochę sprawa się komplikuje gdy chcemy zrobić np. pętlę, w postaci:
for(int i=0;i<10;i++){cout<<"hello"<<i<<endl;}
Jeśli chcemy to zrobić w jednej linii możemy po prostu wpisać for(int i=0;i<10;i++)cout<<"hello"<<i<<endl;, ale jeśli chcemy wykonać kilka linii w kroku pętli, powinniśmy użyć nawiasów klamrowych aby otworzyć tryb "wieloliniony" tj. wpisywać kolejno:
{
for(int i=0;i<10;i++)
{
cout<<"hello "<<i<<endl;
}
}
Bardzo rzadko w trybie interaktywnym używa się ROOT'a do czegoś więcej niż 1-2 komend. Bardziej zaawansowane programy pisze się w formie makr. Makra różnią się "zwykłego programu" pod kilkoma względami. Po pierwsze makro kompiluje root, nie trzeba więc pisać makefile co znacznie redukuje ilość niezbędnej pracy. Po drugie każdy program zaczyna się od funkcji "main" podczas gdy makro zaczyna się od wywołania funkcji o nazwie takiej samej jak maro. Napiszmy więc nasze makro hello_world.C
void hello_world(){ cout<<"hello world"<<endl; }
aby je uruchomić wystarczy wpisać komendę poniżej (wykona się ono również bez opcji -q, ale dzięki tej opcji root się od razu zamknie). Jak wspomniałem nazwa makra i "funkcji głównej" musi być taka sama - inaczej ROOT się zacznie sypać.
root -q hello_world.C
Możemy również uruchomić makro w już uruchomionym ROOT, interpreter ten obsługuje bowiem nie tylko komendy znane z C++, przykładowo:
- .L hello_world.C załaduje nam makro, wtedy można wpisać hello_world() i zostanie wykonana funkcja hello_world (lub dowolna inna zdefiniowana w makrze)
- .x hello_world.C wykona makro - tutaj podobnie jak w przypadku uruchamiania bezpośrednio nazwa funkcji "startowej" musi odpowiadać nazwie makra
- .! wykonaj funkcję z terminala i np. .! ls wyświetli listę plików a .! echo "zonk" wyświetli słowo zonk gdyż to samo zrobiłyby ls czy echo "zonk" wypisane w terminalu
- .q kończy sesję ROOT
W przypadku, gdy chcemy przekazać do makra jakieś parametry musimy zadbać o oto, aby funkcja o takiej samej nazwie jak makro przyjmowała parametry oraz oczywiście o ich podanie, przykładowo nasze hello world może teraz wyglądać tak:
void hello_world(int par){ for(int i=0;i<par;i++){ cout<<"hello world "<<i<<endl; } }
Następnie możemy uruchomić taki kod bezpośrednio z terminala
root -q 'hello_world.C(1)'
lub załadować cały kod i wykonać funkcję hello_world z parametrem
root
.L hello_wordl.C
hello_world(1)