【问题标题】:C++ How to convert substring to intC ++如何将子字符串转换为int
【发布时间】:2019-01-09 15:11:10
【问题描述】:

打印到控制台的内容:

START(0,0)
GOAL(0,2)
ooox
xxoo
ooox

我希望能够将 STARTGOAL 点存储为子字符串中的整数,因为打印到控制台的内容是从外部文件中读取的。

我正在制作一个遍历二维网格的应用程序,其中x 表示被阻塞的路径,o 表示未阻塞。

我尝试使用.substr() 仅获取包含坐标对的字符串部分,并尝试使用std::stoi() 将String 类型转换为Int。

void Grid::loadFromFile(const std::string& filename){
     std::string line, startPoint, goalPoint;
     std::vector<std::string> grid;
     int startX, startY, goalX, goalY;

     std::ifstream file(filename);

     if (!file.is_open()) return;

     if (!std::getline(file, line)) return;

     if (line.compare(0, 5, "START") != 0) return;

     startPoint = line.substr(6,3);

     startX = std::stoi(startPoint.substr(1,1));
     startY = std::stoi(startPoint.substr(2,2));

     if (!std::getline(file, line)) return;

     if (line.compare(0, 4, "GOAL") != 0) return;

     goalPoint = line.substr(5,3);

     goalX = std::stoi(goalPoint.substr(1,1));
     goalY = std::stoi(goalPoint.substr(2,2));

     test = line.substr(7,1);

     while (std::getline(file, line)) {
          grid.push_back(line);
     }

     file.close();

     std::cout << "Start: " << startPoint << "\n";
     std::cout << "Goal: " << goalPoint << "\n";
     std::cout << "Start X: " << startX << "\n";
     std::cout << "Start Y: " << startY << "\n";
     std::cout << "Goal X: " << goalX << "\n";
     std::cout << "Goal Y: " << goalY << std::endl;
}

代码的预期结果是打印正确的startX/YgoalX/Y 值。

我得到的结果:

Start: 0,0
Goal: 0,2
Start X: 1
Start Y: 162010192
Goal X: 0
Goal Y: 1543563378

我不知道为什么我会得到返回的值,因为它们是随机的,并且对我如何获得这些值毫无意义。

【问题讨论】:

  • 那么当你拿出你的调试器并单步执行程序时,你传递给 stoi 的参数是什么?
  • 您检查过startPoint.substr(2,2) 的产量吗?
  • 您执行一个 getline(),然后检查“START”,然后假设下一行会以某种方式被读取。事实上,你需要第二个 getline()

标签: c++ c++11 type-conversion substring


【解决方案1】:

改变

startX = std::stoi(startPoint.substr(1,1));
startY = std::stoi(startPoint.substr(2,2));

startX = std::stoi(startPoint.substr(0,1));
startY = std::stoi(startPoint.substr(2,1));

http://www.cplusplus.com/reference/string/string/substr/

substr 有两个参数:子字符串的位置和长度。

这同样适用于您的目标点数。

【讨论】:

  • 我的建议是发布自信的答案并消除“我的猜测”之类的术语。如果您确信答案是正确的,您应该发布答案,并避免可能不正确的答案。如果您需要更多信息,可以尝试对所询问的问题发表评论。
【解决方案2】:

与其读取字符串,然后使用substr 提取您关心的部分,我更愿意更直接地指定预期的输入格式。在 C 语言中,我可能会这样做:

if (fscanf(infile, "START(%d,%d)", &startX, &startY) != 2)
   // error in reading start point

if (fscanf(infile, "GOAL(%d,%d)", &goalX, &goalY) != 2)
    // error reading goal point

在 C++ 中,至少在我看来,我们真正想要编写的代码是这样的:

input >> "START(">> startX >> "," >> startY >> ")\n";
input >> "GOAL(">> goalX >> "," >> goalY > ")\n";

