继续我上面的评论,鉴于您显示的输入文件是 TEXT 文件,您不想读取为 ios::binary。为什么?读取二进制输入时,所有文本格式字符都没有特殊含义。您只是在读取数据字节,'\n'(值:0xa)只是流中的另一个字节。阅读文本时,您希望使用文本文件中的格式字符来告诉您何时阅读了一行或一个单词。
此外,正如@sheff 所评论的那样,如果您以二进制形式读取,您可以事先知道您将读入username 或password 的字节数以及verfID int 的位置流。他提供的链接很好地解释了C++ FAQ: Serialization and Unserialization 的过程。对于写入二进制数据,特别是当数据在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::string、std::vector(供您收集 @987654341 @ 而不是一个普通的结构数组)等。对于您的类,您将使用一个额外的容器来强制使用唯一的verfID。您可以自己编写一个函数,在每次插入新学生之前扫描所有收集的student_t,或者您可以使用std::unordered_set 以更有效的方式为您完成。
因此,使用 STL 容器,您只需要一个 std::vector<student_t> 来存储您的学生信息(而不是一个数组),并且您可以使用 std::unordered_set<int> 来散列您的 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);
}
...
其余细节由重载的<< 和>> 运算符处理。从<< 的重载开始,你真的不需要它做任何事情,只需要遍历所有学生并以你喜欢的格式输出数据,例如
/* 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)
>> 的重载是大部分工作发生的地方,尽管逻辑很简单。您声明一个临时的 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 提供的容器是更好的方法,而不是尝试自己重新发明轮子(这样可以消除很多错误......)