如何编写一个程序,将字符串和整数读入两个不同的动态数组中?

4

我将为一门课程编写一个程序,该程序从一个 .txt 文件中获取输入,并根据变量类型将其放入不同的数组中。

以下是一个输入文件的示例:

6 8
Cody Coder  84 100 100 70 100 80 100 65
Harry Hardware  77 68 65 100 96 100 86 100
Harry Potter  100 100 95 91 100 70 71 72
Mad Mulligun  88 96 100 90 93 100 100 100
George Washington  100 72 100 76 82 71 82 98
Abraham Lincoln  93 88 100 100 99 77 76 93

所以,我应该使用第一行来获取名称的数量(在这种情况下为6)和考试分数的数量(8),然后使用它来初始化两个动态数组。我相信我正在正确地初始化数组,只是很难从输入文件中填充正确的数据到数组中。在这学期之前,我从未阅读过文件,所以我完全不熟悉。

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

using namespace std;

#ifdef _MSC_VER
#define _CRTDBG_MAP_ALLOC  
#include <crtdbg.h>
#define VS_MEM_CHECK _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#else
#define VS_MEM_CHECK
#endif

int main(int argc, char* argv[]) {
    VS_MEM_CHECK
    // Variables
    int numNames = 32;
    int numExams = 32;

    // Part 1: Read scores from the input file
    cout << "Input file: " << argv[1] << endl;
    ifstream in(argv[1]);
    if (!in) {                                              // fail case
        cerr << "Unable to open " << argv[1] << " for input";
        return 2;
    }
    cout << "Output file: " << argv[2] << endl;
    ofstream out(argv[2]);
    if (!out) {                                             // fail case
        in.close();
        cerr << "Unable to open " << argv[2] << " for output";
        return 3;
    }

    if (in.is_open()) {
        in >> numNames >> numExams;
        // dynamic string array
        string* fullNames = new string[numNames];                   // new: initialize names array
        // 2-d, dynamic double array
        double** scores = new double* [numNames];                   // new: initialize rows of scores array
        for (int i = 0; i < numNames; ++i) {
            scores[i] = new double[numExams];                       // new: initialize columns of scores array
        }

        string currLine;

        for (int i = 0; i < numNames; ++i) {
            string firstName, lastName;

            in >> firstName >> lastName;
            for (int j = 0; j < numExams; ++j) {
                in >> scores[i][j];
            }
            fullNames[i] = firstName + " " + lastName;
        }

    }
    else {
        return -2;
    }

    if (out.is_open()) {

    }
    else {
        return -3;
    }
    return 0;
}

我遇到了一个异常(我认为是因为尝试访问不存在的东西),并且需要使用数组。如果可以使用向量,我就不会遇到这些问题。

编辑:我被要求展示numNames和numExams声明的代码,它们位于main函数的顶部。 编辑2:下面包含我使用的所有库。



谢谢你。

感谢您。


1
小测验:你的程序中哪个部分确保恰好读取和处理 numNames 行,不多也不少?奖励问题:你的程序中哪个部分确保恰好处理每行的 numExams 个分数? - Sam Varshavchik
@TedLyngmo 我不确定需要添加什么来确保可重现性,但我已经添加了声明numNames和numExams的部分。 - burningyeti
@burningyeti 假装你是那个帮忙的人。你能复制你的代码并直接编译吗?或者作为一个热心助人的人,你需要添加什么吗? - Ted Lyngmo
2
@burningyeti 这是您的代码原样。消除那些编译器错误,当您完成后,请更新帖子中的代码。至于您创建二维数组的方式,我认为这种方法:double** scores = new double* [numNames]; for (int i = 0; i < numNames; ++i) {scores[i] = new double[numExams];} 即使您无法使用向量,也应该被禁止。这是最糟糕的做法之一。 - PaulMcKenzie
@PaulMcKenzie 谢谢你的帮助,但我完全不知所措。我知道的唯一解决编译器错误的方法就是删除整个代码块。我会更新我的main.cpp文件,但我觉得这件事情完了。 - burningyeti
显示剩余13条评论
1个回答

0

基本上你的程序是可以工作的。我已经编译并修复了一些小问题。

但是非常重要的是,你需要释放使用 new 分配的内存。

请参考以下修复后的示例:

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