所以,至少在我看来,问题是我们是否(如果可以的话)我们是否可以支持这一点。答案是肯定的,我们可以(可能很明显——做一个大的积累,然后说“对不起,但我们不能这样做”是毫无意义的)。

为了支持这一点,我们需要一个常量字符串的提取器。在最简单的情况下,它可能看起来像这样:

template <class charT>
std::basic_istream<charT> &operator>>(std::basic_istream<charT> &is, charT const *fmt) {
    while (*fmt) {
        if (*fmt != is.peek())
            is.setstate(std::ios_base::failbit);
        ++fmt;
        is.ignore(1);
    }
    return is;
}

因此,这基本上一次只查看输入流中的一个字符,并将其与格式字符串中的当前字符进行比较。如果它们匹配,它只是从流中提取该字符并转到下一个。如果它们不匹配,它会设置失败位来表示提取失败。

这有一个缺点:流有一个skipws 位,如果设置了它,我们希望在尝试做任何其他事情之前跳过空白。这可能应该适用于这里,所以像 infile &gt;&gt; "ignore" 这样的东西会匹配像“忽略”这样的输入。

为了解决这个问题,我们可以添加一个像这样的小循环:

while (std::isspace(is.peek()))
    is.ignore(1);

...在尝试匹配字符串之前。不过,它还有另一个缺点:它总是使用当前的全局locale--但是一个流可以充满它自己的locale,这应该适用于从该流中读取。因此,要正确跳过空白,我们应该检索流的locale,然后从该locale 中获取ctype facet,并使用它来确定某些内容是否为空白。不幸的是,执行此操作的代码比任何人都可能喜欢的要长且复杂:

template <class charT>
std::basic_istream<charT> &operator>>(std::basic_istream<charT> &is, charT const *fmt) {
    if (fmt == nullptr)
        return is;

    if (is.flags() & std::ios_base::skipws) {
        std::locale const &loc = is.getloc();
        if (std::has_facet<std::ctype<charT>>(loc)) {
            auto const &ct = std::use_facet<std::ctype<charT>>(loc);
            while (ct.is(std::ctype_base::blank, is.peek()))
                is.ignore(1);
        }
        else
            while (std::isspace(is.peek()))
                is.ignore(1);
    }

    while (*fmt) {
        if (*fmt != is.peek())
            is.setstate(std::ios_base::failbit);
        ++fmt;
        is.ignore(1);
    }
    return is;
}

至少目前,我已经写了这个,所以它检索流的locale,并使用它的ctype facet(如果有的话),但如果它有一个locale 没有ctype facet(这是至少在理论上是可能的)它回退到使用std::isspace 来决定某些东西是否是空白。可能有争论的余地,那就是在这一点上失败会更好,但我会把这个问题留到另一天。

完成后,我们可以按照自己的意愿阅读内容:

int main() { 
    std::istringstream b("START(0, 0)\nGOAL(1,2)");

    int startX, startY;
    b >> "START(" >> startX >> "," >> startY >> ")";
    std::cout << "start_x: " << startX << ", start_y: " << startY << "\n";

    int goalX, goalY;
    b >> "GOAL(" >> goalX >> "," >> goalY >> ")";
    std::cout << "goal_x: " << goalX << ", goal_y: " << goalY << "\n";
}

请注意,这里的行为与scanf 和公司的行为仍然略有不同。特别是,这将跳过您指定的格式字符串开头之前的空格(如果设置了skipws),然后尝试匹配您按字面传递的字符串中的每个字符。

相比之下,scanf 和 company 将格式字符串中的任何空白视为指令,以跳过输入中所有连续空白的运行。如果您愿意,您(当然)可以将其更改为像 scanf 和公司一样行事 - 但这种行为似乎让很多人感到惊讶,所以我认为它可能会更好。

【讨论】:

    猜你喜欢
    • 2016-03-07
    • 2021-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    • 2010-09-17
    相关资源
    最近更新 更多