0

I am doing a textbook exercise that requires scoring student tests.

The input file looks like:

TTFTFTTTFTFTFFTTFTTF
ABC54102 T FTFTFTTTFTTFTTF TF
DEF56278 TTFTFTTTFTFTFFTTFTTF
ABC42366 TTFTFTTTFTFTFFTTF
ABC42586 TTTTFTTT TFTFFFTF

The first line is the answer key for a test, and the following lines are student IDs and their responses, with blanks as an unanswered question.

The very first thing I need to do is read the first line so that I have something to compare the responses to. My first thought is to use a character array, but I can't seem to get it right. The first method I tried is:

#include <iostream>
#include <fstream>
#include <cstring>
#include <string>

const int NUM_QUESTIONS = 20;

using namespace std;

int main() {
    // Vars
    char answers[NUM_QUESTIONS];
    ifstream infile;

    // Open File
    infile.open("Ch8_Ex6Data.txt");

    infile.getline(answers, NUM_QUESTIONS);
    cout << answers << endl;
    infile.getline(answers, NUM_QUESTIONS);
    cout << answers << endl;

    return 0;
}

Which outputs

TTFTFTTTFTFTFFTTFTT


This is the all of the answers, but I noticed I couldn't do this twice to get to the next line. The second getline() sees an empty string.

The second method I tried was:

#include <iostream>
#include <fstream>
#include <cstring>
#include <string>

const int NUM_QUESTIONS = 20;

using namespace std;

int main() {
    // Vars
    char answers[NUM_QUESTIONS];
    ifstream infile;

    // Open File
    infile.open("Ch8_Ex6Data.txt");

    // Read first line into character array
    infile.getline(answers, '\n');
    cout << answers;

    return 0;
}

Which outputs:

TTFTFTTTF

Which clearly isn't the whole line.

What should I do from here?

5
  • 1
    getline reads NUM_QUESTIONS chars and keeps \n, an empty line for next getline, call it with a single std::string argument. Commented Jun 8, 2023 at 0:25
  • 1
    infile.getline(answers, '\n'); is invalid, that overload is for std::string, but you actually call it with the array infile.getline(answers, 10);, thus you get 10 chars. Commented Jun 8, 2023 at 0:28
  • 5
    In C++ you should steer towards using std::string if only to avoid the endless misery that is C strings. Commented Jun 8, 2023 at 0:36
  • Where you should go from here is the next chapter in your textbook that will, hopefully, explain what std::string is and how it works. It'll make this program much, much easier. Commented Jun 8, 2023 at 1:31
  • I have to ask, but how old is your textbook? Because std::string and std::vector have been around since C++98 and any decent book should cover them by now. Commented Jun 8, 2023 at 4:33

4 Answers 4

2

You can use the std::ifstream and std::getline() for this.

First, create a new input file stream and initialize with the file:

std::ifstream file("filename");

Then, get the line: (The getline() function will read all until a newline character)

std::string s;
std::getline(file, s);

Then, if you need it as a character array, you can use std::string.c_str() to convert into C style string.

Sign up to request clarification or add additional context in comments.

Comments

0

Just with a quick look at yuour code, it looks like you are using a 20 character array to store

TTFTFTTTFTFTFFTTFTTF  

which is exactly 20 characters

getline function will read a string which should always end with a null or \n character, so to read your string of 20 characters yours array size should actually 21. Try changing to

const int NUM_QUESTIONS = 20;

Comments

0

This is the beginning on how to read a file. In practice you would need to add a lot more error handling though (never trust your programs input).

Naming of functions and variables is very important to so you can read the final code like a book (or at least a recipe)

Building blocks : std::string, std::vector, range based for.

Also not loading is done from a generic stream class not a filestream. This will make your code more reusable (it can load from any stream now, also those in memory).

#include <iostream>
#include <sstream> 
//#include <cstring> not needed
#include <string>
#include <vector>

//const int NUM_QUESTIONS = 20;
//not needed with vector you can adapt to the input size at runtime

