ROOT/入門/使用 ROOT 的多種方式
在本節中,我們將瞭解在 ROOT 中執行程式碼的多種方法。
如果您在終端上啟動 ROOT 會話,您將獲得一個直譯器,它會接受您的程式碼並逐行執行。例如,讓我們嘗試“Hello World”程式
| root [0] cout << "hello world!" << endl; |
| hello world! |
嗯,這很簡單吧?讓我們看看發生了什麼。當您按下回車鍵時,第 0 行(由開頭處的 root [0] 指示)被髮送到 CINT,它會立即執行它。CINT 是由 Goto 正治(安捷倫科技)、Philippe Canal 和 Paul Russo(費米實驗室)以及 Leandro Franco、Diego Marcos 和 Axel Naumann(CERN)開發的 C++ 直譯器[1]。直譯器是一種軟體,它逐行執行程式碼,而不是一次編譯所有程式碼。這具有不必等待編譯器並且可以立即看到/檢查結果的優點。另一方面,解釋程式碼比執行編譯後的程式碼要慢得多。
即使您在命令完成之前就按下了回車鍵,直譯器也能智慧地等待您完成命令。當輸入迴圈時,這很有用,我們可以在以下示例中看到
| root [1] Float_t u = 0.0; |
| root [2] for (Int_t i = 0; i < 10; i++) { |
| root [3] u = 2.0 * cos(u) - sin(u); |
| root [3] if (u >= 0) { |
| root [4] cout << u << " is positive" << endl; } |
| root [5] else { |
| root [6] cout << u << " is negative" << endl; } |
| root [7] } |
| 2 is positive |
| -1.74159 is negative |
| 0.645519 is positive |
| 0.995963 is positive |
| 0.248108 is positive |
| 1.69319 is positive |
| -1.23669 is negative |
| 1.60055 is positive |
| -1.05906 is negative |
| 1.85128 is positive |
請注意,我在這裡使用了機器無關型別 Float_t 和 Int_t,它們在任何機器上都是相同的。
您還應該注意到 cout、sin() 和 cos() 按預期工作,即使我們從未包含相應的標頭檔案或解析名稱空間。這是因為程式碼沒有被編譯,CINT 知道標準 C++ 函式。
使用 ROOT 命令列的另一個好處是,如果您犯了錯誤,您會立即得到通知並可以輕鬆地糾正它。
| root [8] cout < "watch out!" << endl; |
| Error: operator< not defined for basic_ostream<char,char_traits<char> > (tmpfile):1 *** Interpreter error recovered *** |
| root [9] cout << "watch out!" << endl; |
| watch out! |
在這個例子中,我在第 0 行忘記了第二個 <。直譯器告訴我,我可以透過按向上箭頭鍵來呼叫最後一行,糾正錯誤並再次執行它。
應該說,CINT 非常寬容,允許您使用通常無效的 C++ 輸入。但是,我明確建議不要使用此選項,因為它會使您的程式碼看起來很奇怪,難以理解,更容易失敗,並且如果以後想要編譯,則無法編譯。因此,這些“功能”將不會在此處介紹。
另一個非常有用的工具是自動完成。例如,嘗試輸入 TRa,然後按 Tab 鍵。該詞將完成為 TRandom,這仍然是模稜兩可的。如果您再次按下 Tab 鍵,您將看到直譯器知道的四種可能性。它們是隨機類 TRandom、TRandom1、TRandom2 和 TRandom3。我們稍後會看到它們有什麼用。
特別是如果您想嘗試一些新東西,並且不確定要使用哪些名稱,這可以提供巨大的幫助。
| 練習:嘗試互動式 ROOT |
|---|
在互動式 ROOT 會話中:用 機器無關 ROOT 型別 Double_t 填充一個包含 100,000,000 個 64 位浮點數的陣列,用隨機數填充。計算陣列的平均值並將其列印到控制檯。注意計算時間。提示:您可以透過建立一個指向 TRandom *R = new TRandom(time(0)); // create a pointer to a new instance of TRandom in the heap
cout << R->Rndm() << endl;
|
| [解決方案] |
您現在已經瞭解瞭如何使用 ROOT 命令列。這很好,特別是如果您想嘗試一些東西,並且不確定語法。但是,如果您要解決更復雜的問題,您可能希望將您的程式碼儲存在一個可以編輯和多次執行的檔案中。那麼還有什麼比將命令儲存在一個指令碼檔案中並告訴 ROOT 執行它們更方便的呢?我們開始吧
建立一個名為 helloscript.cc 的文字檔案。在這個檔案中,您可以放置您在會話期間可能鍵入的任何命令。為了簡單起見,讓我們先做“Hello World”這件事。在這種情況下,您將編寫
void helloscript()
{
cout << "hello world!" << endl;
}
指令碼檔案必須包含一個名為與檔案相同的 void 函式。當 ROOT 執行您的指令碼檔案時,它將呼叫此函式。為此,請在您儲存指令碼的目錄中開啟 ROOT 會話並說
| root [10] .x helloscript.cc |
| hello world! |
請注意,程式碼仍然逐行解釋,而不是編譯。除了您必須將所有內容放在 void 函式中之外,與使用命令列沒有區別。特別地,不需要解析名稱空間(例如,對於 iostream 中的 cout)。
您可以將引數傳遞給您呼叫的宏。考慮以下指令碼
void hello(Int_t times)
{
for (Int_t i = 0; i < times; i++)
{
cout << "hello world!" << endl;
}
}
您可以透過以下方式呼叫它
| root [11] .x hello.cc(3) |
| hello world! hello world! hello world! |
甚至可以過載函式。例如,我們可以定義 void hello() 和 void hello(Int_t times)。現在,這兩個呼叫中的任何一個
| root [12] .x hello.cc |
和
| root [13] .x hello.cc(3) |
都是合法的。
| 練習:解釋型 ROOT 宏 |
|---|
|
| [解決方案] |
宏對於您在開始時想要做的幾乎所有事情都很好。但它們有一個巨大的缺點:它們執行緩慢!(有些人還觀察到在某些情況下出現奇怪的行為…)然而,ROOT 實現了非常複雜的例程,以確保充分利用機器的資源。要使用它們,您必須編譯您的程式碼。
我們只是看一下我們之前編寫的指令碼。為了使其成為有效的 C++,我們現在必須瞭解名稱空間和標頭檔案。(如果您以前使用過 CINT 仍然接受的混亂程式碼——我警告過您——那麼您現在必須糾正它。否則 AcLiC 將無法編譯。)因此,我們修改後的檔案應該看起來像這樣
# include <iostream>
using namespace std;
void helloscript()
{
cout << "hello world!" << endl;
}
現在我們可以透過以下方式執行它
| root [14] .x helloscript.cc+ |
當然,輸出結果是一樣的。末尾的“+”告訴 ROOT 在執行之前編譯程式碼。這是由一個名為 ACLiC(“Automatic Compiler of Libraries for CINT”)的程式完成的。ACLiC 是一個智慧工具,它利用您安裝的編譯器構建並重用來自編譯程式碼的庫。但是,您也可以透過在末尾新增“++”來告訴 ACLiC 即使不需要也從頭構建庫。
另一個好訊息是,即使您以 ACLiC 可以編譯的方式修改了指令碼,仍然可以
- 傳遞引數。語法:
.x<script>.cc+(<parameter>) - 透過 CINT 解釋它,而無需編譯。
| 練習:編譯後的 ROOT 宏 |
|---|
使用 您之前編寫的指令碼 並修改它,以便它可以透過 ACLiC 作為編譯後的宏執行。
|
| [解決方案] |
構建獨立應用程式
[edit | edit source]執行 ROOT 程式碼最先進的方式是將其製作成獨立應用程式。請記住,ROOT 本質上是一組 C++ 類。這些類可用於構建全新的應用程式,這些應用程式可以編譯和執行,而無需再依賴原始的 ROOT 安裝。我將展示如何使用 g++ 進行編譯。
要在我們的檔案上執行 g++,它必須符合 C++ 標準。特別是,它必須包含一個 int main() 函式。當然,可以簡單地將以前的 void 方法重新命名,但有一個更優雅的方法。如果我們重新命名該函式,則程式碼可以編譯,但不再可以解釋。(在解釋程式碼中不允許使用名為 int main() 的函式,因為它會與 CINT 的內部函式衝突。)值得慶幸的是,CINT 定義了預處理變數 __CINT__。瞭解這一點後,我們可以新增一個 main 函式,該函式只有在 g++ 處理程式碼時才可見,而在 CINT 處理程式碼時不可見。以下是一個示例
# include <iostream>
using namespace std;
void hello()
{
cout << "hello world!" << endl;
}
# ifndef __CINT__
int main()
{
hello();
return 0;
}
# endif
從 CINT 的角度來看,該檔案根本沒有改變。當 g++ 編譯它時,它將使 main 呼叫 void 函式,這正是 CINT 通常會做的事情。
要編譯該檔案,請說
g++ -o hello hello.cc `root-config --cflags --glibs`
然後用以下命令執行它
./hello
根本不需要關心 ROOT。在編譯時,ROOT 會為您確保所有連結都正確設定。
但是,您可能會有點失望,因為我們實際上寫的只是一個 hello world 程式,它根本沒有使用任何 ROOT 資源。好吧,這裡還有一個簡單的示例,它使用內建的資料型別 Double_t 和隨機類 TRandom。
# include <iostream>
# include "TRandom.h"
using namespace std;
void test()
{
TRandom *rnd = new TRandom(time(0));
Double_t x = rnd->Rndm();
cout << "x = " << x << endl;
}
# ifndef __CINT__
int main()
{
test();
return 0;
}
# endif
| 練習:一個簡單的獨立應用程式 |
|---|
| 再次考慮 您應該之前編寫的程式碼 以計算一組隨機數的平均值。現在,最後但並非最不重要的一點是,將其製作成一個獨立的應用程式,並使其獨立於任何 ROOT 會話執行。確保引數傳遞仍然有效。檢查應用程式的效能。還要注意您的修改不會干擾 CINT 或 ACLiC 的解釋或即時編譯。 |
| [解決方案] |
參考文獻
[edit | edit source]- ↑ CERN: CINT. http://root.cern.ch/drupal/content/cint