Can't load correct information from file

瘦欲@ 提交于 2021-02-05 08:18:52

问题


the problem with this bit of code is that it is not reading the .txt file correctly, I have provided a image of the .txt file while also providing the current output it is giving me. Any help would be welcomed.

#include <fstream>
#include <iostream>

using namespace std;
const int MAX_CHARS = 10;
const int MAX_STUDENTS = 1;
class File
{
public:
void openFile()
{
    ifstream input_file("UserPass.txt", ios::binary);
    if (input_file.fail())
    {
        cout << "Could not open file" << endl;
    }
    else
    {
        if (!input_file.read((char*)&studLoaded, sizeof(studLoaded)))
        {
            cout << "Could not read file" << endl;
        }
        else
        {
            streamsize bytesRead = input_file.gcount();
            if (bytesRead != sizeof(studLoaded))
            {
                cout << "Could not read expected number of bytes" << endl;
            }
            else
            {
                input_file.read((char*)&studLoaded, sizeof(studLoaded));
                input_file.close();
            }


        }
    }
};

void displayFile()
{
    for (size_t i = 0; i < MAX_STUDENTS; i++)
    {
        cout << "Username: " << studLoaded[i].username << endl;
        cout << "Password: " << studLoaded[i].password << endl;
        cout << "Verf ID:" << studLoaded[i].verfID << endl;
    }
}

private:

typedef struct
{
    char username[MAX_CHARS];
    char password[MAX_CHARS];
    int verfID;

}student_t;

student_t studLoaded[MAX_STUDENTS];
};

The main is just calling these functions

File f;
f.openFile();
f.displayFile(); 

This is what is in the .txt file

This is the current output I have. i have tried numerous of things but I can't seem to get it to work. This is the current output I am getting.


回答1:


Continuing from my comment above, given the input file you show is a TEXT file, you don't want to read as ios::binary. Why? When reading binary input, all text formatting character have no special meaning. You are simply reading bytes of data, and '\n' (value: 0xa) is just another byte in the stream. When reading text, you want to use the formatting characters in the text file to tell you when you have read a line or a word.

Further, as commented by @sheff were you to read in binary, it is up to you to know beforehand how many bytes you will read into username or password and where the verfID int is located in the stream. The link he provided gives a good explanation of the process C++ FAQ: Serialization and Unserialization. For writing binary data, especially when the data is in a struct, unless you serialize, there is no guarantee of portability between compilers due to where padding bits may be inserted into the struct.

So, unless you have a requirement to read and write in binary, you are better served reading a text file as text.

You can make the reading and outputting of your student data much simpler by overloading the << and >> operators to read a students worth of data at a time from your input stream as text. For example to overload the << operator to read a student_t worh of data, you can simply add a member function to your class:

    /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* handle storage of s here */
        }

        return is;  /* return stream state */
    }

The benefit of using the overloaded operators not only reduces the custom input functions to have to write, but will reduce your main() considerably. For example:

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

    if (argc < 2) { /* verify at least 1 argument for filename */
        std::cerr << "error: password filename required.\n";
        return 1;
    }

    passfile pf (argv[1]);      /* declare instance of class, with filename */
    std::cout << pf;            /* output all student data */
}

To put the pieces of your class together, avoid using fundamental types such as char[CONST] and instead what the STL provides such as std::string, std::vector (for your collection of student_t instead of a plain-old array of struct), etc. For your class, you will one additional container to force a unique verfID. You can write a function yourself to scan over all the collected student_t each time before insertion of a new student, or you can use std::unordered_set to do it for you in a much more efficient way.

So using the STL containers, you would simply need a std::vector<student_t> for your student information storage (instead of an array) and you would use std::unordered_set<int> to hash your verfID and enforce uniqueness. Your class private: data members could be something like:

class passfile {

    struct student_t {
        std::string username {}, password {};   /* user std:string istead */
        int verfID;
    };

    std::unordered_set<int> verfID;         /* require unique verfID before add */
    std::vector<student_t> students {};     /* use vector of struct for storage */
    ...

For your public: members, you can use a constructor that takes the filename to read from as an argument, and then you would need only one helper-function in addition to the overloaded << and >> operators. The helper-function just loops taking input with the overloaded >> operator until you reach the end of file.

Your constructors really don't need to be any more than:

