首先。给出的分析器非常好,足以满足经验水平并被接受。一切都很好。
我想给出一个额外的答案,向您展示使用 C++17 的更现代的 C++ 解决方案以及它的外观。基本算法与给出答案中的相同。
首先请看代码:
std::vector<double> averager(const std::string& fileName) {
// The resulting data. A vector with all average values per line
std::vector<double> result{};
// Open the file and check, if it could be opened.
if (std::ifstream sourceFileStream(fileName); sourceFileStream) {
// Now read all lines of the file in a simple for loop
for (std::string line{}; std::getline(sourceFileStream, line); ) {
// Put the just read line into an std::istringstream for easier extraction
std::istringstream iss{line};
// Construct a std::vector value using its range constructor and get all ints in the iss
std::vector values(std::istream_iterator<int>(iss), {});
// Calculate the average and store it in our result vector
result.emplace_back(std::accumulate(values.begin(), values.end(), 0.0) / values.size());
}
}
else {
std::cerr << "\n*** Error: Could not open source file '" << fileName << "'\n";
}
return result;
}
const std::string fileName{"r:\\temp.dat"};
int main() {
// Read all averages from file
std::vector avr{ averager(fileName) };
// Show the result to the user
std::copy(avr.begin(), avr.end(), std::ostream_iterator<double>(std::cout, "\n"));
return 0;
}
好的,那我们来看看吧。起初,我们在其中看到许多 cmets。编写 cmets 非常重要。这可以帮助您了解自己在做什么。现在和以后。它甚至可以防止错误。而且,代码的质量也会提高。没有 cmets 的代码质量为零。
接下来你看到的是我改变了函数的签名。我会return 结果。在早期,人们认为返回大数据或复杂数据是不好的。但在 C++ 中,我们现在有 RVO(返回值优化)和复制省略。
使用 RVO 和 Copy elision,您可以而且应该返回“按值”。即使对于超大的物体。另请参阅here 和 here
接下来,“if 语句”。
我们这里有一个if statement with initializer。这从 C++17 开始可用。您可以(除了条件)定义一个变量并初始化它。所以,在
if (std::ifstream sourceFileStream(fileName); sourceFileStream) {
我们首先定义一个名为“sourceFileStream”的变量,类型为std::ifstream。我们使用统一初始化器“{}”来使用输入文件名对其进行初始化。
这将调用变量“sourceFileStream”的构造函数,并打开文件。 (这是,这个构造函数是否如此)。在“if-statement”的关闭“}”之后,变量“sourceFileStream”将超出范围,std::ifstream 的析构函数将被调用。这将自动关闭文件。
引入了这种类型的 if 语句来帮助防止命名空间污染。该变量应仅在使用它的范围内可见。没有它,您将不得不在 if 外部(之前)定义 std::ifstream ,并且它对于外部上下文是可见的,并且文件将在很晚的时候关闭。所以,请熟悉它。
接下来和类似的,while 语句。我们不在 out 范围内声明变量,然后开始一个 while 循环,我们确实在 while 的内部范围内使用声明的变量。您可能听说过for 和while 是可互换的,因为它们的功能相同。但是使用for,您可以将初始化程序作为第一个元素。因此,出于这个原因,for 的使用是目前更推荐的解决方案。
所以循环看起来像
// Now read all lines of the file in a simple for loop
for (std::string line{}; std::getline(sourceFileStream, line); ) {
这完全一样
// Now read all lines of the file in a simple for loop
std::string line{};
while (std::getline(sourceFileStream, line)) {
但是,没有命名空间污染,for 只有一行。
让我们更深入地了解for 语句(或while)的条件部分。我们通常期望一些布尔条件或布尔值或函数的布尔结果。在这里,这是可行的,因为std::getline 返回正在运行的流,因此是对“”的引用。并且流具有覆盖的布尔运算符!,以检查流的条件。请参阅here。因此,如果发生错误(或“文件结尾”),则条件将为 false。
您应该始终检查每次 IO-Operation 是否有效。
所以,我们使用for 逐行读取源文件。并且每一行,我们将输入一个std::istringstream,以便能够像使用标准“>>”语句(提取器)一样从中提取值。
下一行是:
// Construct a std::vector "value" using its range constructor and get all ints in the iss
std::vector values(std::istream_iterator<int>(iss), {});
哦哦。那是什么。让我们从std::istream_iterator 开始。如果您阅读链接的描述,那么您会发现它基本上会为指定类型调用提取器运算符 >>。并且由于它是一个迭代器,如果迭代器递增,它将一次又一次地调用它。好的,可以理解,但是。 . .
我们将变量值定义为std::vector,并使用 2 个参数调用其构造函数。该构造函数就是std::vector 的所谓范围构造函数。请参阅constructor (5) 的说明。啊哈,它有一个“begin()”迭代器和一个“end()”迭代器。好的,但是这个奇怪的 {} 是什么而不是“end()”-iterator。这是默认初始化器(请参阅here 和here。如果我们查看std::istream_iterator 的描述,我们可以看到默认是结束迭代器。
另外请注意:由于我们使用的是 C++17,我们可以为没有模板参数的“值”定义 std::vector。所以,不是std::vector<int> values,而只是std::vector values,没有<int>。编译器可以从给定的函数参数中推断出参数。此功能称为 CTAD(“类模板参数推导”)。因此,我们将在稍后的函数 main 中使用它。
啊哈,这就是它的工作原理。
函数的最后一个重要语句是:
// Calculate the average and store it in our result vector
result.emplace_back(std::accumulate(values.begin(), values.end(), 0.0) / values.size());
我们不会先创建一个临时值,然后再将其数据复制到向量中。我们使用std::veoctors emplace_back() 函数来做一个inplace construction 的值。这样可以节省不必要的复制操作。
最后,我们返回结果,函数就是这样。
在 main 中,我们定义变量 avr(再次使用 CTAD)并用我们函数的结果初始化它。所有数据都将被读取和计算,用那条简单的线。
最后但同样重要的是,我们使用std::ostream_iteratorcopy 将所有数据发送到```std::cout````。
所以,我希望我能给你解释一下,你可以用现代 C++ 做什么