如何在 C++ 中的 .txt 文件中查找值?

How to find values at .txt file in C++?

我需要从 .txt 文件中提取复数值并将其分配给 4 个数组(4 个复矩阵 3*3),然后执行 +、-、*、/、* (-0.8+1.6i) 操作它们之间。我需要用什么来查找和获取 txt 文件中的这些复杂值?

这是 .txt 文件的简短版本。

# file.txt 
# some text bla bla 123
# same
-2.3-14.6i,9.1
7.1+2.8i-7.1-11.7i
# bla bla 

现在我做了什么。

struct comp
    {
        double x, y;
    };

    comp arr[100];
    ifstream infile("C:\Users\Nick Leeker\Desktop\data.txt");

    if (!infile)    // Does file open correctly?
    {
        cout << "error -1\n\n";
        return -1;
    }
    else
    {
        cout << "file is open\n\n";
        return 1;
    }

首先,C++ 库提供了一个 std::complex,您可以使用它来代替 comp 结构。使用标准复杂类型将提供比 struct 更多的功能。但是,将您的 struct comp 用于学习目的并从您的文件中解析您需要的信息并没有错。

无论您使用提供的 std::complex 还是您自己的 struct comp,如果您想存储所有复数值以便它们可用,您将需要创建一个 std::vector 来保存所有值而不是 comp 的普通旧数组。 (让 STL 处理内存管理和存储——这比自己从头开始编写更不容易出错)

不要在代码中使用幻数硬编码文件名。 (这就是 main() 采用参数 int main (int argc, char **argv) 的原因。将您的文件名作为参数传递给 main() 或将文件名作为输入。(如果不是,提供默认文件名很好给定)要验证您是否为文件名提供了参数并声明您的 std::vector<comp> 并打开并验证您的文件是否打开,可以按以下方式完成:

...
struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }
    ...

要从文件中读取复数值,您需要一次将一行数据读入 arr。然后,您希望从 arr 创建一个 std::stringstream,这将允许您使用 ',' 定界符从字符串流中读取分隔包含复杂值的字符串。 (您不能使用 ',' 定界符从文件中读取,getline 不知道该行在哪里结束并通过忽略每行末尾的 '\n' 直接跳过对于下一个 ',')