  public:

    passfile() {}
    passfile (std::string fname) { readpwfile (fname); }
    ...

Your helper-function to repeatedly use the >> operator can be:

    void readpwfile (std::string fname)     /* read all students from filename */
    {
        std::ifstream f (fname);
        do
            f >> *this;                     /* use overloaded >> for read */
        while (f);
    }
    ...

The remaining details are handled by the overloaded << and >> operators. Starting with the overload of <<, you really don't need it to do any more than loop over all students and output the data in the format you like, e.g.

    /* overload << to output all student data */
    friend std::ostream& operator << (std::ostream& os, const passfile& pf)
    {
        for (auto s : pf.students)
            os  << "Username: " << s.username << '\n' 
                << "Password: " << s.password << '\n' 
                << "Verf ID : " << s.verfID << "\n\n";

        return os;
    }

(note: the friend keyword in used in the declaration within the class, if you defined the function elsewhere, you would omit friend before the definition)

Your overload of >> is where most of the work takes place, though the logic is simple. You declare a temporary student_t to read values into from the stream. If that succeeds, you do a quick lookup in your unordered_set to see if the verfID is already present. If it isn't you add the verfID to your unordered_set, and add your temporary student_t to your vector and your are done. If the verfID is a duplicate, you can issue a warning or error, e.g.

     /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* if verfID not already in verfID unordered_set */
            if (pf.verfID.find (s.verfID) == pf.verfID.end()) {
                pf.verfID.insert (s.verfID);    /* add verfID to unordered_set */
                pf.students.push_back (s);      /* add temp student to vector */
            }
            else    /* warn on duplicate verfID */
                std::cerr << "error: duplicate verfID " << s.verfID << ".\n";
        }

        return is;  /* return stream state */
    }

Putting it altogether in a short example (which basically just adds the headers and closes the class for the information above), you would have:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <utility>
#include <unordered_set>

class passfile {

    struct student_t {
        std::string username {}, password {};   /* user std:string istead */
        int verfID;
    };

    std::unordered_set<int> verfID;         /* require unique verfID before add */
    std::vector<student_t> students {};     /* use vector of struct for storage */

  public:

    passfile() {}
    passfile (std::string fname) { readpwfile (fname); }

    void readpwfile (std::string fname)     /* read all students from filename */
    {
        std::ifstream f (fname);
        do
            f >> *this;                     /* use overloaded >> for read */
        while (f);
    }

    /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* if verfID not already in verfID unordered_set */
            if (pf.verfID.find (s.verfID) == pf.verfID.end()) {
                pf.verfID.insert (s.verfID);    /* add verfID to unordered_set */
                pf.students.push_back (s);      /* add temp student to vector */
            }
            else    /* warn on duplicate verfID */
                std::cerr << "error: duplicate verfID " << s.verfID << ".\n";
        }

        return is;  /* return stream state */
    }

    /* overload << to output all student data */
    friend std::ostream& operator << (std::ostream& os, const passfile& pf)
    {
        for (auto s : pf.students)
            os  << "Username: " << s.username << '\n' 
                << "Password: " << s.password << '\n' 
                << "Verf ID : " << s.verfID << "\n\n";

        return os;
    }
};

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

    if (argc < 2) { /* verify at least 1 argument for filename */
        std::cerr << "error: password filename required.\n";
        return 1;
    }

    passfile pf (argv[1]);      /* declare instance of class, with filename */
    std::cout << pf;            /* output all student data */
}

Example Input File

Using your input file above as a TEXT file:

$ cat dat/userpass.txt
Adam
Pass121
1
Jamie
abc1
2

Example Use/Output

Running the program and providing your input file as the first argument would result in:

$ ./bin/passwdfile dat/userpass.txt
Username: Adam
Password: Pass121
Verf ID : 1

Username: Jamie
Password: abc1
Verf ID : 2

If you needed to add more students by prompting the user for information, then all that would be needed is:

    std::cout << "enter user pass verfID: ";
    std::cin >> pf;

(try it, and try adding with a duplicate verfID...)

Look things over and let me know if you have further questions. Using the containers the STL provides is by far the better approach to take rather than trying to reinvent the wheel on your own (you eliminate a lot of errors that way...)



来源:https://stackoverflow.com/questions/60245777/cant-load-correct-information-from-file

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!