int main(int argc, char* argv[]) {
    // Variables
    int numNames = 32;
    int numExams = 32;

    // Part 1: Read scores from the input file
    std::cout << "Input file: " << argv[1] << std::endl;
    std::ifstream in(argv[1]);
    if (!in) {                                              // fail case
        std::cerr << "Unable to open " << argv[1] << " for input";
        return 2;
    }
    std::cout << "Output file: " << argv[2] << std::endl;
    std::ofstream out(argv[2]);
    if (!out) {                                             // fail case
        in.close();
        std::cerr << "Unable to open " << argv[2] << " for output";
        return 3;
    }

    if (in.is_open()) {
        in >> numNames >> numExams;
        // dynamic string array
        std::string* fullNames = new std::string[numNames];                   // new: initialize names array
        // 2-d, dynamic double array
        double** scores = new double* [numNames];                   // new: initialize rows of scores array
        for (int i = 0; i < numNames; ++i) {
            scores[i] = new double[numExams];                       // new: initialize columns of scores array
        }

        std::string currLine;

        for (int i = 0; i < numNames; ++i) {
            std::string firstName, lastName;

            in >> firstName >> lastName;
            for (int j = 0; j < numExams; ++j) {
                in >> scores[i][j];
            }
            fullNames[i] = firstName + " " + lastName;
        }

        if (out.is_open()) {
            for (int i = 0; i < numNames; ++i) {
                out << fullNames[i] << ":";
                for (int j = 0; j < numExams; ++j) {
                    out << " " << scores[i][j];
                }
                out << "\n";
            }
        }

        // Release memory
        for (int i = 0; i < numNames; ++i) {
            delete[] scores[i];
        }
        delete[] scores;
        delete[] fullNames;

        if (!out.is_open()) {
            return -3;
        }
    }
    else {
        return -2;
    }
    return 0;
}

如果我们在软件方面再多花一点功夫,它就可以看起来像这样:
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>

// Read file with students and scores and copy write the read data to a new file
int main(int argc, char* argv[]) {

    // Check, if the program has been called with the expected numbers of parameters
    if (3 == argc) {
        // Give feddback to user about filenames:
        std::cout << "\nProgram will work with files: '" << argv[1] << "' and '" << argv[2] << "'\n";

        // Try to open the input file and try to open the output file
        if (std::ifstream in(argv[1]); in) {
            if (std::ofstream out(argv[2]); out) {

                // All files are open. Read number of data. Check, if read worked
                if (size_t numNames{}, numExams{}; (in >> numNames >> numExams) && (numNames > 0U) && (numExams > 0U)) {

                    // We found plausible data Now, allocate memory
                    std::string* fullNames = new std::string[numNames];                   // new: initialize names array
                    // 2-d, dynamic double array
                    double** scores = new double* [numNames];                   // new: initialize rows of scores array
                    for (int i = 0; i < numNames; ++i) {
                        scores[i] = new double[numExams];                       // new: initialize columns of scores array
                    }

                    // Read data
                    for (size_t i = 0U; i < numNames; ++i) {
                        std::string firstName, lastName;
                        in >> firstName >> lastName;
                        for (size_t j = 0U; j < numExams; ++j) {
                            in >> scores[i][j];
                        }
                        fullNames[i] = firstName + " " + lastName;
                    }
                    // Write data
                    for (size_t i = 0U; i < numNames; ++i) {
                        out << fullNames[i] << ":";
                        for (size_t j = 0U; j < numExams; ++j) {
                            out << " " << scores[i][j];
                        }
                        out << "\n";
                    }

                    // Release memory
                    for (int i = 0; i < numNames; ++i) {
                        delete[] scores[i];
                    }
                    delete[] scores;
                    delete[] fullNames;
                }
            }
            else {
                std::cerr << "\nError: Could not open output file '" << argv[2] << "'\n";
            }
        }
        else {
            std::cerr << "\nError: Could not open input file '" << argv[1] << "'\n";
        }
    }
    else {
        std::cerr << "\nError: Please call program with 2 filenames for input data and output data\n\n";
    }
    return 0;
}

最后一步是采用更现代的C++方法。

在C++中,我们不使用原始指针来管理内存,也不应该使用new。如果必须使用,我们应该使用std::unique_ptrstd::make_unique。但是,对于数组不适用。

对于动态数组,我们将始终使用std::vector或类似的容器。它们几乎完美地适合所需的任务。而且,这样做会让生活更轻松。

此外,C++是一种面向对象的语言。我们使用由数据和操作数据的函数组成的对象。

因此,我定义了一个名为Student的类(结构体),其中包含姓名和分数。由于这是一个对象,它知道如何读取和写入其数据。在类外部,没有人关心这些。

我们重载了这个类的插入器和提取器运算符,然后可以像标准数据类型一样使用类的实例与插入器和提取器运算符。因此,可以编写std::cout << student

这使得进一步的操作更加容易。并且它更适合C++的其他algorithm

重要通知。此方法不需要输入文件中的第一行。它是完全动态的!另外,每行分数的数量可能会有所不同。


让我们来看一下提取器。它首先读取完整的一行,并将这一行放入一个istringstream对象中。

