为未知 length/structure 输入重载运算符>>()

Overloading operator>>() for unknown length/structure of input

我正在尝试读取包含个人信息的文件。每行包含一个人的数据,假设它看起来像这样:

First(s) Last ID SSN
Peter Barker 1234 5678
James Herbert Bond 007 999
Barack Hussein Obama 2007 14165

所以我想使用 std::copy 读取每一行并将其复制到 (std::vector<Person>) 中,如下所示:

struct Person
{
    std::string firstName_s;
    std::string lastName;
    int ID;
    int SSD;
}

我认为重载提取运算符会很方便:

std::istringstream& operator>>(std::istringstream& in, struct Person& person)
{
    struct Person tmp;
    in  >> tmp.firstName_s 
        >> tmp.lastName 
        >> tmp.ID 
        >> tmp.SSN;
    person = std::move(tmp);
    return in;
}

但是,我遇到的问题是我不知道这个人会有多少个名字

我考虑过将全名读入一个字符串,直到遇到一个数字,然后他们将姓氏从包含名字的字符串中分离出来,这工作正常但看起来 'ugly'。 如果有人有更好的建议,或者 link 我可以看看,那就太好了,我自己似乎找不到东西! 谢谢。

如果你有一个可变长度的行(就字数而言),你可以简单地读取整行并从右边开始处理它,或者缓存所有单词并使用偏移量。下面的例子是后一个。

int to_int(std::string_view str)
{
    int val = 0;
    std::from_chars(str.data(), str.data() + str.size(), val);
    return val;
}

std::istream& operator>>(std::istream& in, Person& person)
{
    std::string line;

    // read whole line
    if (std::getline(in, line))
    {
        // split line into words
        std::vector<std::string> words;
        std::stringstream tmp_stream(line);
        for (std::string word; tmp_stream >> word; )
            words.push_back(word);

        // join first names
        tmp_stream.str(words[0]);
        for (std::size_t i = 1; i < words.size() - 3; i++)
            tmp_stream << ' ' << words[i];

        person.firstName_s = tmp_stream.str();
        person.lastName = words[words.size() - 3];
        person.ID = to_int(words[words.size() - 2]);
        person.SSN = to_int(words[words.size() - 1]);
    }

    return in;
}

我认为代码是不言自明的。 Here 是一个完整的例子。

The OP does not want to change the file, so this answer is no longer suitable. Work in progress.

这里有两大策略

我们可以选择:

  • 用户为我们格式化数据。
  • 程序格式化数据。

然后我们解析它。

1) 固定数据格式:

最简单的解决方案。
使用户以易于解析的格式输入数据。在这里,由用户正确输入数据,程序将检查输入的数据是否正确。这可以通过多种方式完成,包括但不完整:

Peter-Richmond Barker 1234 5678        // hyphen(-) separated
"James Herbert" Bond 007 999           // enclosed in quotes("")
Barack_Hussein Obama 2007 14165        // underscore(_) separated

第一种和第三种情况,std::cin >> person.first_names就够了。
在第二种情况下,你必须

std::getline(std::cin, person.first_names, '\"'); // any character delimiter

它,在用 std::cin.get() == '\"' 检查开始分隔符后。

2) 缓慢而稳定

另一个非常简单的解决方案。只需让用户一次输入一件事:

std::cout << "Enter some datum 1: ";
std::cin >> person.some_datum_1;
...

(与大众想象相反,datum is singular, and data is plural)。
对于多个输入,请参阅 line tokenisation:

让我在这里抓住一个方法:

std::cout << "Enter some data 1: ";
// Grab the line and put into a stream
std::getline(std::cin, line);
std::stringstream line_buffer(line);

// Prepare to iterate over the stream
std::istream_iterator<std::string> it(line_buffer);
std::istream_iterator<std::string> end;

// Set the name with a move assignment operator
person.first_names = std::move(std::vector<std::string>(it, end));
...

请注意,此方法需要 person.first_namesstd::vector<std::string>

3) 从头开始

这里先输入未定尺寸的数据

Warning: It will work only for a single undetermined size input. If both the first and last names can be more than two, this wouldn't work. I mention it only for completeness.

如果您不想强迫用户这样做并破坏他们的体验,您将不得不自己解析输入。整行输入好旧的 std::getline(std::cin, line);.

初始化int read_from = std::string::npos;
现在,找到最后一个 space 和 read_from = line.rfind(' ', read_from);。一个read_from == std::string::npos会告诉你所有的输入都被解析了,或者有错误。

A line.substr(read_from) 获取最后一个输入。将其转换为适当的类型并存储。您还必须使用 line.resize(read_from);

擦除已解析的输入

冲洗并重复其他输入。

Note: It is suggested to store the undetermined data in a std::vector of the appropriate type.

4) 字节行进

我知道你会说我们没有解决 OP 的问题,

... read in a file with personal information...

既然我们已经讨论了从用户那里获取输入,我们还可以选择如何存储它(并获取它)。

最简单的方法是:

personal_data_file.write((char*)&person_list[i], sizeof(Person));  // Write it...
personal_data_file.read((char*)&person_list[i], sizeof(Person));   // ...Now read it.

在一个循环中,其中 person_listPersonstd::vector

Note: Remember to open the file in std::ios::binary mode!

优雅!


但以防万一您不熟悉 类 和上面示例中使用的某些功能。以下是一些链接:

std::getline https://www.geeksforgeeks.org/how-to-use-getline-in-c-when-there-are-black-lines-in-input/

std::istream::read http://www.cplusplus.com/reference/istream/istream/read/

std::ostream::write http://www.cplusplus.com/reference/ostream/ostream/write/

std::vector https://www.geeksforgeeks.org/vector-in-cpp-stl/

std::istream_iterator http://www.cplusplus.com/reference/iterator/istream_iterator/