从输入文件中获取字符串直到整数?C++

Get string from input file until integer? C++

本文关键字:整数 C++ 字符串 输入 文件 获取      更新时间:2023-10-16

我的一个输入文件,看起来像这样:

Stroustrup, Bjarne  8   8   -1  -1  -1
Lovelace, Ada   1   60  14  43  -1
von Neumann, Jon    77  48  65  -1  -1
Wirth, Niklaus  51  59  -1  -1  -1
Wozniak, Steve  81  -1  -1  -1  -1
Babbage, Charles    31  92  -1  -1  -1
Hopper, Grace   76  -1  -1  -1  -1
Bird, Tweety    -99 -99 -99 -99 -99
Sylvester           77  39  -1  -1  -1

我当前的程序使用流式传输数据

infile >> lastName >> firstName >> ...

不幸的是,这只适用于其他输入文件,因为每一行实际上都有姓氏和名字。这里,由于第三行中有两个部分的姓氏,而最后一行中只有名字,所以其余数据无法流式传输。有没有什么方法可以从一行的开头抓取一个字符串,直到它达到一个整数?

在解析输入文件时,您几乎永远不会得到直接使用原始流来用值填充变量的解决方案。输入格式可能不同,可能出现错误。。。在这种情况下,更好的方法是逐行读取输入并分别处理每一行。在处理每一行时,您可以构建一个临时istringstream,用于从中读取单词,并检查单词是否可以转换为数字(如果0不是有效值,请使用std::atoi):

std::string line;
while (std::getline(infile,line))
{
    if (line.empty()) continue;
    std::istringstream is(line);
    std::string word;
    while (is >> word)
    {
        int val = std::atoi(word);
        if (val)
        {
            // TODO: number
        }
        else
        {
            // word
        }
    }
}

或者,您可以考虑使用std::isdigit来检查单词的第一个字符是否为数字:

if (std::isdigit(word[0])) ...

您可以控制输入文件的格式吗?鉴于您既可以选择由两部分组成的名称(例如"von Neumann"),也可以选择由一个名称(例如,不是第一个和最后一个,例如"Sylvester"),解析起来会变得不必要的困难。若你们可以引用名称,比如"von Neumann", Jon,那个么事情可能会容易得多。

一种方法可能包括:

#define DELIM 'c'
ostringstream namestream;
infile.get(namestream, DELIM);
string name = namestream.str();

这将从中野获得字符串,直到到达字符DELIM。查看您的文件,它看起来像是名称部分和名字/姓氏由空格或逗号后跟空格分隔,其中名称末尾和第一个数字之间的间隙可能是一个制表符。如果是这种情况,您可以使用制表符('t')作为delimeter字符。

不幸的是,这种方法只支持将DELIM作为单个字符,而不是一组字符。因此,您将无法读取第一个数字(如果您想支持负数,则无法读取"digiit或'-'")。

下面是一个完整的示例程序:

#include <iostream>
#include <string>
#include <locale>
#include <fstream>
template<
    class charT,
    class iter_type,
    class string_iterator_type
>
void basic_get_name(iter_type beg, iter_type end, std::ios_base& str,
                    string_iterator_type it1, string_iterator_type it2)
{
    auto ctypeFacet = &std::use_facet<std::ctype<charT>>(str.getloc());
    typedef std::ctype_base base_type;
    while (beg != end)
    {
        if (ctypeFacet->is(base_type::alpha | base_type::punct, *beg))
            *it1++ = *beg;
        else if (ctypeFacet->is(base_type::space, *beg))
        {
            if (ctypeFacet->is(base_type::alpha, *++beg))
            {
                while (ctypeFacet->is(base_type::alpha, *beg) && (beg != end))
                    *it2++ = *beg++;
                break;
            }
            break;
        }
        ++beg;
    }
}
template<class charT>
void get_name(std::basic_istream<charT>& is, std::basic_string<charT>& first,
                                             std::basic_string<charT>& last)
{
     typedef std::istreambuf_iterator<charT> iter_type;
     basic_get_name<charT>(iter_type{is}, iter_type{},
                           is, std::back_inserter(first),
                               std::back_inserter(last));
}

以下是您的称呼:

int main()
{
    std::string first, last;
    get_name(infile, first, last);
    std::cout << first << last;
}

您甚至可以创建自己的同时具有名字字符串和姓氏字符串的类,并创建一个用于将名称提取到这些数据成员中的方面。

并且get_name应该返回流,以便可以对流状态进行检查。目前,这段代码并没有做到这一点,但它可以通过类型为ios_base::iostate的参数来实现,并在basic_get_name中向其添加流状态错误位。