C++ 将文件行分隔为 String 和 Int

C++ Seperating file line into String and Int

对于我的课程,我被分配了一个程序,允许用户输入他们的名字和分数,如果输入的分数大于文件中存储的十个分数之一,则该分数(和它的名称)将被新的乐谱和名称覆盖。但是,我不知道如何从文件中分离名称和分数(因为它们在同一行),以便我可以通过条件语句 运行 int。

这是我的乐谱文件的样子:

Henry | 100
Thomas | 85
Barry | 79
James | 76
Connor | 74
Jake | 70
Sam | 66
Rory | 60
Joe | 52
Darren | 49

假设用户输入了一个分数为 75 的名字,程序应该从列表中删除 Darren(分数最低的玩家)并添加新的名字和分数,分数不需要按顺序排列到我的任务简介。

这是我目前得到的代码:

void enterScore()
{
    std::cout << "Please enter your name" << std::endl;
    std::string name;
    std::cin >> name;

    std::cout << "Please enter your score" << std::endl;
    int score;
    std::cin >> score;

    std::string fileNames[10];  //Array for storing all 10 of the names already in the file
    int fileScores[10];  //Array for storing all 10 of the scores already in the file

    std::fstream inoutFile("Scores.txt");

    if (inoutFile.is_open())
    {
        //Divide the names and scores
        //E.G:
        //fileName[0] = Henry    fileScore[0] = 100
        //fileName[1] = Thomas   fileScore[1] = 85

        //Loop through all array cells
        //if fileName[i] < score, then:             Assignment brief states that the scores do not need to be sorted
        inoutFile << name << " | " << score << std::endl;  //Overwrite lowest score
    }
    else
    {
        std::cout << "File could not be opened" << std::endl;
    }
}

我已将伪代码添加到您的代码示例中,希望能指导您朝着正确的方向前进,而无需给出完整的作业解决方案。

void enterScore()
{
    std::cout << "Please enter your name" << std::endl;
    std::string name;
    std::cin >> name;

    std::cout << "Please enter your score" << std::endl;
    int score;
    std::cin >> score;

    std::string fileNames[10];  //Array for storing all 10 of the names already in the file
    int fileScores[10];  //Array for storing all 10 of the scores already in the file

    std::fstream inoutFile("Scores.txt");

    if (inoutFile.is_open())
    {
        //Loop on each line of the file, line by line
        // split the string into two parts, one before the pipe ( | ) and the other part after the pipe. There are multiple ways to do this, one would be to loop on each character of the string value and use a condition.

        //increment a counter variable

        //fileNames[myCounter] = assign first part of the string obtained above.
        //fileScores[myCounter] = assign second part of the string obtained above.


    // Rest of logic depends on precise requirement, see below
    }
    else
    {
    std::cout << "File could not be opened" << std::endl;
    }
}

如果您只想覆盖最低分数,则需要循环整个文件以找到要替换的最佳行。然而,如果你想替换任何较低的分数,你可以只用一个循环来完成。当您阅读文件中的行时,一旦找到分数较低的行,就可以替换它。

话虽这么说,为了一开始就让事情变得简单,我建议阅读整个文件,用一个变量来保持最低值及其在数组中的位置。

完成后,为简单起见,我建议编写第二个循环,在用新值替换所需值后,逐行使用两个数组中的值编写一个文件。

你可以google我的大部分comments/pseudo代码来获取你需要的代码。

我想提出一个“更”现代的 C++ 和面向对象的解决方案。并且,一个完整的工作解决方案。

在 C++ 中,我们通常将数据和操作该数据的函数放入一个对象 class 中。 class 并且只有 class 应该知道如何处理它的数据。没有外部全局函数应该这样做。

所以在你的例子中,“name”和“score”是一个对象的属性,从 std::istream 中提取它们或将它们插入 std::ostream 应该只有那个对象知道并由它处理。

因此,我们创建了一个class/struct,定义了2个数据成员,即“name”为std::string,“score”为int。然后,我们覆盖提取器运算符 (>>) 和插入器运算符。提取器运算符有点棘手,因为我们首先读取完整的一行,然后将其拆分为标记。

在 C++ 中,您的文件结构称为 CSV(逗号分隔值)。而且,阅读这是一项标准任务。首先,阅读完整的行,然后使用给定的定界符提取该字符串的标记。 “拆分”也称为“标记化”,C++ 具有用于该目的的专用功能:std::sregex_token_iterator。 这个东西是一个迭代器。用于遍历字符串,因此是“sregex”。 begin 部分定义了我们要在什么范围的输入上进行操作,然后在输入字符串中有一个 std::regex 表示应该匹配/或不应该匹配的内容。匹配策略的类型由最后一个参数给出。

1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.

我们可以使用此迭代器将标记存储在 std::vector 中。 std::vector 有一个范围构造函数,它接受 2 个迭代器作为参数,并将第一个迭代器和第二个迭代器之间的数据复制到 std::vector。声明

