Продолжая мой комментарий выше, учитывая, что отображаемый вами входной файл является 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, безусловно, является лучшим подходом, чем попытка изобретать колесо самостоятельно (таким образом вы устраняете множество ошибок ...)