为未知 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_names
为 std::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_list
是 Person
的 std::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/
我正在尝试读取包含个人信息的文件。每行包含一个人的数据,假设它看起来像这样:
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_names
为 std::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_list
是 Person
的 std::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