2.4 编译执行:分开数据读取与分析代码
这一节保留 2.3 的 tracking 算法。区别是把它从 MakeClass 生成的文件中移到 ana 类。以后 branch 变化需要重新生成 reader 时,物理分析代码仍保留在自己的文件中。
1. 文件与继承关系
本例位于 code/compile2。tracking.h/C 由 MakeClass 生成,负责读取 branch;ana.h/ana.cpp 是用户分析代码。main.cpp 仍负责打开文件和保存结果。
class ana : public tracking {
public:
TTree *fOutTree;
ana(TTree *input, TTree *output)
: tracking(input), fOutTree(output) {}
void Analysis();
// xx、yy、tx 等计算变量及方法声明见完整 ana.h
};: tracking(input) 调用基类构造函数,绑定输入树;fOutTree(output) 保存输出树指针。Analysis() 中的 GetEntry、径迹拟合和 Fill 与上一节相同,不需要再复制一遍学习。
初始化列表的含义
构造函数冒号后的 tracking(input) 调用基类构造函数,让基类绑定输入 Branch;fOutTree(output) 直接初始化成员指针。它们在构造函数的大括号执行前完成。
初始化与先构造后赋值并不相同。对类对象,初始化列表可避免先默认构造再赋值;对未显式初始化的基本类型,不应把其未确定的值描述为“系统分配的随机数”。
保留自动生成的读取层
以后 TTree 结构变化时,重新生成 tracking 的读取成员,物理分析仍放在 ana 中。ana::Analysis() 使用继承来的 GetEntry、Branch 变量和输入树指针,输出树则由 main 创建并传入。这样可以检查数据读取的改动是否影响分析,而不必把两部分代码混在一起重写。
%jsroot on
2. 主程序:参数、文件、分析调用
argc 是参数个数,argv[1] 是 run 号。没有提供目录时,输入在章目录,输出在本例程序目录。下面代码与实际 main.cpp 一致;读取失败时返回非零值,供批处理脚本判断。
#include <TFile.h>
#include <TTree.h>
#include <TString.h>
#include <cstdlib>
#include <iostream>
#include "ana.h"
int main(int argc, char** argv) {
if (argc!=2 && argc!=4) {
std::cerr << "Usage: ./tracking run [input_dir output_dir]\n";
return 1;
}
char* end = nullptr;
long parsed = std::strtol(argv[1], &end, 10);
if (end==argv[1] || end!='\0' || parsed<0 || parsed>999999) {
std::cerr << "Invalid run number: " << argv[1] << '\n';
return 1;
}
int run = int(parsed);
const char inputDir = argc==4 ? argv[2] : "../..";
const char* outputDir = argc==4 ? argv[3] : ".";
TString inputName = Form("%s/f8ppac%03d.root",inputDir,run);
TString outputName = Form("%s/out%03d.root",outputDir,run);
TFile* input = TFile::Open(inputName);
if (!input || input->IsZombie()) {
std::cerr << "Cannot open " << inputName << '\n';
delete input;
return 1;
}
TTree* tin = input->Get<TTree>("tree");
if (!tin) {
std::cerr << "Missing tree in " << inputName << '\n';
delete input;
return 1;
}
TFile output(outputName,"RECREATE");
if (output.IsZombie()) return 1;
TTree* tout = new TTree("tree","PPAC tracking");
{
ana analysis(tin,tout);
analysis.Analysis();
std::cout << "Input=" << tin->GetEntries() << ", output=" << tout->GetEntries() << '\n';
output.Write();
} // MakeClass 基类析构时释放输入文件;不再重复 delete input。
return 0;
}
3. Makefile:编译与链接
CXX = c++
CPPFLAGS = -Iinclude $(shell root-config --cflags)
CXXFLAGS = -O2 -Wall
LDLIBS = $(shell root-config --libs)
SOURCES = main.cpp $(wildcard src/*.cpp src/*.C)
HEADERS = $(wildcard include/*.h)
all: tracking
tracking: $(SOURCES) $(HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(SOURCES) $(LDLIBS) -o $@
clean:
rm -f tracking
root-config --cflags 提供 ROOT 头文件及编译选项,--libs 提供链接库。包含头文件解决声明问题,链接解决函数实现问题,两者不能互相替代。修改源码后重新执行 make;编译失败先看第一条 error。
4. 运行和核对
在章目录执行下面两行。程序读取 f8ppac001.root,写出 code/compile2/out001.root。原始文件不变。使用同一输入与选择条件时,输出事例编号、靶点位置及拟合参数应与 2.2 一致,而不只是比较最终事例数。
!make -C code/compile2
!cd code/compile2 && ./tracking 1
make: Nothing to be done for `all'.
Processing Event: 30000 / 739685
Processing Event: 50000 / 739685
Processing Event: 110000 / 739685
Processing Event: 130000 / 739685
Processing Event: 140000 / 739685
Processing Event: 150000 / 739685
Processing Event: 190000 / 739685
Processing Event: 240000 / 739685
Processing Event: 270000 / 739685
Processing Event: 360000 / 739685
Processing Event: 390000 / 739685
Processing Event: 400000 / 739685
Processing Event: 410000 / 739685
Processing Event: 440000 / 739685
Processing Event: 450000 / 739685
Processing Event: 490000 / 739685
Processing Event: 550000 / 739685
Processing Event: 560000 / 739685
Processing Event: 590000 / 739685
Processing Event: 600000 / 739685
Processing Event: 620000 / 739685
Processing Event: 630000 / 739685
Processing Event: 640000 / 739685
Processing Event: 650000 / 739685
Input events=739685, accepted reference tracks=232180
Input=739685, output=232180
完整源码:main.cpp · tracking.h · tracking.C · ana.h · ana.cpp
