我有以下User.h,它包含多个属性(字符串)。 User.cpp具有所有定义。
//User.h
#ifndef USER_H
#define USER_H
#include<iostream>
#include <cstring>
using namespace std;
class User{
string username;
public:
User();
string getUsername() const;
void setUsername(string);
};
#endif
我正在使用另一个类“File”从随机访问的.dat文件中插入新用户/查看用户
//File.h
#ifndef FILE_H
#define FILE_H
#include "User.h"
class File{
public:
void loadUser();
bool addUser(User&);
};
#endif
文件类定义
//File.cpp
#include<cstring>
#include<iostream>
#include<iomanip>
#include<fstream>
#include "User.h"
#include "File.h"
using namespace std;
User tempUser;
fstream usersFile;
void File::loadUser(){
usersFile.open("users.dat", ios::in | ios::binary);
usersFile.seekg(0);
// commenting the following lines prevented the issue
usersFile.read(reinterpret_cast<char *>(&tempUser), sizeof(tempUser));
cout<<tempUser.getUsername().c_str();
usersFile.close();
}
bool File::addUser(User& user){
usersFile.open("users.dat", ios::out | ios::ate | ios::binary);
// no issue when writing to file
usersFile.write( reinterpret_cast<const char *>(&user), sizeof(user));
usersFile.close();
cout<<"User added";
}
我在运行时遇到上述问题。但是没有编译问题。
在处理内部有“字符串属性”的对象时,处理时是否有任何问题?
请帮忙
答案 0 :(得分:1)
您无法读取此类非POD类型。字符串不是POD类型。 What are POD types in C++?
有几种方法可以正确读取字符串,具体取决于它们的存储方式。
对于文本文件:
如果字符串只是一个单词,两边用空格分隔,则可以使用普通的&gt;&gt;运营商。如果它不止一个单词,您可以将它存储在自己的行中,并使用getline。
对于二进制文件:
以null终止形式存储字符串。一次读一个字符,检查空字符。或者,在字符串前面加一个存储它的大小的整数。当你阅读它时,首先读入整数,然后读入那么多字符。
答案 1 :(得分:1)
我认为问题在于您将C ++代码与C思维模式混合在一起。
你应该在这里做的是使用提取运算符opeartor>>()
以及C ++ IO流。这与使用C标准IO和read()
函数相反。要编写对象,请使用插入运算符operator<<()
而不是C write()
函数。
使用std::string
处理字符串时。此课程提供operator<<()
和operator>>()
。因此,您可以说std::string s
然后io << s
和io >> s
,其中io
是一些C ++ IO流对象。这将做正确的事(tm)。这里的哲学是std::string
类比用户更了解如何序列化std::string
对象。所以让它做到这一点,&lt;&lt;和&gt;&gt;运算符。
继续这个想法,作为User
的作者,您比任何人都更了解如何序列化User
对象。所以提供&lt;&lt;和&gt;&gt;作为服务的类的用户的运营商。当你完全忘记如何正确序列化User
对象时,“你班级的用户”很可能就是你一周之后。 (或者,你认为你记得但实际上你忘记了一个细节,导致代码中的错误)。例如:
// in User.h
#include <string>
#include <iosfwd> // forward declarations of standard IO streams
namespace mine {
class User {
User(const std::string& name) : username(name) { }
friend std::ostream& operator<<(std::ostream&, const User&);
friend std::istream& operator>>(std::istream&, User&);
private:
std::string username;
};
std::ostream& operator<<(std::ostream& out, const User& u)
{
return out << u.username;
}
std::istream& operator>>(std::istream& in, User& u)
{
return in >> u.username;
}
} // namespace mine
从此处开始,将用户保存到您说的文件
std::ofstream f("filename");
User u("John");
f << u;
就是这样。阅读用户:
std::ifstream f2("filename");
f2 >> u;
将代码包装在命名空间中是一种很好的做法。 IDE通过显示自动完成功能可见的符号数量,可以很好地显示此问题。你可以看到全球范围内有多少混乱。通过将代码包装在命名空间中,可以将其分组到范围名称下,从而在全局名称范围中节省更多混乱。这只是整洁。如果您将代码放在自己的命名空间中,那么您可以为函数,类或变量选择任何名称,只要您之前没有选择它。如果您不将其放在命名空间中,则需要与其他人共享名称。这就像臭鼬宣告自己的领土,只是没有床上的气味。
根据该说明,我建议您从标题中取消using namespace std
。这会将std
命名空间中的所有符号放入#include
标题的所有文件的范围内。这是一个不好的做法。如果您愿意,只在实现文件中说using namespace std
,但不在头文件中。
当然,有些人会说这是一个坏主意。我个人认为,如果你知道你可能在那个特定的实现文件中有名称冲突的事实。但至少你知道using
语句在哪里:它在你的实现文件中,它只会导致该实现文件中的冲突。这是一种枪,(塑料水枪,但仍然是一把枪),只有你自己只能射击(弄湿)自己的脚,而不是别人的。在我看来,这完全没问题。
答案 2 :(得分:0)
是的,std :: string类不是普通的旧数据,换句话说它包含指针。 如果以这种方式保存/加载字符串类,则不会加载/保存指向的数据,只会加载指针的值。
此外,sizeof(tempUser)将不包括字符串指向的文本大小。
您的解决方案是改变您读/写数据的方式。 一种方法是使用boost :: serialization来处理像std :: string这样的数据类型。 另一种方法是自己将每个字符串写入文本文档中的单独行(而不是二进制模式),然后使用readline将其读回。
答案 3 :(得分:0)
string是一个对象,这意味着你不是在写它的内容。
尝试编写用户并检查文件以查看我的意思。你正在读的是一些指针,指向无效的内存位置。
答案 4 :(得分:0)
而不是构建自己的自定义序列化代码,像Google Protocol Buffers之类的东西可以用更少的精力做你想做的事。它非常适合从一个地方传递简单的结构化数据。