// using namespace std; NO Unlearn this.

// then model your input
struct answer_t
{
    std::string student_id;
    std::string input;
};

struct exam_data_t
{
    std::string correct_answers;
    std::vector<answer_t> student_answers;
};


// make functions
exam_data_t load_exam_data(std::istream& stream)
{
    exam_data_t answers;
    std::getline(stream, answers.correct_answers);

    // toda add more input validation, answer text lengths 
    // should be equal to length of correct_answers etc...

    std::string line;
    while (std::getline(stream, line))
    {
        answer_t answer;

        // simple split line based on assumption that
        // input is correct (usually not true in practice!)

        answer.student_id = line.substr(0ul, 8ul);
        answer.input = line.substr(10ul);
        answers.student_answers.push_back(answer);
    }

    return answers;
}


int main() 
{
    // simulate input file
    std::istringstream input_file
    {
        "TTFTFTTTFTFTFFTTFTTF\n"
        "ABC54102 T FTFTFTTTFTTFTTF TF\n"
        "DEF56278 TTFTFTTTFTFTFFTTFTTF\n"
        "ABC42366 TTFTFTTTFTFTFFTTF\n"
        "ABC42586 TTTTFTTT TFTFFFTF\n"
    };

    //std::ifstream input_file{ "Ch8_Ex6Data.txt" };
    auto exam_data = load_exam_data(input_file);

    std::cout << "correct answers : " << exam_data.correct_answers << "\n";

    for (const auto& answer : exam_data.student_answers)
    {
        std::cout << "Student : " << answer.student_id << ", answer = " << answer.input << "\n";
    }

    return 0;
}

Comments

0

std::istream::getline ensures the extracted data is terminated by a 0 char. This makes extracting 20 chars from the stream given a buffer of size 20 to write to impossible. (You may realize that the F at the end of the line is missing.)

You've got several options here:

  1. Increase the buffer size by 1
  2. Use read instead of getline. I only recommend doing this, if the exact number of chars to extract is known or you've validating that the input read contains only 'T' and 'F' anyways.
  3. Use getline, ignore the fail bit being set, and then extract another char using get. (Not recommended, since this makes for code that isn't as easy to read as the other options.)

The following code demonstrates all of those options:

constexpr size_t NumQuestions = 20;

template<class F>
void RunExample(char const* functionName, F&& readLine)
{
    std::istringstream in("TTFTFTTTFTFTFFTTFTTF\nFFTFTFFFTFTFTTFFTFFT");
    std::cout << "Running " << functionName << '\n';
    readLine(in, std::cout);
    readLine(in, std::cout);
}

int main() {
    RunExample("getline(increased buffer size)", [](std::istream& in, std::ostream& out) // 1.
        {
            char buffer[NumQuestions + 1];

            in.getline(buffer, NumQuestions + 1, '\n');
            assert(in.gcount() >= NumQuestions);

            out << std::string_view(buffer, NumQuestions) << '\n';
        });
    RunExample("read+ignore", [](std::istream& in, std::ostream& out) // 2.
        {
            char buffer[NumQuestions];
            in.read(buffer, NumQuestions);
            in.ignore(1, '\n');

            out << std::string_view(buffer, NumQuestions) << '\n';
        });
    RunExample("getline+get+ignore", [](std::istream& in, std::ostream& out) // 3.
        {
            char buffer[NumQuestions];

            in.getline(buffer, NumQuestions, '\n');
            assert(in.gcount() == (NumQuestions - 1));
            assert(!in.bad());
            in.clear(); // remove fail bit set since the delimiter is not encountered before exhausting the buffer storage
            auto const lastChar = in.get();
            assert(lastChar != '\n');
            assert(lastChar != std::istream::traits_type::eof());
            buffer[NumQuestions - 1] = lastChar;
            in.ignore(1, '\n'); // skip newline after the input just extracted, if present

            out << std::string_view(buffer, NumQuestions) << '\n';
        });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.