接下来,我们定义了一个名为“part”的std::vector,并使用其范围构造函数填充它。范围构造函数以某个范围的开头和结尾作为参数获取一个迭代器,并将数据复制到自身中。

开始的迭代器是std:istream_iterator,此处为std::string。这个迭代器将简单地调用给定流的提取运算符(>>),直到读取所有数据。结束标记为{}。那是默认构造的std::istream_iterator,也称为流结束迭代器。请参见here

请注意:我们可以在不带模板参数的情况下定义std::vector。编译器可以从给定的函数参数中推断出参数。这个特性被称为CTAD(“类模板参数推导”)。

因此,我们在名为“part”的std::vector中拥有所有子字符串。在此函数的末尾,我们只需将相关部分复制到我们类的内部数据中。

对于分数,我们将字符串转换为double,并使用std::back_inserter动态地将分数添加到我们的分数-vector中。


输出时,插入运算符(<<)要简单得多。我们只需将结果复制到流中即可。在这里,我们还利用了标准算法库中的std::copy函数和std::ostream:iterator。这个迭代器也非常有用。它只为每个给定的元素调用插入运算符(<<)。

因此,在定义了类之后,主程序中的完整程序仅包含两个重要语句:

                // Define Roster and read all students
                std::vector roster(std::istream_iterator<Student>(in), {});

                // Write complete roster to out file
                std::copy(roster.begin(), roster.end(), std::ostream_iterator<Student>(out, "\n"));

只需两行代码,我们就可以:1. 读取完整的文件并2. 将其写入所需的输出文件。请再次注意,即使这也可以优化为一条语句:

std::copy(std::istream_iterator<Student>(in), {}, std::ostream_iterator<Student>(out, "\n"));

因此,本质上我们得到了一个一行代码...

请查看完整的代码示例:

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

struct Student {
    // Data for one student
    std::string firstName{};
    std::string lastName{};
    std::vector <double>scores{};

    // Specific extractor operator
    friend std::istream& operator >> (std::istream& is, Student& st) {

        // Read one complete line from the source stream
        if (std::string line{}; std::getline(is, line)) {

            // Put the line in a istringstream
            std::istringstream iss(line);

            // Split the line and get all substrings into a vector
            std::vector part(std::istream_iterator<std::string>(iss), {});

            // Copy the string parts to our local data
            if (part.size() > 1) {
                // Copy name
                st.firstName = part[0]; st.lastName = part[1];

                // Copy scores
                st.scores.clear();
                std::transform(std::next(part.begin(), 2), part.end(), std::back_inserter(st.scores), [](const std::string& s) {return std::stod(s); });
            }
        }
        return is;
    }

    friend std::ostream& operator << (std::ostream& os, const Student& st) {
        os << st.firstName << " " << st.lastName << ": ";
        std::copy(st.scores.begin(), st.scores.end(), std::ostream_iterator<double>(os, " "));
        return os;
    }


};

// Read file with students and scores and copy write the read data to a new file
int main(int argc, char* argv[]) {

    // Check, if the program has been called with the expected numbers of parameters
    if (3 == argc) {
        // Try to open the input file and try to open the output file
        if (std::ifstream in(argv[1]); in) {
            if (std::ofstream out(argv[2]); out) {


                // Define Roster and read all students
                //std::vector roster(std::istream_iterator<Student>(in), {});

                // Write complete roster to out file
                //std::copy(roster.begin(), roster.end(), std::ostream_iterator<Student>(out, "\n"));

                // Copy complete input to output.
                std::copy(std::istream_iterator<Student>(in), {}, std::ostream_iterator<Student>(out, "\n"));


            } else {    std::cerr << "\nError: Could not open output file '" << argv[2] << "'\n";}
        } else {    std::cerr << "\nError: Could not open input file '" << argv[1] << "'\n"; }
    } else {    std::cerr << "\nError: Please call program with 2 filenames for input data and output data\n\n";}

    return 0;
}

真遗憾你很可能会继续使用你的方案...


正如主要注释中提到的,使用for循环分配内存并不是一个好的方法。如果确实不能使用std::vector,那么使用new[]的方式来分配2D数组是一个更好的选择。 - PaulMcKenzie
@Paul:我的意图并不是推荐使用新的或原始指针。相反,我的观点是:永远不要为拥有的内存使用原始指针,也永远不要使用new。如果使用new, 只能与std::unique_ptr一起使用。在我的帖子中,我首先修复了原始代码,然后添加了一些改进,并展示了我推荐的C++解决方案。问题是,像OP这样的新手即使有很好的解释,也无法理解更复杂的C++。请原谅我,我不会使用链接包装器来处理二维数组。但是,每个人都可以自行决定。 - A M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接