-3

I am trying to read input from a file one by one instead of giving it from standard input.

What I have, which is currently working!

   for(int i = 0; i <CLASS_SIZE; i++)
    {
      for(int j = 0; j <10 ; j++)
        {
            scanf("%d", &grade);
            studentsInClass[i].setGrade(j,grade);
        }  
    }

current input from console is this:

91 92 85 58 87 75 89 97 79 65
88 72 81 94 90 61 72 75 68 77
75 49 87 79 65 64 62 51 44 70

I want this input to be read directly from a file! So, I tried reading it as a file stream, line by line and then tried to save that line into a vector of strings, so that I can extract the single number per line in an inner loop.

std::ifstream inputfile("input.txt");
std::string line;
std::vector<std::string> v;

for(int i = 0; i <CLASS_SIZE; i++)
{
  for(int j = 0; j <10 ; j++)
    {
        if(inputfile.is_open()){
          while( std::getline(inputfile, line) ){
                std::cout << line << '\n';
                std::istringstream iss(line);
                iss>>v;
                for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it) {
                  studentsInClass[i].setGrade(j, std::stoi(it, nullptr, 2));
             }
          }
        inputfile.close();
        }
    }
}

but getting following error! Because I don't know the right way to do it!

error: cannot bind ‘std::basic_istream<char>’ lvalue to ‘std::basic_istream<char>&&’
                     iss>>v;

and below error, because I am trying to convert the string to an integer as my program needs to consume as integer and again I am doing something wrong with the syntax.

error: no matching function for call to ‘stoi(std::vector<std::__cxx11::basic_string<char> >::iterator&, std::nullptr_t
, int)’
                       studentsInClass[i].setGrade(j, std::stoi(it, nullptr, 2));

can anyone help to fix this issue?

Anu
  • 3,198
  • 5
  • 28
  • 49
  • 1
    Are you aware of `fscanf()` ? – Sid S Jan 14 '19 at 01:02
  • @SidS, nope, if you could solve the problem using it, please add an answer, that would be a great help. – Anu Jan 14 '19 at 01:03
  • Just look it up - your existing `scanf()` code translates easily. The example [here](http://www.cplusplus.com/reference/cstdio/fscanf/) should get you started. – Sid S Jan 14 '19 at 01:07
  • Unlike Python, C++ doesn't have functions that will allow you to read a whole line of space-separated values into a vector all at once. You have to read the values in each line separately using a loop like `while (iss >> val) { /* use val */ }`. Also, the `while( std::getline(inputfile, line) )` loop will read lines one by one until it encounters the end of the file, which means that you will end up reading the entire file in the first iteration of your `for(int j = 0; j <10 ; j++)` loop. You need to keep track of `i` and `j` using separate variables and remove the two outer loops. – eesiraed Jan 14 '19 at 01:49

2 Answers2

1

1) Using scanf() without checking return code is a major problem. If the input does not match your expectations, you're running into undefined behaviour.

2) Any code that reads from console can read from file by simple use of input redirection. (./myprog < file.txt)

3) Why not first convert from scanf() to std::cin, and then from console to file input? One step at a time. (I still think you should leave it to the user to decide whether to input from console or redirect from file. More flexible.)

4) The equivalent to scanf( "%d", &grade ) is std::cin >> grade. No need for that getline() / istringstream stuff, just read one number at a time like you are doing in your currently working code.


As for your errors:

error: cannot bind ‘std::basic_istream<char>’ lvalue to ‘std::basic_istream<char>&&’
                     iss>>v;

You are trying to read from a stream into a vector (iss >> v). That is not a defined operation. You need to read the values one at a time.

error: no matching function for call to ‘stoi(std::vector<std::__cxx11::basic_string<char> >::iterator&, std::nullptr_t
, int)’
                       studentsInClass[i].setGrade(j, std::stoi(it, nullptr, 2));

Your it is an iterator, which needs to be dereferenced to give the value it is referencing (*it). Note what I said previously, though -- your v doesn't get filled as you wanted, see previous error.


Instead of:

 for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)

Use type deduction (the compiler does know the return type of v.begin()):

 for ( auto it = v.begin(); it != v.end(); ++it )

Even better, use a range-for:

 for ( auto & value : v )

for(int i = 0; i <CLASS_SIZE; i++)
{
  for(int j = 0; j <10 ; j++)
    {
        if(inputfile.is_open()){

If inputfile is not open, you can skip the whole loop. So test before the loop, not inside it.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • I don't want to read the file from the console! I mean I don't want to give it as an input `./myprogram.o – Anu Jan 14 '19 at 00:30
  • @anu: Yes, there is, your code is not wrong in that respect -- open an `ifstream` and read from that. However, your code has *various* problems, and I wanted to address as many as I could. – DevSolar Jan 14 '19 at 00:34
  • Thanks for addressing the issues, you suggested `You are trying to read from a stream into a vector (iss >> v). That is not a defined operation. You need to read the values one at a time.`, that's is where I am stuck! I know how to iterate, but don't know how to iterate on a `read line` can you suggest in those lines? Are you saying that don't read the input as line by line using getline() and while loop, if that's that case how to read in a simpler way?I get [this](https://stackoverflow.com/questions/2291802/is-there-a-c-iterator-that-can-iterate-over-a-file-line-by-line) post, is right way? – Anu Jan 14 '19 at 00:53
  • Also, can you point me to some source on the web that has explained you point `Using scanf() without checking return code is a major problem`? – Anu Jan 14 '19 at 06:15
  • 1
    @anu: I pointed out the direct equivalence of `scanf( "%d", &grade )` and `cin >> grade`. You know that `inputfile` is a stream, so you can use `inputfile >> grade` as well. Generally speaking, if a function call can fail, you need to check for that failure before you assume it succeeded. `scanf` returns a value. Find out what that value tells you, and act on it. – DevSolar Jan 14 '19 at 07:48
1

As a first approximation, I'd make the most minimal changes necessary to the code that makes you happy:

std::ifstream infile("filename");

for(int i = 0; i <CLASS_SIZE; i++)
{
  for(int j = 0; j <10 ; j++)
    {          
        // scanf("%d", &grade);
        infile >> grade;
        studentsInClass[i].setGrade(j,grade);
    }  
}

Given that you know the exact number of grades for each student, you gain little (if anything) from using getline. That would be useful primarily if the input was line-oriented. For example, consider an input file like this:

91 92 85 58 87 75 89 97 79 65 88 
72 81 94 90 61 72 75 68 77
75 49 87 79 65 64 62 51 44 70

As it stands, you still apparently want this read as 3 students with 10 grades apiece, so the last grade on the first line (the 88) belongs to the second student, not the first.

Using getline and then parsing each line individually would make sense if you wanted this input data interpreted as the first student having 11 grades, and the second student 9 grades (and the third 10 grades).


That covers the question you asked, but I'm still not particularly happy about the code. Personally, I'd prefer to write a student class that knew how to read its own data from a stream:

class student { 
    std::vector<int> grades;
public:

   // other stuff, of course.

   friend std::istream &operator>>(std::istream &is, student &s) {
       int grade;
       for (int i=0; i<10; i++) {
           is >> grade;
           s.grades.push_back(grade);
       }
       return is;
    }
};

Then when we want to read data for multiple students, we'd do something like this:

std::vector<students> the_class;
std::ifstream infile("grades.txt");

for (int i=0; i<CLASS_SIZE; i++) {
    student s;

    // use `operator>>` above to read all 10 grades for one student.
    infile >> s;
    the_class.push_back(s);
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111