简体   繁体   中英

How can I write a program that reads in strings and integers into two separate dynamic arrays?

I'm writing a program for a class that gets input from a .txt file and puts it in separate arrays based on variable type.

Here's an example of an input file

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

So, I'm supposed to use the first line to get the amount of names (6 in this case) and the amount of exam scores (8) then use that to initialize two dynamic arrays. I believe I'm initializing the arrays correctly, I'm just having a hard time filling the arrays with the correct data from the input file. I've never read from a file before this semester, so I'm completely unfamiliar.

#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;
}

I'm getting an exception thrown (I think for trying to access something that doesn't exist) and I'm required to use arrays. I wouldn't be having issues if I could use vectors.

Edit: I've been asked to show the code where numNames and numExams is declared and it's near the top of main. Edit 2: Included all the libraries I used below


Thank you.

Basically your program is working. I compiled it and fixed some minor stuff.

But it is very important, that you release the memory that you allocate with new.

Please see here the fixed example:

#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;
}

If we put a little bit more effort in the software, then it could look like that:

#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;
}

And the last step is to go to the more modern C++ approach.

In C++ we do not use raw pointers for owned memory and we should also not use new. If at all we should use std::unique_ptr and std::make_unique . But, not for arrays.

For dynamic arrays, we will use always std::vector or similar. They fit nearly perfect to the requested task. And, it makes life easier.

Then, C++ is an object oriented language. We are using objects, consisting of data and functions that operate on the data.

So, I defined a class (struct) Student, where we will put the names and the scores. And since this is an object, it knows how to read and write its data. Outside the class, nobody cares about that.

We overwrite the inserter and extractor operator for this class and can then use instances of the class with the inserter and extractor operator in the same way as for standard data types. So, it is possible to write std::cout << student .

That makes further operations much more easy. And it does fit better to other algorithm s of C++.

Important notice. This approach does not need the first line in the input file. It is fully dynamic! Also the number of scores can be different from line to line.


Let's have a look on the extractor. It first reads a complete line and put this complete line in an istringstream object.

Next we define a std::vector with the name "part" and use its range constructor to fill it. The range constructor gets an iterator to the beginning of some range and the end of some range as parameter and copies the data from there into itself.

The begin iterator is the std:istream_iterator , in this case for a std::string . This ìterator will simply call the extractor operator ( >> ) for the given stream until all data is read. The end is marked with {}. That is the default-constructed std::istream_iterator and known as the end-of-stream iterator. Please see here .

Please note: We can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").

So, then, we have all sub strings in the std::vector with the name "part". At the end of this function, we simply copy the relevant parts to our class internal data.

For the scores, we convert the strings to double, and use the std::back_inserter to dynamically add the scores to our scores- vector .


The output, the inserter operator ( << ) is by far more simple. We simply copy the result to the stream. Here we take also advantage of the std::copy function from the standard algorithm library and the std::ostream:iterator . This iterator is also very helpful. It simply calls the inserter operator ( << ) for each given element.

So, after having defined the class, the complete program in main boils down to 2 major statements:

                // 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"));

With 2 lines of code, we can: 1. read the complete file and 2 write it to the desired output file. Please again note that even that could be optimized to one statement:

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

So, in essence, we are ending up with a one liner . . .

Please see the complete code example:

#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;
}

What a pity that you will most probably continue with your new solution . . .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM