跳轉到內容

ROOT/入門/使用 ROOT 的多種方式

來自 Wikibooks,開放的書籍,開放的世界

在本節中,我們將瞭解在 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_tInt_t,它們在任何機器上都是相同的。

您還應該注意到 coutsin()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 鍵,您將看到直譯器知道的四種可能性。它們是隨機類 TRandomTRandom1TRandom2TRandom3。我們稍後會看到它們有什麼用。

特別是如果您想嘗試一些新東西,並且不確定要使用哪些名稱,這可以提供巨大的幫助。

練習:嘗試互動式 ROOT
在互動式 ROOT 會話中:用 機器無關 ROOT 型別 Double_t 填充一個包含 100,000,000 個 64 位浮點數的陣列,用隨機數填充。計算陣列的平均值並將其列印到控制檯。注意計算時間。

提示:您可以透過建立一個指向 TRandom 類例項的指標來獲得隨機實數。(建構函式將一個任意整數作為初始化。)然後您可以呼叫方法 TRandom::Rndm() 來獲取一個簡單的偽隨機數,該隨機數均勻分佈在 0 和 1 之間。例如

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 宏
  1. 考慮在先前練習中解決的問題。現在編寫一個指令碼,執行與該指令碼相同的事情,並將其作為解釋型宏執行。它執行得更快嗎?
  2. 修改指令碼,以便使用者可以將要建立的隨機數數量作為引數傳遞。
  3. 過載宏,以便仍然可以傳遞數字數量,但如果未給出引數,則將生成 100,000,000 個數字。
[解決方案]

動態編譯程式碼

[編輯 | 編輯原始碼]

宏對於您在開始時想要做的幾乎所有事情都很好。但它們有一個巨大的缺點:它們執行緩慢!(有些人還觀察到在某些情況下出現奇怪的行為&#133;)然而,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 作為編譯後的宏執行。
  • 確保使用和不使用引數呼叫都仍然有效。
  • 在您更改指令碼後,它還能被 CINT 解釋嗎?
  • 編譯後的宏對於 100 000 000 個數字的效能如何?對於 5 個數字,哪種更快?
[解決方案]

構建獨立應用程式

[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]
  1. CERN: CINT. http://root.cern.ch/drupal/content/cint
華夏公益教科書