所以你只需将这一行放在stringstream中,然后从stringstream中读取(stringstream中只有一行数据,所以没有机会读取到末尾)。例如:

    ...
    while (getline (infile, arr)) {     /* read each line */
        if (arr.at(0) == '#')           /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        ...

正如您使用上面的循环从文件中循环读取带有 getline 的行一样,您现在只需以类似的方式使用第二个循环从字符串流中读取由 ',' 分隔的字符串.在循环中,您需要获取当前字符串的长度,保留一个从字符串开头开始偏移的变量,以便与 substr 一起使用,以告诉 stod 从哪里开始读取下一个值,你会想要声明一个 pos (位置)值,由 std::stod 填充,告诉你在将行的当前部分转换为 double 时使用了多少个字符,所以您可以将其添加到您的偏移量以了解从哪里开始阅读下一个。参见 std::stof, std::stod, std::stold

您还需要声明一个 临时 struct comp 实例来填充转换为 double 的值,然后您可以将其添加到您的 vector 如果对两个 x, y 值的解析都成功:

        ...
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            ...

使用 std::stod 进行转换时,您必须使用 try {...} catch (exception) {...} 来捕获转换过程中的任何错误,以便您可以正确处理错误。阅读实部 (x) 后,您将使用转换中使用的字符数(由 pos 报告)更新 s 中的偏移量,以了解从哪里开始虚部的转换(如果没有虚部,只需将值设置为零),例如

            ...
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;
            ...

(注:既然你说明了虚数之间会有一个',',你可以简单地使用s而不是s.substr(off) 在将实部转换为双精度的调用中。如果 ',' 是可选的,则需要使用 substr 以允许使用 'i' 的定界符,然后检查是否下一个字符是 ',',这需要在第一次调用时使用 off == 1

剩下的就是将您填充的临时 struct comp 添加到向量中,例如

            ...
            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

基本上就是这样。您已填充 std::vector<comp> cmplx 将从文件中读取的所有复数值。您可以简单地输出它们来验证结果,例如

    ...
    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

综合起来你会得到:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }

    while (getline (infile, arr)) {     /* read each line */
        if (arr.at(0) == '#')           /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;

            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

示例Use/Output

使用文件 dat/cmplx.txt 中的数据,运行 程序和解析文件中的值将导致:

$ ./bin/parse_complex dat/cmplx.txt
results

(-2.3, -14.6i)
(9.1, 0i)
(7.1, 2.8i)
(-7.1, -11.7i)

检查一下,如果您还有其他问题,请告诉我。

编辑 - 实际输入文件的格式不同

查看 past.openSUSE.org 上的输入文件后,我看到需要调整的唯一变化是从检查评论的开头从第一列到行中的任何位置。我已将测试更新为:if (arr.find("#") != std::string::npos)

完整代码为:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }

    while (getline (infile, arr)) {     /* read each line */
            if (arr.find("#") != std::string::npos) /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;

            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

示例Use/Output

将文件从 paste.opensuse.org 复制并粘贴到文件 dat/complexfile.txt 中,我得到以下输出:

$ ./bin/parse_complex2 dat/complexfile.txt
results

(-2.3, -14.6i)
(9.1, -2.5i)
(7.1, 2.8i)
(14.5, 12.2i)
(2.6, -13.2i)
(11.7, 0.7i)
(14, 1.6i)
(4.1, -10i)
(-7.3, 3.9i)
(9.9, 4.9i)
(5.6, -1.2i)
(15, 7.4i)
(-11.7, -4.2i)
(-8.2, 5.8i)
(9, -12i)
(13.8, 3.7i)
(-10.1, -8.9i)
(13.9, 6.4i)
(-3.2, -11.2i)
(-10.8, 13.9i)
(0.3, 1i)
(-1.1, -13.9i)
(-0.2, 0.1i)
(-11.8, 0.6i)
(-7, -6.9i)
(-14, 2.5i)
(-12.6, 10.7i)
(-0.3, 0.5i)

如果您仍然遇到问题,我们将需要在字节级别比较文件内容。复制的输入文件的十六进制转储是 hexdump -Cv file

使用 VS 构建示例,运行 Windows

为了满足自己,我去了一个windows框,打开VS Developers Command Prompt(最新版本的VS17)并构建了程序,例如

C:\Users\david\Documents\dev\src-cpp>cl /W3 /wd4996 /Ox /EHsc /Foobj /Febin/parse_complex2 /Tp parse_complex2.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27034 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

parse_complex2.cpp
Microsoft (R) Incremental Linker Version 14.16.27034.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:bin/parse_complex2.exe
obj.obj

然后我转到 matrix 并单击 Download 并将文件以默认文件名保存在我构建可执行文件的 dat 目录下,例如

C:\Users\david\Documents\dev\src-cpp>dir dat
 Volume in drive C is Windows
 Volume Serial Number is ECC4-9A8B

 Directory of C:\Users\david\Documents\dev\src-cpp\dat

11/12/2019  05:06 PM    <DIR>          .
11/12/2019  05:06 PM    <DIR>          ..
11/12/2019  05:06 PM             4,916 40665451.txt
               1 File(s)          4,916 bytes
               2 Dir(s)  1,787,090,620,416 bytes free

然后我运行程序(我总是把可执行文件放在当前的bin目录下):

C:\Users\david\Documents\dev\src-cpp>bin\parse_complex2.exe dat665451.txt
results

(-2.3, -14.6i)
(9.1, -2.5i)
(7.1, 2.8i)
(14.5, 12.2i)
(2.6, -13.2i)
(11.7, 0.7i)
(14, 1.6i)
(4.1, -10i)
(-7.3, 3.9i)
(9.9, 4.9i)
(5.6, -1.2i)
(15, 7.4i)
(-11.7, -4.2i)
(-8.2, 5.8i)
(9, -12i)
(13.8, 3.7i)
(-10.1, -8.9i)
(13.9, 6.4i)
(-3.2, -11.2i)
(-10.8, 13.9i)
(0.3, 1i)
(-1.1, -13.9i)
(-0.2, 0.1i)
(-11.8, 0.6i)
(-7, -6.9i)
(-14, 2.5i)
(-12.6, 10.7i)
(-0.3, 0.5i)

一切都很好没问题。所以我真的很困惑为什么你在读取文件和获得正确结果时遇到问题。逐步尝试以上操作,如果您仍有问题,请告诉我。我们会解决的....