【问题标题】:Trouble checking for newline character when taking integers as input将整数作为输入时检查换行符时遇到问题
【发布时间】:2020-07-09 13:42:54
【问题描述】:

我正在为我的一个类工作一个小项目,该项目涉及从文件中获取格式化输入,但我无法检测换行符。这是我正在使用的函数:

void averager(vector<double> &outvec)
{
    ifstream temp;
    vector<int> tempvec;
    double average;

    temp.open("temp.dat");

    int num;

    while (! temp.eof())
    {
        temp >> num;

        if (num != 9999)
            tempvec.push_back(num);

        else if (num == 9999)
        {
           average = accumulate(tempvec.begin(), tempvec.end(), 0.0) / tempvec.size();

           outvec.push_back(average);
           tempvec.clear();
        }
    }

    temp.close();

    return;
}

我从一个包含整数集的临时文件中获取输入,在这种情况下是测试分数,以空格分隔,以整数 9999 作为分隔符结束。当它到达分隔符时,它会停止对整数进行平均,将新数字放入向量outvec,然后在下一行重新开始。

我只想使用'\n' 字符作为分隔符,但我似乎无法检查我的num 变量是否等于一个字符,最好是换行符。我在行尾注入一个奇怪整数的方法有效,但我不喜欢它。

对不起,如果这段代码很糟糕或者这个问题很愚蠢;我是一个初学者,我希望这是一个很好的代码。提前致谢!

【问题讨论】:

标签: c++ vector stream


【解决方案1】:

函数std::getline() 可以用于此。它将一行输入读入一个字符串。使用该字符串,您可以解析出所有整数。

要解析整数,您可以使用std::istringstream,其作用类似于std::cin,但它使用字符串作为源而不是控制台。

#include <sstream>
// ...
std::string line;
while (std::getline(std::cin, line)) {
  std::istringstream sstream(line);
  int temp;
  while (sstream >> temp) {
     tempvec.push_back(temp);
  }
  average = accumulate(tempvec.begin(), tempvec.end(), 0.0) / tempvec.size();
  outvec.push_back(average);
  tempvec.clear();
}

【讨论】:

  • 嘿,我在调用std::istringstream sstream(line); 时遇到错误,告诉我“不允许输入不完整类型”。有什么见解吗?
  • @kiwi_ std::istringstream 在标题&lt;sstream&gt; 下提供,因此您需要将其包含在#include &lt;sstream&gt; 中。
  • 效果很好!我只是没有给 getline 正确的数据。非常感谢!
【解决方案2】:

首先。给出的分析器非常好,足以满足经验水平并被接受。一切都很好。

我想给出一个额外的答案,向您展示使用 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,您可以而且应该返回“按值”。即使对于超大的物体。另请参阅herehere

接下来,“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 的内部范围内使用声明的变量。您可能听说过forwhile 是可互换的,因为它们的功能相同。但是使用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。这是默认初始化器(请参阅herehere。如果我们查看std::istream_iterator 的描述,我们可以看到默认是结束迭代器。

另外请注意:由于我们使用的是 C++17,我们可以为没有模板参数的“值”定义 std::vector。所以,不是std::vector&lt;int&gt; values,而只是std::vector values,没有&lt;int&gt;。编译器可以从给定的函数参数中推断出参数。此功能称为 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++ 做什么

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-12
    • 2020-08-05
    • 2018-12-09
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多