Не могу загрузить правильную информацию из файла - PullRequest
1 голос
/ 16 февраля 2020

проблема с этим битом кода в том, что он неправильно читает файл .txt, я предоставил изображение файла .txt, а также предоставил текущий вывод, который он мне дает. Любая помощь будет приветствоваться.

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

Основное просто вызывает эти функции

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

Это то, что находится в файле .txt enter image description here

Это токовый выход, который у меня есть. Я перепробовал множество вещей, но я не могу заставить его работать. Это токовый выход, который я получаю.

enter image description here

1 Ответ

1 голос
/ 16 февраля 2020

Продолжая мой комментарий выше, учитывая, что отображаемый вами входной файл является TEXT-файлом, вы не хотите читать как ios::binary. Почему? При чтении двоичного ввода все символы форматирования текста не имеют особого значения. Вы просто читаете байты данных, а '\n' (значение: 0xa) - это просто еще один байт в потоке. При чтении текста вы хотите использовать символы форматирования в текстовом файле, чтобы сообщать вам, когда вы прочитали строку или слово.

Далее, как прокомментировал @ sheff , если бы вы если вы читаете в двоичном формате, вам заранее нужно знать, сколько байтов вы прочитаете в username или password и где находится verfID int в потоке. Ссылка, которую он предоставил, дает хорошее объяснение процесса C ++ FAQ: Сериализация и десериализация . Для записи двоичных данных, особенно когда данные находятся в struct, если вы не сериализуете, нет никакой гарантии переносимости между компиляторами из-за того, что биты заполнения могут быть вставлены в структуру.

Итак, если вы не у вас есть требование читать и писать в двоичном формате, вам лучше читать текстовый файл в виде текста.

Вы можете значительно упростить чтение и вывод данных вашего ученика, перегружая << и >> операторы для чтения данных студентов за один раз из вашего входного потока в виде текста. Например, чтобы перегрузить оператор << для считывания student_t данных, вы можете просто добавить функцию-член в свой класс:

    /* 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 */
    }

Преимущество использования перегруженных операторов не только уменьшает пользовательские функции ввода, которые нужно написать, но значительно уменьшит ваш main(). Например:

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 */
}

Чтобы соединить части вашего класса, избегайте использования основных типов , таких как char[CONST], и вместо того, что предоставляет STL, например std :: строка , std :: vector (для вашей коллекции student_t вместо простого старого массива struct), et c. Для вашего класса у вас будет один дополнительный контейнер, чтобы вызвать уникальный verfID. Вы можете написать функцию, чтобы сканировать все собранные student_t каждый раз перед вставкой нового студента, или вы можете использовать std :: unordered_set , чтобы сделать это для вас гораздо более эффективным способом.

Таким образом, при использовании контейнеров STL вам просто понадобится std::vector<student_t> для хранения информации о вашем ученике (вместо массива), и вы будете использовать std::unordered_set<int> для ha sh вашего verfID и применять уникальность , Ваш класс private: членов данных может выглядеть примерно так:

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: членов вы можете использовать конструктор, который принимает имя файла для чтения в качестве аргумента, и тогда вам потребуется только одна вспомогательная функция в дополнение к перегруженным операторам << и >>. Вспомогательная функция просто зацикливает ввод данных с помощью перегруженного оператора >>, пока вы не достигнете конца файла.

Ваши конструкторы на самом деле не должны быть больше, чем:

  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);
    }
    ...

Остальные детали обрабатываются перегруженными операторами << и >>. Начиная с перегрузки <<, вам действительно не нужно делать больше, чем l oop для всех учащихся и выводить данные в формате, который вам нравится, например,

    /* 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;
    }

( примечание: ключевое слово friend, используемое в объявлении в классе, если вы определили функцию в другом месте, вы опустите friend перед определением)

Ваша перегрузка >> Здесь большая часть работы происходит, хотя лог c прост. Вы объявляете временную student_t для чтения значений из потока. Если это удастся, вы сделаете быстрый поиск в вашем unordered_set, чтобы увидеть, присутствует ли verfID. Если это не так, вы добавляете verfID к вашему unordered_set и добавляете временный student_t к вашему вектору, и все готово. Если verfID является дубликатом, вы можете выдать предупреждение или ошибку, например

     /* 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 */
    }

В целом, в коротком примере (который просто добавляет заголовки и закрывает класс для информации выше), вы получите:

#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 */
}

Пример входного файла

Использование указанного выше входного файла в качестве TEXT-файла:

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

Пример Использование / Вывод

Запуск программы и предоставление входного файла в качестве Первый аргумент приведет к:

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

Username: Jamie
Password: abc1
Verf ID : 2

Если вам нужно добавить больше учеников, запрашивая у пользователя информацию, то все, что потребуется:

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

(попробуйте, и попробуйте добавить с дубликатом verfID ...)

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы. Использование контейнеров, которые предоставляет STL, безусловно, является лучшим подходом, чем попытка изобретать колесо самостоятельно (таким образом вы устраняете множество ошибок ...)

...