std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

将变量“tokens”定义为 std::string 类型的 std::vector 并使用所谓的 std::vector 范围构造函数。请注意:我使用的是 C++17,并且可以在没有模板参数的情况下定义 std::vector。编译器可以从给定的函数参数中推断出参数。此功能称为 CTAD ("class template argument deduction")。

此外,您可以看到我没有明确使用 "end()" 迭代器。

此迭代器将从具有正确类型的空大括号括起的初始值设定项列表构造,因为由于 std::vector 构造函数要求,它将被推断为与第一个参数的类型相同.

在读取行并将其拆分为标记后,我们检查是否正好有 2 个标记,然后将结果存储为“name”和“score”。所以,总的来说,一个非常简单的操作。

我们定义的下一个对象是 ScoreList。这包含一个 std::vector 的上述定义对象作为数据成员和一些用于演示目的的附加成员函数。我们有一个

  • 插入器过载
  • 从文件加载
  • 根据分数对列表进行排序
  • 保存文件
  • 添加新条目

只有前10名的秘诀是,对列表进行排序,只保存其中得分最高的10个名字。为此,我们使用 std::algorithm std::copy_n

为了读取完整的 csv-source-text 文件,我们还使用了专门的功能。

首先,我们打开文件并检查是否正确。然后我们使用 std::istream_iterator 从输入流中复制数据。这个迭代器,具有上面对象的给定类型,将逐行读取并将所有内容拆分为标记并将结果插入我们内部 std::vector.

的后面
std::copy(std::istream_iterator<NameAndScore>(is), {}, std::back_inserter(scoreList));

同样,非常简单和简短的一行。

其他功能也很简单。不用多说了。

main 中,我添加了一些驱动程序代码来向您展示它的外观。当然,您可以根据需要添加任何其他功能。


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>

const std::string delimiter("|");
const std::regex re("\" + delimiter);

struct NameAndScore {

    // The data

    std::string name{};
    int score{};

    // Member functions

    // Overwrite extractor operator
    friend std::istream& operator >> (std::istream& is, NameAndScore& ns) {
        // Read a comlete line
        if (std::string line; std::getline(is, line)) {
            // Split it into parts
            std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
            // If we have 2 parts, then assign to our internal data
            if (2 == tokens.size()) {
                ns.name = tokens[0];
                ns.score = std::stoi(tokens[1]);
            }
        }
        return is;
    }

    // Overwrite inserter operator
    friend std::ostream& operator << (std::ostream& os, const NameAndScore& ns) {
        return os << ns.name << " " << delimiter << " " << ns.score;
    }
};


class ScoreList {
    static constexpr size_t MaxElementsInList = 10;
    std::vector<NameAndScore> scoreList{};
public:
    // Inserter operator
    friend std::ostream& operator << (std::ostream& os, const ScoreList& sl) {
        std::for_each(sl.scoreList.begin(), sl.scoreList.end(), [&os](const NameAndScore& ns) {os << ns << "\n";});
        return os;
    }

    // Read score list from disk
    void load(const std::string& filename) {

        // Clear existing score list
        scoreList.clear();

        // Open file and check if OK
        if (std::ifstream is(filename); is) {
            // Read all data from file
            std::copy(std::istream_iterator<NameAndScore>(is), {}, std::back_inserter(scoreList));
        }
    }
    // Write Scorelist to disk
    void save(const std::string& filename) {

        // Open file and check, if it is OK
        if (std::ofstream os(filename); os) {
            sort();
            // Write 10 entries to a file
            std::copy_n(scoreList.begin(), MaxElementsInList, std::ostream_iterator<NameAndScore>(os, "\n"));
        }
    }

    // Sort the score list
    void sort() {
        std::sort(scoreList.begin(), scoreList.end(), [](const NameAndScore & s1, const NameAndScore & s2) { return s2.score < s1.score; });
    }

    // Add a new enty from the user
    void addEntry() {
        std::cout << "\nPlease enter your name:     ";
        if (std::string name; std::cin >> name) {
            std::cout << "\nPlease enter your score:    ";
            if (int score; std::cin >> score)
                scoreList.emplace_back(NameAndScore({name, score }));
        }
    }
};


const std::string filenameScoreList("r:\Scores.txt");

int main() {
    // Define an empty score list
    ScoreList sl{};

    // Load contents from file
    sl.load(filenameScoreList);

    // Show contents
    std::cout << sl;
    // Add a new entry, given by the user
    sl.addEntry();
    // Sort and save 10 top most scored data in a file
    sl.save(filenameScoreList);

    // Jsut for demo purposes. Load the file again
    // And sho the result
    sl.load(filenameScoreList);
    std::cout << sl;

    return 0;
}

我希望,我能给你一个想法,如何解决你的问题。 . .