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